内存中的局部变量位置

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

其中一个问题让我感到困惑 - 我们被告知:

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

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

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

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


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,r2进入z。 那么x的家在fp-16,y在fp-20,而z在fp-24。 那么对one()的调用需要x和y,以便从堆栈(x和y)中获取它们。 one()的结果进入一个保存在fp-8中的结果,因此这是a的归属。 等等。

函数1不是真的在地址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 push {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 add r0,r0,r4 1c:e8bd4038 pop {r3,r4,r5,lr} 20:e12fff1e bx lr

编译器决定做的事情是通过将它们保存在堆栈上来为自己提供更多的寄存器。 为什么保存r3是一个谜,但那是另一个话题...

根据调用约定输入函数r0 = x,r1 = y和r2 = z,我们可以单独离开r0和r1(再次尝试使用一个(y,x)并查看会发生什么),因为它们直接放入一个永远不会再使用。 调用约定表示r0-r3可以被函数销毁,所以我们需要保留z以备后用,所以我们把它保存在r5中。 每个调用约定中one()的结果是r0,因为two()可以销毁r0-r3,我们需要稍后保存a,在调用two()之后,我们还需要r0来调用两个,所以r4现在拥有一个。 我们在r5中保存了z(在r2中移动到了r5),我们需要将one()的结果作为two()的第一个参数,它已经存在,我们需要z作为第二个参数,所以我们移动r5,我们将z保存到r1,然后调用两个()。 每个调用约定两个()的结果。 由于基本数学属性的b + a = a + b,返回之前的最终加法是r0 + r4,它是b + a,并且结果出现在r0中,这是用于根据约定从函数返回某些内容的寄存器。 清理堆栈并恢复修改的寄存器,完成。

由于myfun()使用bl调用其他函数,bl修改链接寄存器(r14),为了能够从myfun()返回,我们需要链接寄存器中的值从入口函数保留到函数最终的回报(bx lr),所以lr被压入堆栈。 约定规定我们可以在函数中销毁r0-r3,但不能在其他寄存器中销毁r4-r3,因为我们使用它们。 为什么从调用约定的角度来看,为什么将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,我需要考虑堆栈指针vs帧指针的东西了一下。 我认为有一个命令行参数来告诉编译器不要使用帧指针。 -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

优化可以节省很多,但...


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

objdump -S myprogram > dump.txt

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

链接地址: http://www.djcxy.com/p/12135.html

上一篇: Local variable location in memory

下一篇: Facebook full permission AccessToken alternative to Offline access token