我正在尝试做一些练习以便更好地使用IA32程序集,并且我正在努力将这个递归的汇编代码片段转换为可理解的C代码。他们给了我们一个提示,即代码中的所有函数只给出一个参数,但我对IA32堆栈的理解仍然有点差。
.globl bar
.type bar, @function
bar:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $10, %eax
popl %ebp
ret
.globl foo
.type foo, @function
foo:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl %ebx, -8(%ebp)
movl %esi, -4(%ebp)
movl 8(%ebp), %ebx
movl $1, %eax
cmpl $1, %ebx
jle .L5
movl %ebx, (%esp)
call bar
movl %eax, %esi
subl $1, %ebx
movl %ebx, (%esp)
call foo
imull %esi, %eax
.L5:
movl -8(%ebp), %ebx
movl -4(%ebp), %esi
movl %ebp, %esp
popl %ebp
ret
bar函数似乎很容易 - 它为参数添加了10并返回。 “foo”功能是我迷路的地方。我理解subl $ 24,esp保留了24个字节的空间,但是从那里我开始迷失自己。我们似乎递归地调用“foo”并且每次递减参数(让它称之为x)直到它达到1,然后将bar(x)的结果乘以bar(x-1)的结果,但是其他所有的东西都会去在我似乎没有。
具体来说,这两个命令有什么作用?他们在$ 24,%esp之后就来了,我认为这是理解整个事情的关键。 movl%ebx,-8(%ebp) movl%esi,-4(%ebp)
答案 0 :(得分:2)
在x86中,堆栈向下(朝向较小的地址)。在IA32中,寄存器是32位或4字节,指令指针(EIP)也是如此。堆栈指针寄存器(ESP)指向堆栈顶部"即堆栈上推送的最后一项 - 并且由于堆栈朝向较低地址增长,这是具有有效数据的最低地址堆栈。按下指令将ESP递减4,然后在该地址处存储32位(4字节)。调用指令隐含4个字节的推送(返回地址,紧接着调用之后的地址)。
那么在函数入口处的堆栈顶部是什么?这将是ESP指向的4个字节,它包含返回地址。什么是ESP的偏移量+4?这将是推到堆栈的倒数第二个东西,在具有一个参数的函数的情况下,它将是该参数:
| |
+----------------+
| parameter 1 | ESP + 4
+----------------+
| return address | <===== ESP (top of stack)
+----------------+
为了展开堆栈(如调试器所希望的那样),编译器构建"EBP chain" - EBP指向堆栈中存储调用者的EBP的位置... EBP反过来指向呼叫者呼叫者的已保存EBP,依此类推。这就是为什么在功能开始时你会看到:
pushl %ebp # save caller's EBP on the stack
movl %esp, %ebp # EBP now points to caller's EBP on the stack
某些功能在自动存储中没有变量......在这种情况下功能前导码已经完成...但是,假设每4个字节有6个变量,则需要6x4 = 24个字节的自动存储在堆栈上...这是通过从ESP中减去24来实现的,然后你有6个局部变量的空间(local-var-0 ... local-var-5):
subl $24, %esp
堆栈现在看起来像这样:
| |
+----------------+
| parameter 1 | 8(ebp)
+----------------+
| return address | 4(ebp)
+----------------+
| caller's EBP | 0(ebp)
+----------------+
| local-var-0 | -4(ebp)
+----------------+
| local-var-1 | -8(ebp)
+----------------+
| local-var-2 | -12(ebp)
+----------------+
| local-var-3 | -16(ebp)
+----------------+
| local-var-4 | -20(ebp)
+----------------+
| local-var-5 | -24(ebp)
+----------------+
如您所见,-8(ebp)是local-var-1的地址,-4(ebp)是local-var-0的地址,因此该代码将寄存器保存在堆栈中
movl %ebx, -8(%ebp) # save %ebx in local-var-1
movl %esi, -4(%ebp) # save %esi in local-var-0
并在返回前恢复:
.L5:
movl -8(%ebp), %ebx # restore %ebx from local-var-1
movl -4(%ebp), %esi # restore %esi from local-var-0
通用寄存器%ebx和%esi(以及%edi和%ebp)由&#34; callee&#34;保存。根据IA32呼叫惯例。请参阅Table 4, Chapter 6 Register Usage of Agner Fog's Calling Conventions文档。