7

对于家庭作业,我得到了一些 c 文件,并使用 arm-linux-gcc 编译它们(我们最终将针对 gumstix 板,但对于这些练习,我们一直在使用 qemu 和 ema)。

其中一个问题让我有点困惑——我们被告知:

使用 arm-linux-objdump 在可执行二进制文件中查找 main() 中声明的变量的位置。

但是,这些变量是本地的,因此在运行之前不应该有地址,对吗?

我在想也许我需要找到的是堆栈帧中的偏移量,实际上可以使用 objdump 找到它(我不知道如何)。

无论如何,对此事的任何见解将不胜感激,如有必要,我很乐意发布源代码。

4

2 回答 2

2
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

简短的回答是在编译时和运行时都“分配”了内存。在编译时,编译器在编译时确定堆栈帧的大小以及谁去哪里。运行时内存本身在堆栈上,这是一个动态的东西。堆栈帧在运行时从堆栈内存中获取,类似于 malloc() 和 free()。

了解调用约定会有所帮助,x 在 r0 中输入,y 在 r1 中,z 在 r2 中。那么 x 的家位于 fp-16,y 位于 fp-20,z 位于 fp-24。那么对 one() 的调用需要 x 和 y,因此它会从堆栈中提取它们(x 和 y)。one() 的结果进入 a,它保存在 fp-8 中,因此这是 a 的家。等等。

函数一实际上不在地址 0 处,这是对目标文件的反汇编,而不是链接的二进制文件。一旦一个对象与其余的对象和库链接,缺少的部分,如外部函数所在的位置,将由链接器修补,并且对 one() 和 two() 的调用将获得真实地址。(并且程序可能不会从地址 0 开始)。

我在这里作弊了一点,我知道如果没有在编译器上启用优化并且像这样一个相对简单的函数,真的没有任何堆栈帧的理由:

只需一点优化即可编译

arm-none-eabi-gcc -O1 -c fun.c -o fun.o
arm-none-eabi-objdump -D fun.o

堆栈帧消失了,局部变量保留在寄存器中。

00000000 : 0: e92d4038 推 {r3, r4, r5, lr} 4: e1a05002 mov r5, r2 8: ebffffffe bl 0 c: e1a04000 mov r4, r0 10: e1a01005 mov r1, r5 14: e080000 0 18: r0, r0, r4 1c: e8bd4038 pop {r3, r4, r5, lr} 20: e12fff1e bx lr

编译器决定做的是通过将它们保存在堆栈上来给自己更多的寄存器来使用。为什么它保存了 r3 是一个谜,但这是另一个话题......

根据调用约定输入函数 r0 = x, r1 = y 和 r2 = z,我们可以不理会 r0 和 r1(用 one(y,x) 再试一次,看看会发生什么),因为它们直接落入 one() 和不再使用。调用约定说 r0-r3 可以被函数销毁,因此我们需要保留 z 以供以后使用,因此我们将其保存在 r5 中。根据调用约定 one() 的结果是 r0,因为 two() 可以破坏 r0-r3 我们需要保存 a 以备后用,在调用 two() 之后我们也需要 r0 来调用两个,所以 r4现在持有一个。我们在调用 one 之前将 z 保存在 r5 中(在 r2 中移动到 r5),我们需要 one() 的结果作为 two() 的第一个参数,并且它已经存在,我们需要 z 作为第二个参数,所以我们将我们保存 z 的 r5 移动到 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-frame-pointer,它保存了一些指令

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

优化可以节省更多...

于 2013-03-03T18:20:21.210 回答
1

这将取决于程序以及他们想要变量位置的确切程度。问题是否需要它们存储在哪个代码段中?.const .bss 等?它需要特定的地址吗?无论哪种方式,一个好的开始都是使用 objdump -S 标志

objdump -S myprogram > dump.txt

这很好,因为它会打印出您的源代码和带有地址的程序集的混合。从这里开始搜索你的int main,这应该让你开始。

于 2013-03-03T01:34:15.743 回答