C 内存中的局部变量位置
作为家庭作业,我得到了一些c文件,并使用arm linux gcc编译了它们(我们最终将针对gumstix板,但对于这些练习,我们一直在使用qemu和ema) 其中一个问题让我有点困惑——我们被告知: 使用arm linux objdump在可执行二进制文件中查找main()中声明的变量的位置 然而,这些变量是本地的,因此在运行时之前不应该有地址,对吗C 内存中的局部变量位置,c,arm,objdump,C,Arm,Objdump,作为家庭作业,我得到了一些c文件,并使用arm linux gcc编译了它们(我们最终将针对gumstix板,但对于这些练习,我们一直在使用qemu和ema) 其中一个问题让我有点困惑——我们被告知: 使用arm linux objdump在可执行二进制文件中查找main()中声明的变量的位置 然而,这些变量是本地的,因此在运行时之前不应该有地址,对吗 我在想,也许我需要找到的是堆栈帧中的偏移量,实际上可以使用objdump(我不知道如何找到) 无论如何,对此事的任何见解都将不胜感激,如有必要,
我在想,也许我需要找到的是堆栈帧中的偏移量,实际上可以使用objdump(我不知道如何找到)
无论如何,对此事的任何见解都将不胜感激,如有必要,我将很乐意发布源代码。这将取决于程序以及他们希望变量位置的准确程度。问题是否需要它们存储在哪个代码段中。康斯特bss等?它需要特定的地址吗?无论哪种方式,一个好的开始都是使用objdump-S标志
objdump -S myprogram > dump.txt
这很好,因为它将打印出源代码和带有地址的程序集的混合。从这里开始,只需搜索intmain
,就可以开始了
unsigned int one ( unsigned int, unsigned int );
unsigned int two ( unsigned int, unsigned int );
unsigned int myfun ( unsigned int x, unsigned int y, unsigned int z )
{
unsigned int a,b;
a=one(x,y);
b=two(a,z);
return(a+b);
}
编译和反汇编
arm-none-eabi-gcc -c fun.c -o fun.o
arm-none-eabi-objdump -D fun.o
由编译器创建的代码
00000000 <myfun>:
0: e92d4800 push {fp, lr}
4: e28db004 add fp, sp, #4
8: e24dd018 sub sp, sp, #24
c: e50b0010 str r0, [fp, #-16]
10: e50b1014 str r1, [fp, #-20]
14: e50b2018 str r2, [fp, #-24]
18: e51b0010 ldr r0, [fp, #-16]
1c: e51b1014 ldr r1, [fp, #-20]
20: ebfffffe bl 0 <one>
24: e50b0008 str r0, [fp, #-8]
28: e51b0008 ldr r0, [fp, #-8]
2c: e51b1018 ldr r1, [fp, #-24]
30: ebfffffe bl 0 <two>
34: e50b000c str r0, [fp, #-12]
38: e51b2008 ldr r2, [fp, #-8]
3c: e51b300c ldr r3, [fp, #-12]
40: e0823003 add r3, r2, r3
44: e1a00003 mov r0, r3
48: e24bd004 sub sp, fp, #4
4c: e8bd4800 pop {fp, lr}
50: e12fff1e bx lr
堆栈帧消失,局部变量保留在寄存器中
00000000:
0:e92d4038推送{r3,r4,r5,lr}
4:e1a05002 mov r5,r2
8:ebfffffe bl 0
c:e1a04000 mov r4,r0
10:e1a01005 mov r1,r5
14:ebfffffe bl 0
18:e0800004添加r0、r0、r4
1c:e8bd4038 pop{r3,r4,r5,lr}
20:E12FF1E bx lr
编译器决定做的是通过将寄存器保存在堆栈上,为自己提供更多的寄存器。为什么它救了r3是个谜,但这是另一个话题
按照调用约定输入函数r0=x,r1=y和r2=z,我们可以不使用r0和r1(使用一个函数(y,x)再试一次,看看会发生什么),因为它们正好落在一个函数()中,不再使用。调用约定说r0-r3可以被一个函数破坏,所以我们需要为以后保留z,所以我们将它保存在r5中。根据调用约定,one()的结果是r0,因为two()会破坏r0-r3,我们需要保存一个以备以后使用,在调用two()之后,我们仍然需要r0来调用two,因此r4现在保存一个。在调用one之前,我们在r5中保存了z(在r2中移动到r5),我们需要one()的结果作为two()的第一个参数,它已经存在,我们需要z作为第二个参数,所以我们将r5移动到保存z到r1的位置,然后我们调用two()。每个调用约定的two()的结果。由于基本数学属性中的b+a=a+b,返回前的最终加法是r0+r4,即b+a,结果进入r0,根据约定,r0是用于从函数返回内容的寄存器。清理堆栈并恢复修改后的寄存器,完成
由于myfun()使用bl调用其他函数,bl修改链接寄存器(r14),为了能够从myfun()返回,我们需要保留链接寄存器中的值,从函数的入口到最终返回(bx lr),因此lr被推到堆栈上。约定规定我们可以在函数中销毁r0-r3,但不能销毁其他寄存器,因此r4和r5被推到堆栈上,因为我们使用了它们。为什么r3被推到堆栈上从调用约定的角度来看是不必要的,我想知道它是否是在预期64位内存系统的情况下完成的,进行两次完整的64位写入比一次64位写入和一次32位写入便宜。但是您需要知道堆栈的对齐方式,所以这只是一个理论。没有理由在这段代码中保留r3
现在掌握这些知识,反汇编分配的代码(arm-…-objdump-D something.something),并进行同样的分析。特别是对于名为main()的函数和未名为main()的函数(我没有特意使用main()),堆栈帧的大小可能没有意义,或者比其他函数的意义小。在上面的非优化情况下,我们需要总共存储6个东西,x,y,z,a,b和链接寄存器6*4=24字节,这导致了sub sp,sp,#24,我需要考虑堆栈指针和帧指针
有一点事。我认为有一个命令行参数告诉编译器不要使用帧指针-fomit帧指针,它保存了两条指令
00000000 <myfun>:
0: e52de004 push {lr} ; (str lr, [sp, #-4]!)
4: e24dd01c sub sp, sp, #28
8: e58d000c str r0, [sp, #12]
c: e58d1008 str r1, [sp, #8]
10: e58d2004 str r2, [sp, #4]
14: e59d000c ldr r0, [sp, #12]
18: e59d1008 ldr r1, [sp, #8]
1c: ebfffffe bl 0 <one>
20: e58d0014 str r0, [sp, #20]
24: e59d0014 ldr r0, [sp, #20]
28: e59d1004 ldr r1, [sp, #4]
2c: ebfffffe bl 0 <two>
30: e58d0010 str r0, [sp, #16]
34: e59d2014 ldr r2, [sp, #20]
38: e59d3010 ldr r3, [sp, #16]
3c: e0823003 add r3, r2, r3
40: e1a00003 mov r0, r3
44: e28dd01c add sp, sp, #28
48: e49de004 pop {lr} ; (ldr lr, [sp], #4)
4c: e12fff1e bx lr
00000000:
0:e52de004推送{lr};(str lr,[sp,#-4]!)
4:e24dd01c子sp,sp,#28
8:e58d000c str r0[sp,#12]
c:e58d1008 str r1[sp,#8]
10:e58d2004 str r2[sp,#4]
14:e59d000c ldr r0,[sp,#12]
18:e59d1008 ldr r1[sp,#8]
1c:ebfffffe bl 0
20:e58d0014 str r0[sp,#20]
24:e59d0014 ldr r0,[sp,#20]
28:e59d1004 ldr r1[sp,#4]
2c:ebfffffe bl 0
30:e58d0010 str r0[sp,#16]
34:e59d2014 ldr r2[sp,#20]
38:e59d3010 ldr r3[sp,#16]
3c:e0823003添加r3、r2、r3
40:e1a00003 mov r0,r3
44:e28dd01c添加sp,sp,#28
48:e49de004 pop{lr};(ldr lr,[sp],#4)
4c:E12FF1E bx lr
不过,优化节省了很多…我认为单靠源代码在这里不会有太多用处。如果你能把反汇编的二进制代码和它一起发布,那就更好了。本质上,您应该尝试扫描生成的代码以查找您所做的分配。然而,如果你运气不好,根本就不会有任何存储位置,因为编译器可以自由地将局部变量保存在寄存器中,而无需将它们存储到主存中。但是你至少可以尝试一下。哦,这一切都适用于二进制文件没有用调试符号构建的情况,否则工作应该会容易得多。“我需要找到堆栈帧中的偏移量”——这就是
00000000 <myfun>:
0: e52de004 push {lr} ; (str lr, [sp, #-4]!)
4: e24dd01c sub sp, sp, #28
8: e58d000c str r0, [sp, #12]
c: e58d1008 str r1, [sp, #8]
10: e58d2004 str r2, [sp, #4]
14: e59d000c ldr r0, [sp, #12]
18: e59d1008 ldr r1, [sp, #8]
1c: ebfffffe bl 0 <one>
20: e58d0014 str r0, [sp, #20]
24: e59d0014 ldr r0, [sp, #20]
28: e59d1004 ldr r1, [sp, #4]
2c: ebfffffe bl 0 <two>
30: e58d0010 str r0, [sp, #16]
34: e59d2014 ldr r2, [sp, #20]
38: e59d3010 ldr r3, [sp, #16]
3c: e0823003 add r3, r2, r3
40: e1a00003 mov r0, r3
44: e28dd01c add sp, sp, #28
48: e49de004 pop {lr} ; (ldr lr, [sp], #4)
4c: e12fff1e bx lr