Dwarf DW_AT_location objdump 和 dwarfdump 不一致

Dwarf DW_AT_location objdump and dwarfdump inconsistent

我正在研究 CPython 并试图了解调试器的工作原理。 具体来说,我正在尝试获取最后一个 PyFrameObject 的位置,以便我可以遍历它并获取 Python 回溯。

在文件ceval.c中,第689行有函数的定义:

PyObject * PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)

我感兴趣的是f在栈上的位置。当使用 dwarfdump 转储二进制文件时,我得到 f 位于 $rbp-824,但是如果我使用 objdump 转储二进制文件,我得到的位置是 $rbp-808 -差异为 16。此外,在使用 GDB 进行调试时,我得到的正确答案是 $rbp-808,就像 objdump 给我的一样。为什么会出现差异,为什么 dwarfdump 不正确?我有什么不明白的?

如何从技术上重现问题: 从 Python 网站下载 python-2.7.17.tgz。提取.

我使用调试符号 (./configure --enable-pydebug && make) 从源代码编译了 python-2.7.17。 运行 对生成的 python 二进制文件执行以下命令:

dwarfdump Python-2.7.17/python 具有以下输出:

                        DW_AT_name                  f           
                        DW_AT_decl_file             0x00000001 /home/meir/code/python/Python-2.7.17/Python/ceval.c
                        DW_AT_decl_line             0x000002b1                         
                        DW_AT_type                  <0x00002916>
                        DW_AT_location              len 0x0003: 91c879: DW_OP_fbreg -824

我知道这是正确的 f 因为声明变量的行是 689 (0x2b1)。如您所见,位置是:

DW_AT_location len 0x0003: 91c879: DW_OP_fbreg -824:意思是$rbp-824.

运行命令 objdump -S Python-2.7.17/python 有以下输出:

PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
{
   f7577:       55                      push   %rbp
   f7578:       48 89 e5                mov    %rsp,%rbp
   f757b:       41 57                   push   %r15
   f757d:       41 56                   push   %r14
   f757f:       41 55                   push   %r13
   f7581:       41 54                   push   %r12
   f7583:       53                      push   %rbx
   f7584:       48 81 ec 38 03 00 00    sub    [=12=]x338,%rsp
   f758b:       48 89 bd d8 fc ff ff    mov    %rdi,-0x328(%rbp)
   f7592:       89 b5 d4 fc ff ff       mov    %esi,-0x32c(%rbp)
   f7598:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
   f759f:       00 00 
   f75a1:       48 89 45 c8             mov    %rax,-0x38(%rbp)
   f75a5:       31 c0                   xor    %eax,%eax

调试此输出将向您显示相关行是: f758b: 48 89 bd d8 fc ff ff mov %rdi,-0x328(%rbp) 你可以清楚地看到 f 是从 -0x328(%rbp) 加载的,也就是 $rbp-808。此外,GDB 支持这一发现。

那么,问题又来了,我错过了什么,为什么 dwarfdump 和现实之间存在 16 字节的差异?

谢谢

编辑: dwarfdump包括上面的函数是:

< 1><0x00004519>    DW_TAG_subprogram
                      DW_AT_external              yes(1)
                      DW_AT_name                  PyEval_EvalFrameEx
                      DW_AT_decl_file             0x00000001 /home/meir/code/python/Python-2.7.17/Python/ceval.c
                      DW_AT_decl_line             0x000002b1
                      DW_AT_prototyped            yes(1)
                      DW_AT_type                  <0x00000817>
                      DW_AT_low_pc                0x000f7577
                      DW_AT_high_pc               <offset-from-lowpc>53969
                      DW_AT_frame_base            len 0x0001: 9c: DW_OP_call_frame_cfa
                      DW_AT_GNU_all_tail_call_sites yes(1)
                      DW_AT_sibling               <0x00005bbe>
< 2><0x0000453b>      DW_TAG_formal_parameter
                        DW_AT_name                  f
                        DW_AT_decl_file             0x00000001 /home/meir/code/python/Python-2.7.17/Python/ceval.c
                        DW_AT_decl_line             0x000002b1
                        DW_AT_type                  <0x00002916>
                        DW_AT_location              len 0x0003: 91c879: DW_OP_fbreg -824

根据下面的回答,DW_OP_fbreg 偏离了框架基础 - 在我的例子中是 DW_OP_call_frame_cfa。我在识别框架基础时遇到问题。我的寄存器如下:

(gdb) info registers
rax            0xfffffffffffffdfe       -514
rbx            0x7f6a4887d040   140094460121152
rcx            0x7f6a48e83ff7   140094466441207
rdx            0x0      0
rsi            0x0      0
rdi            0x0      0
rbp            0x7ffd24bcef00   0x7ffd24bcef00
rsp            0x7ffd24bceba0   0x7ffd24bceba0
r8             0x7ffd24bcea50   140725219813968
r9             0x0      0
r10            0x0      0
r11            0x246    582
r12            0x7f6a48870df0   140094460071408
r13            0x7f6a48874b58   140094460087128
r14            0x1      1
r15            0x7f6a48873794   140094460082068
rip            0x5559834e99c0   0x5559834e99c0 <PyEval_EvalFrameEx+46153>
eflags         0x246    [ PF ZF IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0

如上所述,我已经知道 %rbp-808 有效。使用我拥有的寄存器的正确方法是什么?

编辑: 我终于明白了答案。我需要再展开一个函数,并找到我的函数被调用的地方。在那里,我正在寻找的变量确实在 $rsp 中并且 $rsp-824 是正确的

DW_OP_fbreg -824: Meaning $rbp-824

确实不是那个意思。这意味着,从 frame base(虚拟)寄存器偏移 -824,不一定(通常也不)等于 $rbp.

您需要查找 DW_AT_frame_base 才能知道当前函数中的框架基础是什么。

很可能它被定义为 DW_OP_call_frame_cfa,这是 $RSP 就在 当前函数被调用之前的值,等于 $RBP-16(由 CALL 指令保存的 return 地址的 8 个字节,由函数的第一条指令保存的先前 $RBP 的 8 个字节)。