简体   繁体   English

如何在函数返回的 GDB 中设置断点?

[英]How to set a breakpoint in GDB where the function returns?

I have a C++ function which has many return statements at various places.我有一个 C++ 函数,它在不同的地方有很多 return 语句。 How to set a breakpoint at the return statement where the function actually returns ?如何在函数实际返回的 return 语句处设置断点?

And what does "break" command without argument means?没有参数的“break”命令是什么意思?

Contrary to answers so far, most compilers will create a single return assembly instruction, regardless of how many return statements are in the function (it is convenient for the compiler to do that, so there is only a single place to perform all the stack frame cleanup).与目前的答案相反,大多数编译器都会创建一条 return 汇编指令,无论函数中有多少个return语句(编译器这样做很方便,因此只有一个地方可以执行所有堆栈帧清理)。

If you wanted to stop on that instruction, all you have to do is disas and look for retq (or whatever the return instruction for your processor is), and set a breakpoint on it.如果您想停止该指令,您所要做的就是disas并查找retq (或任何处理器的返回指令),并在其上设置断点。 For example:例如:

int foo(int x)
{
  switch(x) {
   case 1: return 2;
   case 2: return 3;
   default: return 42;
  }
}

int main()
{
  return foo(0);
}


(gdb) disas foo
Dump of assembler code for function foo:
   0x0000000000400448 <+0>: push   %rbp
   0x0000000000400449 <+1>: mov    %rsp,%rbp
   0x000000000040044c <+4>: mov    %edi,-0x4(%rbp)
   0x000000000040044f <+7>: mov    -0x4(%rbp),%eax
   0x0000000000400452 <+10>:    mov    %eax,-0xc(%rbp)
   0x0000000000400455 <+13>:    cmpl   $0x1,-0xc(%rbp)
   0x0000000000400459 <+17>:    je     0x400463 <foo+27>
   0x000000000040045b <+19>:    cmpl   $0x2,-0xc(%rbp)
   0x000000000040045f <+23>:    je     0x40046c <foo+36>
   0x0000000000400461 <+25>:    jmp    0x400475 <foo+45>
   0x0000000000400463 <+27>:    movl   $0x2,-0x8(%rbp)
   0x000000000040046a <+34>:    jmp    0x40047c <foo+52>
   0x000000000040046c <+36>:    movl   $0x3,-0x8(%rbp)
   0x0000000000400473 <+43>:    jmp    0x40047c <foo+52>
   0x0000000000400475 <+45>:    movl   $0x2a,-0x8(%rbp)
   0x000000000040047c <+52>:    mov    -0x8(%rbp),%eax
   0x000000000040047f <+55>:    leaveq 
   0x0000000000400480 <+56>:    retq   
End of assembler dump.
(gdb) b *0x0000000000400480
Breakpoint 1 at 0x400480
(gdb) r

Breakpoint 1, 0x0000000000400480 in foo ()
(gdb) p $rax
$1 = 42

You can use reverse debugging to find out where function actually returns.您可以使用反向调试来找出函数实际返回的位置。 Finish executing current frame, do reverse-step and then you should stop at just returned statement.执行完当前帧,做反向步骤,然后你应该在刚刚返回的语句处停止。

(gdb) record
(gdb) fin
(gdb) reverse-step

Break on all retq of current function中断当前函数的所有retq

This Python command puts a breakpoint on every retq instruction of the current function:这个 Python 命令在当前函数的每个retq指令上放置一个断点:

class BreakReturn(gdb.Command):
    def __init__(self):
        super().__init__(
            'break-return',
            gdb.COMMAND_RUNNING,
            gdb.COMPLETE_NONE,
            False
        )
    def invoke(self, arg, from_tty):
        frame = gdb.selected_frame()
        # TODO make this work if there is no debugging information, where .block() fails.
        block = frame.block()
        # Find the function block in case we are in an inner block.
        while block:
            if block.function:
                break
            block = block.superblock
        start = block.start
        end = block.end
        arch = frame.architecture()
        pc = gdb.selected_frame().pc()
        instructions = arch.disassemble(start, end - 1)
        for instruction in instructions:
            if instruction['asm'].startswith('retq '):
                gdb.Breakpoint('*{}'.format(instruction['addr']))
BreakReturn()

Source it with:来源:

source gdb.py

and use the command as:并将命令用作:

break-return
continue

You should now be at retq .你现在应该在retq

Step until retq步进直到 retq

Just for fun, another implementation that stops when a retq is found (less efficient of because no hardware support):只是为了好玩,另一个实现在找到retq时停止(效率较低,因为没有硬件支持):

class ContinueReturn(gdb.Command):
    def __init__(self):
        super().__init__(
            'continue-return',
            gdb.COMMAND_RUNNING,
            gdb.COMPLETE_NONE,
            False
        )
    def invoke(self, arg, from_tty):
        thread = gdb.inferiors()[0].threads()[0]
        while thread.is_valid():
            gdb.execute('ni', to_string=True)
            frame = gdb.selected_frame()
            arch = frame.architecture()
            pc = gdb.selected_frame().pc()
            instruction = arch.disassemble(pc)[0]['asm']
            if instruction.startswith('retq '):
                break
ContinueReturn()

This will ignore your other breakpoints.这将忽略您的其他断点。 TODO: can be avoided? TODO:可以避免吗?

Not sure if it is faster or slower than reverse-step .不确定它是比reverse-step快还是慢。

A version that stops at a given opcode can be found at: https://stackoverflow.com/a/31249378/895245可以在以下位置找到在给定操作码处停止的版本: https : //stackoverflow.com/a/31249378/895245

break without arguments stops execution at the next instruction in the currently selected stack frame.不带参数的 break 在当前选定堆栈帧中的下一条指令处停止执行。 You select strack frames via the frame or up and down commands.您可以通过frameupdown命令选择跟踪帧。 If you want to debug the point where you are actually leaving the current function, select the next outer frame and break there.如果要调试实际离开当前函数的点,请选择下一个外部框架并在那里中断。

rr reverse debugging rr反向调试

Similar to GDB record mentioned at https://stackoverflow.com/a/3649698/895245 , but much more functional as of GDB 7.11 vs rr 4.1.0 in Ubuntu 16.04.类似于https://stackoverflow.com/a/3649698/895245 中提到的 GDB record ,但在 Ubuntu 16.04 中,GDB 7.11 与rr 4.1.0 相比功能更强大。

Notably, it deals with AVX correctly:值得注意的是,它正确处理 AVX:

which prevents it from working with the default standard library calls.这会阻止它使用默认的标准库调用。

Install Ubuntu 16.04:安装 Ubuntu 16.04:

sudo apt-get install rr linux-tools-common linux-tools-generic linux-cloud-tools-generic
sudo cpupower frequency-set -g performance

But also consider compiling from source to get the latest updates, it was not hard.但也要考虑从源代码编译以获得最新的更新,这并不难。

Test program:测试程序:

int where_return(int i) {
    if (i)
        return 1;
    else
        return 0;
}

int main(void) {
    where_return(0);
    where_return(1);
}

compile and run:编译并运行:

gcc -O0 -ggdb3 -o reverse.out -std=c89 -Wextra reverse.c
rr record ./reverse.out
rr replay

Now you are left inside a GDB session, and you can properly reverse debug:现在您被留在 GDB 会话中,您可以正确地反向调试:

(rr) break main
Breakpoint 1 at 0x56057c458619: file a.c, line 9.
(rr) continue
Continuing.

Breakpoint 1, main () at a.c:9
9           where_return(0);
(rr) step
where_return (i=0) at a.c:2
2           if (i)
(rr) finish
Run till exit from #0  where_return (i=0) at a.c:2
main () at a.c:10
10          where_return(1);
Value returned is $1 = 0
(rr) reverse-step
where_return (i=0) at a.c:6
6       }
(rr) reverse-step
5               return 0;

We are now on the correct return line.我们现在在正确的返回线上。

If you can change the source code, you might use some dirty trick with the preprocessor:如果您可以更改源代码,您可能会在预处理器中使用一些肮脏的技巧:

void on_return() {

}

#define return return on_return(), /* If the function has a return value != void */
#define return return on_return()  /* If the function has a return value == void */

/* <<<-- Insert your function here -->>> */

#undef return

Then set a breakpoint to on_return and go one frame up .然后设置一个断点on_return去一帧up

Attention: This will not work, if a function does not return via a return statement.注意:如果函数不通过return语句返回,这将不起作用。 So ensure, that it's last line is a return .所以确保它的最后一行是return

Example (shamelessly copied from C code, but will work also in C++):示例(无耻地从 C 代码复制,但也适用于 C++):

#include <stdio.h>

/* Dummy function to place the breakpoint */
void on_return(void) {

}

#define return return on_return()
void myfun1(int a) {
    if (a > 10) return;
    printf("<10\n");
    return;   
}
#undef return

#define return return on_return(),
int myfun2(int a) {
    if (a < 0) return -1;
    if (a > 0) return 1;
    return 0;
}
#undef return


int main(void)
{
    myfun1(1);
    myfun2(2);
}

The first macro will change第一个宏会改变

return;

to

return on_return();

Which is valid, since on_return also returns void .这是有效的,因为on_return也返回void

The second macro will change第二个宏会改变

return -1;

to

return on_return(), -1;

Which will call on_return() and then return -1 (thanks to the , -operator).这将调用on_return()然后返回 -1(感谢, -operator)。

This is a very dirty trick, but despite using backwards-stepping, it will work in multi-threaded environments and inlined functions, too.这是一个非常肮脏的技巧,但尽管使用了向后步进,它也可以在多线程环境和内联函数中工作。

Break without argument sets a breakpoint at the current line. Break without argument 在当前行设置断点。

There is no way for a single breakpoint to catch all return paths.单个断点无法捕获所有返回路径。 Either set a breakpoint at the caller immediately after it returns, or break at all return statements.要么在调用者返回后立即在调用者处设置断点,要么在所有return语句处中断。

Since this is C++, I suppose you could create a local sentry object, and break on its destructor, though.由于这是 C++,我想您可以创建一个本地哨兵对象,然后中断其析构函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM