调试器定位变量的原理

假设咱们确实在do_stuff中的断点处停了下来。咱们但愿调试器可以告诉咱们my_local变量的值,调试器怎么知道去×××到相关的信息呢?这可比定位函数要难多了,由于变量能够在全局数据区,能够在栈上,甚至是在寄存器中。另外,具备相同名称的变量在不一样的词法做用域中可能有不一样的值。调试信息必须可以反映出全部这些变化,而DWARF确实能作到这些。(用到了ptrace系统调用)
我不会涵盖全部的可能状况,做为例子,我将只展现调试器如何在do_stuff函数中定位到变量my_local。咱们从.debug_info段开始,再次看看do_stuff这一项,这一次咱们也看看其余的子项:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<1><71>: Abbrev Number: 5 (DW_TAG_subprogram)
     <72>   DW_AT_external    : 1
     <73>   DW_AT_name        : (...): do_stuff
     <77>   DW_AT_decl_file   : 1
     <78>   DW_AT_decl_line   : 4
     <79>   DW_AT_prototyped  : 1
     <7a>   DW_AT_low_pc      : 0x8048604
     <7e>   DW_AT_high_pc     : 0x804863e
     <82>   DW_AT_frame_base  : 0x0      (location list)
     <86>   DW_AT_sibling     : <0xb3>
  <2><8a>: Abbrev Number: 6 (DW_TAG_formal_parameter)
     <8b>   DW_AT_name        : (...): my_arg
     <8f>   DW_AT_decl_file   : 1
     <90>   DW_AT_decl_line   : 4
     <91>   DW_AT_type        : <0x4b>
     <95>   DW_AT_location    : (...)       (DW_OP_fbreg: 0)
  <2><98>: Abbrev Number: 7 (DW_TAG_variable)
     <99>   DW_AT_name        : (...): my_local
     <9d>   DW_AT_decl_file   : 1
     <9e>   DW_AT_decl_line   : 6
     <9f>   DW_AT_type        : <0x4b>
     <a3>   DW_AT_location    : (...)      (DW_OP_fbreg: -20)
<2><a6>: Abbrev Number: 8 (DW_TAG_variable)
     <a7>   DW_AT_name        : i
     <a9>   DW_AT_decl_file   : 1
     <aa>   DW_AT_decl_line   : 7
     <ab>   DW_AT_type        : <0x4b>
<af>   DW_AT_location    : (...)      (DW_OP_fbreg: -24)
注意每个表项中第一个尖括号里的数字,这表示嵌套层次——在这个例子中带有<2>的表项都是表项<1>的子项。所以咱们知道变量my_local(以DW_TAG_variable做为标签)是函数do_stuff的一个子项。调试器一样还对变量的类型感兴趣,这样才能正确的显示变量的值。这里my_local的类型根据DW_AT_type标签可知为<0x4b>。若是查看objdump的输出,咱们会发现这是一个有符号4字节整数。
要在执行进程的内存映像中实际定位到变量,调试器须要检查DW_AT_location属性。对于my_local来讲,这个属性为DW_OP_fberg: -20。这表示变量存储在从所包含它的函数的DW_AT_frame_base属性开始偏移-20处,而DW_AT_frame_base正表明了该函数的栈帧起始点。
函数do_stuff的DW_AT_frame_base属性的值是0×0(location list),这表示该值必需要在location list段去查询。咱们看看objdump的输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ objdump --dwarf=loc tracedprog2
  
tracedprog2:      file  format  elf32-i386
  
Contents of the .debug_loc section:
  
     Offset   Begin    End      Expression
     00000000 08048604 08048605 (DW_OP_breg4: 4 )
     00000000 08048605 08048607 (DW_OP_breg4: 8 )
     00000000 08048607 0804863e (DW_OP_breg5: 8 )
     00000000 <End of list>
     0000002c 0804863e 0804863f (DW_OP_breg4: 4 )
     0000002c 0804863f 08048641 (DW_OP_breg4: 8 )
     0000002c 08048641 0804865a (DW_OP_breg5: 8 )
0000002c <End of list>
关于位置信息,咱们这里感兴趣的就是第一个。对于调试器可能定位到的每个地址,它都会指定当前栈帧到变量间的偏移量,而这个偏移就是经过寄存器来计算的。对于x86体系结构,bpreg4表明esp寄存器,而bpreg5表明ebp寄存器。
让咱们再看看do_stuff的开头几条指令:
1
2
3
4
5
6
7
08048604 <do_stuff>:
  8048604:       55          push   ebp
  8048605:       89 e5       mov    ebp,esp
  8048607:       83 ec 28    sub    esp,0x28
  804860a:       8b 45 08    mov    eax, DWORD  PTR [ebp+0x8]
  804860d:       83 c0 02    add    eax,0x2
  8048610:       89 45 f4    mov     DWORD  PTR [ebp-0xc],eax
注意,ebp只有在第二条指令执行后才与咱们创建起关联,对于前两个地址,基地址由前面列出的位置信息中的esp计算得出。一旦获得了ebp的有效值,就能够很方便的计算出与它之间的偏移量。由于以后ebp保持不变,而esp会随着数据压栈和出栈不断移动。
那么这到底为咱们定位变量my_local留下了什么线索?咱们感兴趣的只是在地址0×8048610上的指令执行事后my_local的值(这里my_local的值会经过eax寄存器计算,然后放入内存)。所以调试器须要用到DW_OP_breg5: 8 基址来定位。如今回顾一下my_local的DW_AT_location属性:DW_OP_fbreg: -20。作下算数:从基址开始偏移-20,那就是ebp – 20,再偏移+8,咱们获得ebp – 12。如今再看看反汇编输出,注意到数据确实是从eax寄存器中获得的,而ebp – 12就是my_local存储的位置。
http://www.kuqin.com/system-analysis/20120808/324134.html
相关文章
相关标签/搜索