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

avd*_*avd 22 c++ debugging gdb

我有一个C++函数,它在不同的地方有很多返回语句.如何在函数实际返回的return语句中设置断点?

什么"破坏"命令没有参数意味着什么?

Emp*_*ian 20

与目前为止的答案相反,大多数编译器将创建一个返回汇编指令,无论return函数中有多少语句(编译器都方便这样做,因此只有一个地方可以执行所有堆栈帧清理).

如果你想停止该指令,你所要做的就是disas寻找retq(或处理器的返回指令),并在其上设置一个断点.例如:

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
Run Code Online (Sandbox Code Playgroud)

  • 有趣的!我制作了一个 Python 命令,可以找到 `retq` 并自动在那里放置一个断点:http://stackoverflow.com/a/31264709/895245 (2认同)

ks1*_*322 19

您可以使用反向调试来找出函数实际返回的位置.完成执行当前帧,执行reverse-step然后你应该停在刚返回的语句.

(gdb) record
(gdb) fin
(gdb) reverse-step
Run Code Online (Sandbox Code Playgroud)

  • 根据该页面,这需要Linux-x86,并且它可能具有强大的性能损失.+1无论如何,因为它太酷了. (2认同)
  • http://rr-project.org/上的`rr`工具可以通过Linux上的重放实现反向调试,同时只会导致1.2倍的速度下降(至少根据其网站).它变得更酷,甚至更酷.:) (2认同)
  • @pnkfelix `rr` 与 AVX 一起正常工作,这是一个了不起的工具。在以下位置添加了设置示例:/sf/answers/3228184921/ (2认同)

Cir*_*四事件 7

打破当前功能的所有retq

这个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()
Run Code Online (Sandbox Code Playgroud)

来源:

source gdb.py
Run Code Online (Sandbox Code Playgroud)

并使用命令:

break-return
continue
Run Code Online (Sandbox Code Playgroud)

你现在应该在retq.

一直到retq

只是为了好玩,另一个实现在retq找到a时停止(由于没有硬件支持而效率较低):

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()
Run Code Online (Sandbox Code Playgroud)

这将忽略您的其他断点.TODO:可以避免吗?

不确定它是否比它更快或更慢retq.

可以在以下位置找到在给定操作码处停止的版本:https://stackoverflow.com/a/31249378/895245

  • 很好地实现了“retq”的中断,谢谢!尽管如果“return val;”被翻译成几个汇编指令可能会存在问题,“retq”之前的指令会重写寄存器,返回的“val”在被移动到“%rax”后曾经位于该寄存器中,并使 gdb(和我自己)感到困惑。我将尝试获取“retq”所在行的行号(来自“disas /s func”的输出),并在这些行号上创建断点。 (2认同)

Jim*_*som 6

不带参数的break在当前所选堆栈帧的下一条指令处停止执行.您可以通过frameor updown命令选择strack frames .如果要调试实际离开当前函数的点,请选择下一个外框并在那里中断.

  • 断点设置在当前指令处,而不是下一个指令.如果您正在执行命令,则执行已经停止.当前函数处于活动状态时,调用函数中的任何断点都不会发生,除非它是递归,在这种情况下,这种调试会令人困惑. (3认同)
  • 您如何“选择下一个外部框架并在此处折断”?你能澄清一下吗?(请注意,目标是在函数内部*有一个断点(例如,能够查看其局部变量),但要在返回之前。 (3认同)

Cir*_*四事件 5

rr 反向调试

/sf/answers/255478891/record提到的GDB相似,但是从GDB 7.11 到Ubuntu 16.04中的4.1.0,功能更多。rr

值得注意的是,它可以正确处理AVX:

这使其无法使用默认的标准库调用。

安装Ubuntu 16.04:

sudo apt-get install rr linux-tools-common linux-tools-generic linux-cloud-tools-generic
sudo cpupower frequency-set -g performance
Run Code Online (Sandbox Code Playgroud)

但也可以考虑从源代码进行编译以获取最新更新,这并不难。

测试程序:

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

int main(void) {
    where_return(0);
    where_return(1);
}
Run Code Online (Sandbox Code Playgroud)

编译并运行:

gcc -O0 -ggdb3 -o reverse.out -std=c89 -Wextra reverse.c
rr record ./reverse.out
rr replay
Run Code Online (Sandbox Code Playgroud)

现在您就处于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;
Run Code Online (Sandbox Code Playgroud)

我们现在位于正确的返回线上。