繁体   English   中英

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

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

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

没有参数的“break”命令是什么意思?

与目前的答案相反,大多数编译器都会创建一条 return 汇编指令,无论函数中有多少个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

您可以使用反向调试来找出函数实际返回的位置。 执行完当前帧,做反向步骤,然后你应该在刚刚返回的语句处停止。

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

中断当前函数的所有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()

来源:

source gdb.py

并将命令用作:

break-return
continue

你现在应该在retq

步进直到 retq

只是为了好玩,另一个实现在找到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()

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

不确定它是比reverse-step快还是慢。

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

不带参数的 break 在当前选定堆栈帧中的下一条指令处停止执行。 您可以通过frameupdown命令选择跟踪帧。 如果要调试实际离开当前函数的点,请选择下一个外部框架并在那里中断。

rr反向调试

类似于https://stackoverflow.com/a/3649698/895245 中提到的 GDB record ,但在 Ubuntu 16.04 中,GDB 7.11 与rr 4.1.0 相比功能更强大。

值得注意的是,它正确处理 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

但也要考虑从源代码编译以获得最新的更新,这并不难。

测试程序:

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

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

编译并运行:

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

现在您被留在 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;

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

如果您可以更改源代码,您可能会在预处理器中使用一些肮脏的技巧:

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

然后设置一个断点on_return去一帧up

注意:如果函数不通过return语句返回,这将不起作用。 所以确保它的最后一行是return

示例(无耻地从 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);
}

第一个宏会改变

return;

return on_return();

这是有效的,因为on_return也返回void

第二个宏会改变

return -1;

return on_return(), -1;

这将调用on_return()然后返回 -1(感谢, -operator)。

这是一个非常肮脏的技巧,但尽管使用了向后步进,它也可以在多线程环境和内联函数中工作。

Break without argument 在当前行设置断点。

单个断点无法捕获所有返回路径。 要么在调用者返回后立即在调用者处设置断点,要么在所有return语句处中断。

由于这是 C++,我想您可以创建一个本地哨兵对象,然后中断其析构函数。

暂无
暂无

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

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