所以...我正在使用gcc -S -O2 -m32编译成汇编程序:
void h(int y){int x; x=y+1; f(y); f(2); }
Run Code Online (Sandbox Code Playgroud)
它给了我以下内容:
.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
Run Code Online (Sandbox Code Playgroud)
现在我知道pushl和movel是什么:它们将当前帧指针存储到堆栈中,然后将帧指针寄存器的值设置为堆栈指针的值.
谢谢!
要回答这些有问题的问题:
1) subl $24,%esp
手段 esp = esp - 24
GNU AS使用AT&T语法,这与英特尔语法相反.AT&T的目的地在右侧,英特尔的目的地在左侧.AT&T也明确了论证的大小.英特尔试图推断它或迫使你明确.
堆栈在内存中增长,esp 中和 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.
Run Code Online (Sandbox Code Playgroud)
没有内存到内存的指令格式.(严格地说,您可以使用movs或通过push mem,执行内存到内存操作pop mem,但不能在同一指令上执行两个内存操作数)
"立即"表示该值被编码到指令中.例如,要在ebx中的地址存储15:
movl $15,(%ebx)
15是"立即"值.
括号使它使用寄存器作为指向内存的指针.
3) movl 8(%ebp),%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,%esp和popl %ebp并保存几个码字节.它实际上做的是撤消对堆栈的更改并恢复调用者的ebp.被调用的函数必须保留ebp在x86 ABI中.
在进入函数时,编译器做了subl $ 24,%esp为局部变量腾出空间,有时临时存储它没有足够的寄存器来保存.
在你的脑海中"想象"堆栈框架的最好方法是将其视为一个坐在堆栈上的结构.虚构结构的第一个成员是最近"推"的值.所以当你推到一个堆栈时,想象一下在结构的开头插入一个新成员,而其他成员都没有移动.当您从堆栈"弹出"时,您将获得虚构结构的第一个成员的值,并且该结构的(第一)行将从存在中消失.
堆栈帧操作主要是移动堆栈指针,以便在我们称之为堆栈帧的虚构结构中产生更多或更少的空间.从堆栈指针中减去只需将多个虚构成员放在结构的开头一步.添加到堆栈指针会使第一个成员消失.
您发布的代码的结尾并不典型.那jmp通常是一个ret.编译器很聪明,并进行了"尾部调用优化",这意味着它只是清理它对堆栈所做的操作并跳转到f.当f(2)返回时,它实际上将直接返回给调用者(不还给你发布的代码)
| 归档时间: |
|
| 查看次数: |
15352 次 |
| 最近记录: |