我在文件delay.c
中有一个C程序:
void delay(int num)
{
volatile int i;
for(i=0; i<num; i++);
}
然后我使用命令gcc -g -O1 -o delay.o delay.c
在ARM仿真器(更具体地说是armel)上使用gcc 4.6.3编译程序。 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
我想从调试信息中找出变量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)
它显示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>
从地址4到38,delay
的框架基础应为r13 + 8
。因此,从地址c到20以及从地址28到38,i
的位置为r13 + 8 -12 = r13 - 4
。
但是,从汇编中,我们可以知道没有位置r13 - 4
,而i
显然位于r13 + 4
位置。
我是否错过了一些计算步骤?任何人都可以解释i
在调试信息计算和汇编之间的位置差异吗?
提前致谢!
答案 0 :(得分:3)
TL; DR 问题中的分析是正确的,差异是gcc组件之一中的错误(GNU Arm嵌入式工具链很明显可以记录一个)。
就目前情况而言,this other answer是错误的,因为它错误地将位置表达式求值时的堆栈指针值与函数输入时的堆栈指针值放在一起。
就DWARF而言,i
的位置随程序计数器的不同而不同。例如,考虑文本地址delay+0x18
。此时,i
的位置由DW_OP_fbreg(-12)
给出,即在帧基以下12个字节。帧基由父级DW_TAG_subprogram
的{{1}}属性给定,在这种情况下,该属性还取决于程序计数器:对于DW_AT_frame_base
,其表达式为delay+0x18
,即DW_OP_breg13(8)
。重要的是,此计算使用r13 + 8
的 current 值,即程序计数器等于r13
时的r13
值。
因此DWARF断言delay+0x18
处delay+0x18
位于i
处,即在现有堆栈的底部下方4个字节。对程序集的检查表明,在r13 + 8 - 12
处,delay+018
应该位于堆栈底部上方 4个字节。因此,DWARF是错误的,无论生成什么都是有缺陷的。
一个人可以使用i
并通过围绕问题中提供的测试用例的简单包装来演示该错误:
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)
处设置一个断点,并运行到第二次出现(我们期望delay+0x18
为1):
i
从反汇编中我们知道(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)
比堆栈指针高四个字节。确实有:
i
但是,虚假的DWARF意味着gdb在错误的位置显示:
(gdb) print *((int *)($r13 + 4))
$1 = 1
(gdb)
如上所述,DWARF错误地在堆栈指针下方的四个字节处给出了(gdb) print i
$2 = 0
(gdb)
的位置。那里是零,因此报告的值为i
:
i
这不是巧合。当要求(gdb) print *((int *)($r13 - 4))
$3 = 0
(gdb)
打印gdb
时,将再次出现写入到堆栈指针下方该虚假位置的幻数:
i
因此,在(gdb) set *((int *)($r13 - 4)) = 42
(gdb) print i
$6 = 42
(gdb)
, DWARF将delay+0x18
的位置错误地编码为i
,即使其真实位置是r13 - 4
。 / p>
可以通过手动编辑编译单元并用r13 + 4
(字节DW_OP_fbreg(-12)
)替换0x91 0x74
(字节DW_OP_fbreg(-4)
)来进一步走。这给出了
0x91 0x7c
换句话说,DWARF已得到纠正,以便在$ 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>
$
处delay+0x18
的位置被指定为i
,与程序集匹配。用更正后的DWARF重复gdb实验,每次循环时都会显示frame base - 4 = r13 + 8 - 4 = r13 + 4
的期望值:
i
答案 1 :(得分:2)
我不同意OP的asm分析:
00000000 <delay>: ; so far, let's suppose sp = sp(0)
0: e24dd008 sub sp, sp, #8 ; sp = sp(0) - 8
4: e3a03000 mov r3, #0 ; r3 = 0
8: e58d3004 str r3, [sp, #4] ; store the value of r3 in (sp + 4)
c: e59d3004 ldr r3, [sp, #4] ; load (sp + 4) in r3
10: e1500003 cmp r0, r3 ; compare r3 and r0
14: da000005 ble 30 <delay+0x30> ; go to end of loop
18: e59d3004 ldr r3, [sp, #4] ; i is in r3, and it is being loaded from
; (sp + 4), that is,
; sp(i) = sp(0) - 8 + 4 = sp(0) - 4
1c: e2833001 add r3, r3, #1 ; r3 = r3 + 1, that is, increment i
20: e58d3004 str r3, [sp, #4] ; store i (which is in r3) in (sp + 4),
; being again sp(i) = sp(0) - 8 + 4 = \
; sp(0) - 4
24: e59d3004 ldr r3, [sp, #4] ; load sp + 4 in r3
28: e1530000 cmp r3, r0 ; compare r3 and r0
2c: bafffff9 blt 18 <delay+0x18> ; go to init of loop
30: e28dd008 add sp, sp, #8 ; sp = sp + 8
34: e12fff1e bx lr ;
因此i
位于sp(0) - 4
中,这与侏儒分析相符(表明i
位于0 + 8 - 12
中)
编辑以添加有关我的DWARF分析的信息:
根据以下行:00000020 0000000c 00000020 (DW_OP_fbreg: -12)
,为DW_OP_fbreg:
DW_OP_fbreg操作提供了一个与LEB128的有符号偏移量 指定的地址 的DW_AT_frame_base属性中的位置描述 当前功能。 (这是 通常是一个“堆栈指针”寄存器,加上或减去一些偏移量。 在更复杂的系统上 它可能是一个位置列表,可根据 更改堆栈指针为 PC发生了变化。)
,地址为frame_base + offset,其中:
00000000 00000004 00000038 (DW_OP_breg13 (r13): 8)
),从00000004到00000038,偏移量为+8(r13是SP)鉴于此,DWARF表示它指向sp(0)+ 8-12 = sp(0) - 4