2010-03-17 121 views
2

所以...我编译成汇编程序,用gcc -S -02 -m32:subl在这里做什么?

void h(int y){int x; x=y+1; f(y); f(2); } 

,它给我下面的:

.file "sample.c" 
.text 
.p2align 4,,15 
.globl h 
.type h, @function 
h: 
pushl %ebp 
movl %esp, %ebp 
subl $24, %esp 
movl 8(%ebp), %eax 
movl %eax, (%esp) 
call f 
movl $2, 8(%ebp) 
leave 
jmp f 
.size h, .-h 
.ident "GCC: (GNU) 4.4.3 20100127 (Red Hat 4.4.3-4)" 
.section .note.GNU-stack,"",@progbits 

现在我知道什么pushl和MOVEL :它们将当前帧指针存储到堆栈中,然后将帧指针寄存器的值设置为堆栈指针的值。

  1. 但我不知道是什么subl $ 24,%esp是。我知道它将堆栈指针向下移动了24个字节。正确?
  2. 这是怎么回事?
  3. 为什么movl 8(%ebp),%eax使用8?它是8字节吗?这是为了适应返回值+参数到h?或者我完全离开这里。所以这意味着从堆栈指针回看8个字节?
  4. 什么是movl $ 2,8(%ebp)做什么?它将Contant 2复制到帧指针之前的8个字节位置。当我们调用f时帧指针是否改变?如果是 - 那么8(%ebp)指向f的参数位置。
  5. 什么是假吗?它如何“移除”堆栈框架?我的意思是你不能删除一块内存。在文档中,它说mov(esp,ebp),pop ebp

谢谢!

+0

有趣的是,下面的答案被标记为接受,尽管它实际上没有给出问题1的解释。[Here]( http://stackoverflow.com/questions/1061818/stack-allocation-padding-and-alignment)是另一个问题/答案,给出了解释1。 – andreee 2015-12-15 20:24:00

回答

4

编译器为堆栈预留本地空间以及其他可能需要的内容。我不确定它为什么保留了24个字节(它似乎并不需要或不使用它)。

当调用函数f(),而不是使用推送指令把参数堆栈上,它使用简单movl到最后位置时,它保留:

movl 8(%ebp), %eax ; get the value of `y` passed in to `h()` 
movl %eax, (%esp)  ; put that value on the stack for call to `f()` 

一个更有趣的(​​在我看来)这里发生的事情是编译器是如何处理呼叫到f(2)

movl $2, 8(%ebp)  ; store 2 in the `y` argument passed to `h()` 
         ;  since `h()` won't be using `y` anymore 
leave     ; get rid of the stackframe for `h()` 
jmp f     ; jump to `f()` instead of calling it - it'll return 
         ;  directly to whatever called `h()` 

要回答你的问题,“我mmed的方式?“ - 指令引用用来指示该值在指令操作码中编码,而不是像寄存器或内存位置那样到达其他地方。

+0

它为什么减去?看到我的问题更新... – drozzy 2010-03-17 19:33:52

+2

从'esp'寄存器中减去本质上'保留'堆栈空间。就好像你已经将24个字节的东西(6个32位值)推入堆栈 - 不同之处在于你不能依赖堆栈中那些“推送”值 - 你知道的只是您可以使用该内存而不必担心随后的推送会覆盖它。 – 2010-03-17 19:38:26

+0

因为堆栈在内存中向下“增长”而减少。 'immed'是一个直接价值,就像你引用的24美元一样。 – mocj 2010-03-17 19:39:25

4

为了回答那些编号的问题:

1)subl $24,%esp

意味着esp = esp - 24

GNU AS使用AT & T语法,这是英特尔的语法的相反。 AT & T在右侧有目的地,英特尔在左侧有目的地。另外AT & T明确表示参数的大小。英特尔试图推断它或迫使你明确。

该堆栈在内存中增长下来,内存处于处,其中esp是堆栈内容,低于esp的地址是未使用的堆栈空间。 esp指向推入堆栈的最后一个东西。

2) x86指令编码大多允许以下:

movl rm,r ' move value from register or memory to a register 
movl r,rm ' move a value from a register to a register or memory 
movl imm,rm ' Move immediate value. 

没有存储器到存储器的指令格式。 (严格说来,你可以做内存到内存的操作与movspush mempop mem,但也采取相同的指令两个内存操作数)

“即时”是指值编码权到指令。例如,为了在地址存储15 EBX:

movl $15,(%ebx)

15是 “立即” 值。

括号使它使用寄存器作为指向内存的指针。

3)movl 8(%ebp),%eax

手段,

  • 采取EBP
  • 的值添加8到它(不修改EBP虽然),
  • 使用它作为一个地址(括号),
  • 从该地址读取32位值,
  • 并将值存储在eax中

esp是堆栈指针。 在32位模式下,堆栈上的每个推入和弹出消息都是4个字节宽。通常,大多数变量总是占用4个字节。所以你可以说8(%ebp)的意思是从栈顶开始,给我的值2(4 x 2 = 8)int到堆栈中。

通常,32位代码使用ebp指向函数中局部变量的开始。在16位x86代码中,没有办法将堆栈指针用作指针(很难相信,对吗?)。所以人们所做的是将sp复制到bp并使用bp作为本地帧指针。当32位模式出现时(80386),这变得完全没有必要,它确实有办法直接使用堆栈指针。不幸的是,ebp使得调试变得更容易,所以我们最终继续在32位代码中使用ebp(如果使用ebp,那么进行堆栈转储非常容易)。

谢天谢地,amd64给了我们一个新的ABI,它不使用ebp作为帧指针,64位代码通常使用esp来访问局部变量,ebp可用来存放变量。

4)解释上述

5)leave是一个古老的指令,仅仅确实movl %ebp,%esppopl %ebp和保存几个码字节。它实际上做的是撤消对堆栈的更改并恢复调用者的ebp。被调用函数必须在x86 ABI中保存ebp

在进入该函数时,编译器做了subl $ 24,%esp来为本地变量腾出空间,有时候它没有足够的寄存器来存放临时存储空间。

想象一下堆栈框架在你脑海中的最好方法就是将它看作一个坐在堆栈上的结构。想象结构的第一个成员是最近“推动”的价值观。所以当你推入堆栈时,想象一下在结构的开始处插入一个新成员,而其他成员没有移动。当你从堆栈中“弹出”时,你会得到虚构结构的第一个成员的值,并且该结构的第一行会从存在中消失。

堆栈帧操作大多只是移动堆栈指针,以使我们称之为堆栈帧的虚构结构或多或少。从堆栈指针中减去只是在结构的起始处一步放入多个虚构成员。添加到堆栈指针使得第一个这么多的成员消失。

您发布的代码的结尾并不典型。那jmp通常是ret。编译器很聪明,做了“尾部调用优化”,这意味着它只是清理它对堆栈做了什么并跳转到f。当f(2)返回时,它实际上会直接返回给调用者(不会返回到您发布的代码)