ARM中DWARF信息的局部变量位置

Los*_*min 11 debugging gcc arm reverse-engineering dwarf

我在文件中有一个C程序delay.c:

void delay(int num)
{
   volatile int i;
   for(i=0; i<num; i++);
}
Run Code Online (Sandbox Code Playgroud)

然后我使用命令在ARM模拟器(更具体地说)上用gcc 4.6.3编译程序gcc -g -O1 -o delay.o delay.c.组装delay.o是:

00000000 <delay>:
   0:   e24dd008    sub sp, sp, #8
   4:   e3a03000    mov r3, #0
   8:   e58d3004    str r3, [sp, #4]
   c:   e59d3004    ldr r3, [sp, #4]
  10:   e1500003    cmp r0, r3
  14:   da000005    ble 30 <delay+0x30>
  18:   e59d3004    ldr r3, [sp, #4]
  1c:   e2833001    add r3, r3, #1
  20:   e58d3004    str r3, [sp, #4]
  24:   e59d3004    ldr r3, [sp, #4]
  28:   e1530000    cmp r3, r0
  2c:   bafffff9    blt 18 <delay+0x18>
  30:   e28dd008    add sp, sp, #8
  34:   e12fff1e    bx  lr
Run Code Online (Sandbox Code Playgroud)

我想从调试信息中找出变量i在函数堆栈中的位置delay.下面是有关的信息delay,并i.debug_info部分:

<1><25>: Abbrev Number: 2 (DW_TAG_subprogram)
   <26>   DW_AT_external    : 1
   <27>   DW_AT_name        : (indirect string, offset: 0x19): delay
   <2b>   DW_AT_decl_file   : 1
   <2c>   DW_AT_decl_line   : 1
   <2d>   DW_AT_prototyped  : 1
   <2e>   DW_AT_low_pc      : 0x0
   <32>   DW_AT_high_pc     : 0x38
   <36>   DW_AT_frame_base  : 0x0      (location list)
   <3a>   DW_AT_sibling     : <0x59>
...
<2><4b>: Abbrev Number: 4 (DW_TAG_variable)
   <4c>   DW_AT_name        : i
   <4e>   DW_AT_decl_file   : 1
   <4f>   DW_AT_decl_line   : 3
   <50>   DW_AT_type        : <0x60>
   <54>   DW_AT_location    : 0x20     (location list)
Run Code Online (Sandbox Code Playgroud)

它显示i位置列表中的位置.所以我输出位置列表:

Offset   Begin    End      Expression
00000000 00000000 00000004 (DW_OP_breg13 (r13): 0)
00000000 00000004 00000038 (DW_OP_breg13 (r13): 8)
00000000 <End of list>
00000020 0000000c 00000020 (DW_OP_fbreg: -12)
00000020 00000024 00000028 (DW_OP_reg3 (r3))
00000020 00000028 00000038 (DW_OP_fbreg: -12)
00000020 <End of list>
Run Code Online (Sandbox Code Playgroud)

从地址4到38,delay应该是框架基础r13 + 8.因此,从地址c到20以及从地址28到38,位置ir13 + 8 -12 = r13 - 4.

但是,从装配中,我们可以知道没有位置r13 - 4,i显然是在位置r13 + 4.

我错过了一些计算步骤吗?任何人都可以解释i调试信息和汇编之间的位置差异?

提前致谢!

Rob*_*ris 5

TL;DR问题中的分析是正确的,差异是 gcc 组件之一中的错误(GNU Arm 嵌入式工具链是一个明显的记录位置)。

就目前情况而言,另一个答案是不正确的,因为它错误地将位置表达式求值时堆栈指针的值与函数入口处堆栈指针的较早值混为一谈。

就DWARF而言, 的位置i随程序计数器的不同而变化。例如,考虑文本地址delay+0x18。此时, 的位置i由 给出DW_OP_fbreg(-12),即帧基数以下 12 个字节。DW_TAG_subprogram框架基础由父级的属性给出DW_AT_frame_base,在这种情况下,该属性也取决于程序计数器:因为delay+0x18它的表达式是DW_OP_breg13(8),即r13 + 8。重要的是,此计算使用 的当前值,即程序计数器等于 时r13的值。r13delay+0x18

因此,DWARF 断言,atdelay+0x18位于ir13 + 8 - 12,即现有堆栈底部下方 4 个字节。对程序集的检查表明,atdelay+018应该i位于堆栈底部上方4个字节处。因此,DWARF 是错误的,无论生成什么,它都是有缺陷的。

gdb人们可以使用问题中提供的测试用例的简单包装来演示该错误:

$ cat delay.c
void delay(int num)
{
   volatile int i;
   for(i=0; i<num; i++);
}
$ gcc-4.6 -g -O1 -c delay.c
$ cat main.c
void delay(int);

int main(int argc, char **argv) {
    delay(3);
}
$ gcc-4.6 -o test main.c delay.o
$ gdb ./test
.
.
.
(gdb) 
Run Code Online (Sandbox Code Playgroud)

设置断点delay+0x18并运行到第二次出现的位置(我们期望i为 1):

(gdb) break *delay+0x18
Breakpoint 1 at 0x103cc: file delay.c, line 4.
(gdb) run
Starting program: /home/pi/test 

Breakpoint 1, 0x000103cc in delay (num=3) at delay.c:4
4      for(i=0; i<num; i++);
(gdb) cont
Continuing.

Breakpoint 1, 0x000103cc in delay (num=3) at delay.c:4
4      for(i=0; i<num; i++);
(gdb)
Run Code Online (Sandbox Code Playgroud)

从反汇编中我们知道它i位于堆栈指针上方的四个字节处。确实,它就在那里:

(gdb) print *((int *)($r13 + 4))
$1 = 1
(gdb)
Run Code Online (Sandbox Code Playgroud)

然而,伪造的 DWARF 意味着 gdb 查找到了错误的位置:

(gdb) print i
$2 = 0
(gdb)
Run Code Online (Sandbox Code Playgroud)

如上所述,DWARF 错误地给出了i堆栈指针下方四个字节的位置。那里有一个零,因此报告的值为i

(gdb)  print *((int *)($r13 - 4))
$3 = 0
(gdb)
Run Code Online (Sandbox Code Playgroud)

这并非巧合。gdb当要求打印时,写入堆栈指针下方的这个虚假位置的幻数会重新出现i

(gdb) set *((int *)($r13 - 4)) = 42
(gdb) print i
$6 = 42
(gdb)
Run Code Online (Sandbox Code Playgroud)

因此,在 处delay+0x18DWARF 错误地编码了ias的位置r13 - 4,尽管它的真实位置是r13 + 4

可以更进一步,手动编辑编译单元并将DW_OP_fbreg(-12)(bytes 0x91 0x74) 替换为DW_OP_fbreg(-4)(bytes 0x91 0x7c)。这给出了

$ readelf --debug-dump=loc delay.modified.o 
Contents of the .debug_loc section:

Offset   Begin            End              Expression
00000000 00000000 00000004 (DW_OP_breg13 (r13): 0)
0000000c 00000004 00000038 (DW_OP_breg13 (r13): 8)
00000018 <End of list>
00000020 0000000c 00000020 (DW_OP_fbreg: -4)
0000002c 00000024 00000028 (DW_OP_reg3 (r3))
00000037 00000028 00000038 (DW_OP_fbreg: -4)
00000043 <End of list>

$
Run Code Online (Sandbox Code Playgroud)

换句话说,DWARF 已被校正,因此,例如,delay+0x18的位置i给出为frame base - 4 = r13 + 8 - 4 = r13 + 4,与组件相匹配。使用更正后的 DWARF 重复 gdb 实验会显示i循环中每次的预期值:

$ gcc-4.6 -o test.modified main.c delay.modified.o
$ gdb ./test.modified 
.
.
.
(gdb) break *delay+0x18
Breakpoint 1 at 0x103cc: file delay.c, line 4.
(gdb) run
Starting program: /home/pi/test.modified 

Breakpoint 1, 0x000103cc in delay (num=3) at delay.c:4
4      for(i=0; i<num; i++);
(gdb) print i
$1 = 0
(gdb) cont
Continuing.

Breakpoint 1, 0x000103cc in delay (num=3) at delay.c:4
4      for(i=0; i<num; i++);
(gdb) print i
$2 = 1
(gdb) cont
Continuing.

Breakpoint 1, 0x000103cc in delay (num=3) at delay.c:4
4      for(i=0; i<num; i++);
(gdb) print i
$3 = 2
(gdb) cont
Continuing.
[Inferior 1 (process 30954) exited with code 03]
(gdb) 
Run Code Online (Sandbox Code Playgroud)