了解递归IA32汇编调用 [英] Understanding a recursive IA32 assembly call

查看:215
本文介绍了了解递归IA32汇编调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图做一些练习来获得更舒适的IA32装配和上午平移组件code这个递归片段来理解C $ C $ç挣扎了一下。他们给了我们一个提示,在code的所有功能只给一个参数,但我对IA32栈的理解还是有点差。

I'm trying to do some practice to get more comfortable with IA32 assembly, and am struggling a bit on translating this recursive snippet of assembly code to understandable C code. They gave us a hint that all functions in the code give only one argument, but my understanding of the IA32 stack is still a bit poor.

.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

酒吧功能似乎很容易 - 它增加了10到参数并返回。在富功能是在那里我迷路。我明白subl $ 24,特别保留的空间24个字节,但是从那里我开始迷失自己。我们似乎递归调用富和递减参数(姑且称之为X)每次直到它到达1,然后由巴(X-1),但一切的结果乘以栏(X)的结果是怎么回事对我似乎并没有得到。

The bar function seems easy enough - it adds 10 to the argument and returns. The "foo" function is where I'm getting lost. I understand subl $24, esp reserves 24 bytes of space, but from there I start to lose myself. We seem to recursively call "foo" and decrement the argument (let's call it x)each time until it reaches 1, and then multiply the result of bar(x) by the result of bar(x-1) but everything else that's going on I don't seem to get.

具体而言,做这两个命令做什么?他们subl $ 24%ESP之后到来,我认为这是关键了解整个事情。
MOVL%EBX,-8(%EBP)
MOVL%ESI,-4(%EBP)

Specifically, what do these two commands do? They come right after subl $24, %esp and I think that's the key to understanding the whole thing. movl %ebx, -8(%ebp) movl %esi, -4(%ebp)

推荐答案

在86堆栈向下增长(向更小的地址)。在IA32寄存器是32位或4个字节,所以是指令指针(EIP)。堆栈指针寄存器(ESP)所指向的堆栈的顶部,也就是最后一个项目压入堆栈 - 因为堆栈增长向低地址,这是在栈上的有效数据的最低地址。推指令由在该地址4然后存储32位(4字节)递减的​​ESP。调用指令有4字节的隐含推(返回地址,这是紧跟呼叫的地址)。

In x86 the stack grows downwards (towards smaller addresses). In IA32 registers are 32-bits, or 4 bytes, and so is the Instruction Pointer (EIP). The stack pointer register (ESP) is pointing to the "top of stack", that is, the last item pushed on the stack - and because the stack grows towards lower addresses this is the lowest address with valid data on the stack. The push instruction decrements ESP by 4 then stores 32-bits (4 bytes) at that address. The call instruction has an implied push of 4 bytes (the return address, which is the address immediately following the call).

那么,什么是栈在函数的入口顶部?这将是由ESP指向的4个字节,它包含了返回地址。什么是在偏离ESP +4?这将是第二个压入堆栈,它与一个参数的功能的情况下,这将是该参数的最后一件事:

So what is at the top of stack at the entry of a function? That would be the 4 bytes pointed by ESP, and it contains the return address. What is at offset +4 from ESP? That would be the second to last thing pushed on the stack, which in the case of a function with one parameter it would be that parameter:

|                |
+----------------+
|  parameter 1   |     ESP + 4
+----------------+
| return address |     <===== ESP (top of stack)
+----------------+

为了放松堆栈(作为一个调试器会想这样做)编译器生成的的EBP链 - EBP点到呼叫者的EBP被保存在栈上的位置......这EBP依次指向调用者的调用者保存的EBP和依此类推,直到堆栈。这就是为什么你看到一个函数的开头:

In order to unwind the stack (as a debugger would want to do) the compiler builds an "EBP chain" - EBP points to a location on the stack where the caller's EBP is stored... that EBP in turn points to the saved EBP of the caller's caller, and so on up the stack. That is why at the beginning of a function you see:

pushl %ebp        # save caller's EBP on the stack
movl %esp, %ebp   # EBP now points to caller's EBP on the stack

某些功能不具备自动存储变量......在这种情况下,函数preamble做......但是,说你有6个变量4个字节,那么你需要的自动6X4 = 24字节栈......这是由从ESP减去24完成对存储,你再有空间6局部变量(本地-VAR-0 ...本地VAR-5):

Some functions don't have variables in automatic storage... in that case the function preamble is done... however, say you have 6 variables each 4 bytes, then you need 6x4 = 24 bytes of automatic storage on the stack... which is accomplished by subtracting 24 from ESP, you then have room for 6 local variables (local-var-0...local-var-5):

subl $24, %esp

该堆栈现在看起来是这样的:

The stack now looks like this:

|                |
+----------------+
|  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)是本地VAR-1的地址,和-4(EBP)是本地VAR-0的地址,所以这code堆栈上保存寄存器

As you can see, -8(ebp) is the address of local-var-1, and -4(ebp) is the address of local-var-0, so this code saves registers on the stack

movl %ebx, -8(%ebp)    # save %ebx in local-var-1
movl %esi, -4(%ebp)    # save %esi in local-var-0

和他们正在返回之前恢复:

and they are restored prior to returning:

.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%沿着)由根据IA32调用约定的被调用者保存。请参见表4,瓦格纳雾的调用约定文档的第6章注册用法。

General purpose registers %ebx and %esi (along with %edi and %ebp) are saved by the "callee" according to the IA32 calling conventions. See Table 4, Chapter 6 Register Usage of Agner Fog's Calling Conventions document.

这篇关于了解递归IA32汇编调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆