C语言 如何正确使用libdwarf信息获取局部变量位置

xfb7svmp  于 2023-06-05  发布在  其他
关注(0)|答案(1)|浏览(523)

前言:我很抱歉为我的问题准备了很长时间,这样做的原因是为了确保这篇文章是独立的,并希望包括我找到的所有必要信息。
我的问题与先生的这篇好文章相关。Eli Bendersky
因此,我将使用下面的输入代码来回答我的问题:

#include <stdio.h>
void do_stuff(int my_arg)
{
    int my_local = my_arg + 2;
    int i;

    for (i = 0; i < my_local; ++i)
        printf("i = %d\n", i);
}
int main()
{
    do_stuff(2);
    return 0;
}

以上代码编译为gcc -g tracedprog2.c -o tracedprog2
此外,我将使用这里分享的libdwarf示例https://github.com/timsnyder/libdwarf-code/tree/3e75142a5d8938466e00a942c41a04f69510915d,它可以通过以下步骤轻松构建,以使用该程序复制我的发现(这不是必需的,只是想分享一下,以防有人可能正在寻找它):

cd libdwarf-code
mkdir build && cd build
cmake -DBUILD_DWARFEXAMPLE=TRUE ..
make -j4
// built binaries will be available in the directory: $HOME/libdwarf-code/build/src/bin/dwarfexample

问题是如标题所述,如何使用libdwarf收集的信息来获取局部变量的位置?
正如先生所说。Bendersky的帖子中,要做的第一件事是通过objdump --dwarf=info ./tracedprog2获取libdwarf信息,它将输出如下信息(我只包含有用的信息):

<1><8a>: Abbrev Number: 5 (DW_TAG_subprogram)                                                                                                                
    <8b>   DW_AT_external    : 1                                                                                                                              
    <8b>   DW_AT_name        : (indirect string, offset: 0x29): do_stuff                                                                                      
...                                                                                                                          
    <92>   DW_AT_low_pc      : 0x1135
    <9a>   DW_AT_high_pc     : 0x43
    <a2>   DW_AT_frame_base  : 1 byte block: 9c         (DW_OP_call_frame_cfa)
    <a4>   DW_AT_GNU_all_tail_call_sites: 1
...
 <2><b3>: Abbrev Number: 7 (DW_TAG_variable)
    <b4>   DW_AT_name        : (indirect string, offset: 0x0): my_local
...
    <bb>   DW_AT_type        : <0x57>
    <bf>   DW_AT_location    : 2 byte block: 91 68      (DW_OP_fbreg: -24)

我的理解是,为了找出局部变量的位置,需要许多信息(如操作码所示):

  1. libdwarf的框架基础:DW_OP_call_frame_cfa
  2. libdwarf的局部变量偏移量:DW_OP_fbreg
    现在这里是事情变得相当棘手的地方,在阅读DWARF指南(https://dwarfstd.org/doc/DWARF5.pdf)后,它指出:
    The DW_OP_call_frame_cfa operation pushes the value of the CFA, obtained from the Call Frame Information (see Section 6.4 on page 171)
    这就是上面共享的来自dwarfexample的二进制frame1https://github.com/timsnyder/libdwarf-code/tree/3e75142a5d8938466e00a942c41a04f69510915d/src/bin/dwarfexample)试图将此CFA信息解析为用户可读格式的地方。
    在运行./frame1 tracedprog2代码时,您得到的输出看起来像这样(这个程序将从帧描述条目(FDE)解析调用信息条目(CIE)信息);下面是函数do_stuff的帧信息,因为这是这个问题的焦点。我发现了一种更好的方法,使用readelf -w ./tracedprog2输出数据
00000088 000000000000001c 0000005c FDE cie=00000030 pc=0000000000001135..0000000000001178
  DW_CFA_advance_loc: 1 to 0000000000001136
  DW_CFA_def_cfa_offset: 16
  DW_CFA_offset: r6 (rbp) at cfa-16
  DW_CFA_advance_loc: 3 to 0000000000001139
  DW_CFA_def_cfa_register: r6 (rbp)
  DW_CFA_advance_loc: 62 to 0000000000001177
  DW_CFA_def_cfa: r7 (rsp) ofs 8
  DW_CFA_nop
  DW_CFA_nop
  DW_CFA_nop

从DWARF 5书的描述来看,

15. DW_CFA_def_cfa takes two unsigned LEB128 arguments representing a
register number and an offset. The required action is to define the
current CFA rule to use the provided register and offset.
16. DW_CFA_def_cfa_register takes a single unsigned LEB128 argument
representing a register number. The required action is to define the
current CFA rule to use the provided register (but to keep the old
offset).
17. DW_CFA_def_cfa_offset takes a single unsigned LEB128 argument
representing an offset. The required action is to define the current CFA
rule to use the provided offset (but to keep the old register).

重要的信息似乎是DW_CFA_def_cfaDW_CFA_def_cfa_register的值,我认为这可能是我正在寻找的帧基。
因此,要获取变量my_local的位置,我认为需要做以下工作:
首先,CFA是DW_CFA_def_cfa中定义的RSP + 8。接下来,DW_CFA_offsetcfa - 16,这使它成为RSP - 8?从那里,有DW_CFA_def_cfa_offset: 16,这似乎表明我需要像这样添加RSP - 8 + 16,使其成为RSP + 8。然后,使用值DW_CFA_def_cfa_register: r6 (rbp)RSP更改为RBP,因此现在是RBP + 8。从这里开始,添加my_local变量的DW_OP_fbreg: -24以获得RBP - 0x10。但是,我看到在objdump中,它是-0x14(%rbp),%eax

0000000000001135 <do_stuff>:
    1135:       55                      push   %rbp
    1136:       48 89 e5                mov    %rsp,%rbp
    1139:       48 83 ec 20             sub    $0x20,%rsp
    113d:       89 7d ec                mov    %edi,-0x14(%rbp)
    1140:       8b 45 ec                mov    -0x14(%rbp),%eax
    1143:       83 c0 02                add    $0x2,%eax
    1146:       89 45 f8                mov    %eax,-0x8(%rbp)

我相信我能够找到计算局部变量location所需的所有必要信息,但似乎我在某个地方遗漏了一些东西。有人能告诉我我错过了什么吗?先谢谢你。

j9per5c4

j9per5c41#

所以看起来我误解了objdump -S ./tracedprog2向我展示的内容,导致了我上面所说的错误结论。
例如,使用DWARF信息转储将显示反汇编+源代码,如下所示:

void do_stuff(int my_arg)                                                                                                                                     
{                                                                                                                                                             
    1135:       55                      push   %rbp                                                                                                           
    1136:       48 89 e5                mov    %rsp,%rbp                                                                                                      
    1139:       48 83 ec 20             sub    $0x20,%rsp                                                                                                     
    113d:       89 7d ec                mov    %edi,-0x14(%rbp)
    int my_local = my_arg + 2;                                                 
    1140:       8b 45 ec                mov    -0x14(%rbp),%eax
    1143:       83 c0 02                add    $0x2,%eax
    1146:       89 45 f8                mov    %eax,-0x8(%rbp)
    int i;                                                                     
                                                                               
    for (i = 0; i < my_local; ++i)
    1149:       c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
    1150:       eb 1a                   jmp    116c <do_stuff+0x37>
        printf("i = %d\n", i);                                                 
    1152:       8b 45 fc                mov    -0x4(%rbp),%eax
    1155:       89 c6                   mov    %eax,%esi
    1157:       48 8d 3d a6 0e 00 00    lea    0xea6(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
    115e:       b8 00 00 00 00          mov    $0x0,%eax
    1163:       e8 c8 fe ff ff          callq  1030 <printf@plt>
    for (i = 0; i < my_local; ++i)

如你所见my_local就在这条线的正上方
1140: 8b 45 ec mov -0x14(%rbp),%eax
这让我相信我试图找到-0x14(%rbp)的偏移计算。
现在我想我有一个很好的想法是什么发生后,阅读许多额外的来源,采取了一点找到(我会引用他们下面的情况下,任何人都想验证我的答案)。
长话短说,让我扩展我上面显示的信息,看看我是否可以澄清我的理解以及我是如何得到解决方案的:

00000000 0000000000000014 00000000 CIE                                                                                                                        
  Version:               1                                                                                                                                    
  Augmentation:          "zR"                                                                                                                                 
  Code alignment factor: 1                                                                                                                                    
  Data alignment factor: -8                                                                                                                                   
  Return address column: 16                                                                                                                                   
  Augmentation data:     1b                                                                                                                                   
  DW_CFA_def_cfa: r7 (rsp) ofs 8                                                                                                                              
  DW_CFA_offset: r16 (rip) at cfa-8                                                                                                                           
  DW_CFA_undefined: r16 (rip) 
...
00000088 000000000000001c 0000005c FDE cie=00000030 pc=0000000000001135..0000000000001178
  DW_CFA_advance_loc: 1 to 0000000000001136
  DW_CFA_def_cfa_offset: 16
  DW_CFA_offset: r6 (rbp) at cfa-16
  DW_CFA_advance_loc: 3 to 0000000000001139
  DW_CFA_def_cfa_register: r6 (rbp)

Contents of the .debug_loc section:

    Offset   Begin            End              Expression
    00000000 0000000000001178 0000000000001179 (DW_OP_breg7 (rsp): 8)
    00000014 0000000000001179 000000000000117c (DW_OP_breg7 (rsp): 16)
    00000028 000000000000117c 000000000000118c (DW_OP_breg6 (rbp): 16)
    0000003c 000000000000118c 000000000000118d (DW_OP_breg7 (rsp): 8)
    00000050 <End of list>
    00000060 0000000000001135 0000000000001136 (DW_OP_breg7 (rsp): 8)
    00000074 0000000000001136 0000000000001139 (DW_OP_breg7 (rsp): 16)
    00000088 0000000000001139 0000000000001177 (DW_OP_breg6 (rbp): 16)
    0000009c 0000000000001177 0000000000001178 (DW_OP_breg7 (rsp): 8)
    000000b0 <End of list>

以上附加信息可以通过使用DWARF2而不是默认的DWARF5进行编译来找到(源代码:https://blog.tartanllama.xyz/writing-a-linux-debugger-variables/)。
首先,CFA寄存器初始设置为rsp + 8(源代码:https://lists.dwarfstd.org/pipermail/dwarf-discuss/2010-August/000915.html)。
然后,在到达地址0x1135处的do_stuff帧时,我们在FDE表中的0x1136处插入一个新行(因此,这就是值1所表示的)。现在,由于语句DF_CFA_def_cfa_offset,该列的偏移量为16。
这是什么意思?而不是我们之前看到的rsp + 8,现在从0x1136开始,直到该行结束,现在将是rsp + 16
因此,接下来,我们在将3添加到当前地址(例如,0x1139)后创建一个新行,并从这里开始,我们将CFA寄存器定义为rbp。因为我们直到现在才改变偏移量,这意味着从0x1139开始,它将是rbp + 16而不是rsp + 16
基本上,就是这样,我们正在寻找的帧基,以计算局部变量my_localrsp + 16。现在我们看一下.debug_loc部分的内容,结果似乎与我上面的解释一致。
现在回到

<2><b3>: Abbrev Number: 7 (DW_TAG_variable)
    <b4>   DW_AT_name        : (indirect string, offset: 0x0): my_local
...
    <bb>   DW_AT_type        : <0x57>
    <bf>   DW_AT_location    : 2 byte block: 91 68      (DW_OP_fbreg: -24)

有一个DW_OP_fbreg: -24值,你只需把它加到我们找到的帧基上,这意味着rbp + 16 - 24 = rbp - 8,这将是一个等价的mov %eax, -0x8(%rbp)
现在我看着先生。Bendersky的帖子再次,这似乎与他的答案一致,但我不知何故错过了它,显然开始DWARF版本5,.debug_loc似乎默认不包括在内,这误导了我最初追求错误的结论。
我希望这个解决方案是正确的(我认为它对我有意义),如果它是不正确的,请让我知道,因为我也仍然不确定DWARF(这是非常复杂的新手像我一样)。

相关问题