当您执行“中断函数名称”时,GDB如何确定要中断的地址?

时间:2014-08-28 10:00:13

标签: c debugging gdb stack

一个演示我的问题的简单示例:

// test.c
#include <stdio.h>

int foo1(int i) {
    i = i * 2;
    return i;
}

void foo2(int i) {
    printf("greetings from foo! i = %i", i);
}

int main() {
    int i = 7;
    foo1(i);
    foo2(i);
    return 0;
}
  

$ clang -o test -O0 -Wall -g test.c

在GDB内部我执行以下操作并开始执行:

(gdb) b foo1  
(gdb) b foo2

到达第一个断点后,我反汇编:

(gdb) disassemble 
Dump of assembler code for function foo1:
   0x0000000000400530 <+0>:     push   %rbp
   0x0000000000400531 <+1>:     mov    %rsp,%rbp
   0x0000000000400534 <+4>:     mov    %edi,-0x4(%rbp)
=> 0x0000000000400537 <+7>:     mov    -0x4(%rbp),%edi
   0x000000000040053a <+10>:    shl    $0x1,%edi
   0x000000000040053d <+13>:    mov    %edi,-0x4(%rbp)
   0x0000000000400540 <+16>:    mov    -0x4(%rbp),%eax
   0x0000000000400543 <+19>:    pop    %rbp
   0x0000000000400544 <+20>:    retq   
End of assembler dump.

我在达到第二个断点后也这样做了:

(gdb) disassemble 
Dump of assembler code for function foo2:
   0x0000000000400550 <+0>:     push   %rbp
   0x0000000000400551 <+1>:     mov    %rsp,%rbp
   0x0000000000400554 <+4>:     sub    $0x10,%rsp
   0x0000000000400558 <+8>:     lea    0x400644,%rax
   0x0000000000400560 <+16>:    mov    %edi,-0x4(%rbp)
=> 0x0000000000400563 <+19>:    mov    -0x4(%rbp),%esi
   0x0000000000400566 <+22>:    mov    %rax,%rdi
   0x0000000000400569 <+25>:    mov    $0x0,%al
   0x000000000040056b <+27>:    callq  0x400410 <printf@plt>
   0x0000000000400570 <+32>:    mov    %eax,-0x8(%rbp)
   0x0000000000400573 <+35>:    add    $0x10,%rsp
   0x0000000000400577 <+39>:    pop    %rbp
   0x0000000000400578 <+40>:    retq   
End of assembler dump.

当设置断点时,GDB显然使用不同的偏移量(foo1中的+7和foo2中的+19),相对于函数的开头。如何在不使用GDB的情况下自行确定此偏移量?

3 个答案:

答案 0 :(得分:4)

gdb使用一些方法来决定这些信息。

首先,最好的方法是编译器发出描述函数的DWARF。然后gdb可以解码DWARF以找到序言的结尾。

但是,这并不总是可用。 GCC会发出它,但只有在使用优化时才会发出IIRC。

我相信还有一个约定,即如果在行表中重复函数的第一行号,那么第二个实例的地址将用作序言的结尾。如果线条看起来像:

< function f >
line 23  0xffff0000
line 23  0xffff0010

然后gdb将假设函数f的序言在0xfff0010处完成。

我认为这是gcc在未优化时使用的模式。

最后,gdb有一些序言解码器,它们知道在许多平台上如何编写常见的序言。当debuginfo不可用时使用这些,虽然我不记得它的目的是什么。

答案 1 :(得分:2)

在像linux这样的ELF平台上,调试信息存储在可执行文件的单独(不可执行)部分中。在这个单独的部分中,调试器需要所有信息。检查DWARF2规范以了解详细信息。

答案 2 :(得分:0)

正如其他人所提到的,即使没有调试符号,GDB也有一个函数序言解码器,即启发式魔法。

要禁用它,您可以在函数名称之前添加星号:

break *func

在Binutils 2.25上,跳过算法似乎位于:symtab.c:skip_prologue_salbreakpoints.c:break_command,命令定义,间接调用。

序言是在函数调用开始时使用的常见“样板”。

foo2的序言比foo1的序言要长两个,因为:

  • sub $0x10,%rsp

    foo2调用另一个函数,因此它不是叶函数。这可以防止一些优化,特别是它必须在另一次调用之前减少rsp以节省本地状态的空间。

    由于128字节的ABI红色区域,

    叶子函数不需要,请参阅:Why does the x86-64 GCC function prologue allocate less stack than the local variables?

    foo1但是是一个叶子函数。

  • lea 0x400644,%rax

    由于某种原因,clang将本地字符串常量的地址(存储在.rodata中)存储在寄存器中,作为函数序言的一部分。

    我们知道rax包含"greetings from foo! i = %i",因为它会传递给%rdi printf的第一个参数。

    foo1没有本地字符串常量。

序言的其他说明对两个函数都是通用的: