大多数CPU上的程序实现使用栈来支持函数调用操作。栈被用来传递函数参数、存储返回信息(返回地址,调用者的ebp),临时保存寄存器原有值以备恢复以及用来存储局部数据。
一个栈桢由两个寄存器指定,栈底ebp,栈顶esp。
Intel CPU,所有函数必须遵守的寄存器用法统一惯例:
eax, edx, ecx
由调用者自己负责保存,ebx, esi, edi
由被调用者来保护。
相关汇编指令:push pop call ret iret
push压栈,pop从栈中弹出。
call:把返回地址入栈,并跳转到被调用函数开始处执行。(返回地址是程序中紧随调用指令call后面一天指令的地址)。
ret:(此时esp指向调用者栈桢的顶部,也就是存放返回地址的地方)弹出栈顶处的地址并跳转到该地址处。
这是通过一条汇编指令leave
实现的,leave
实际上是下面两天汇编:
mov %ebp, %esp # 恢复原esp的值(esp=ebp,esp此时指向被调用者栈桢的开始处)
pop %ebp # 恢复原ebp的值(esp=esp+4,它执行了调用者的返回地址处,ebp也指向了调用者的栈底)
举个例子:
#include
void swap(int *a, int *b) {
int c;
c = *a; *a = *b; *b = c;
}
int main() {
int a, b;
a = 16; b = 32;
swap(&a, &b);
printf("%d\n", (a-b));
return (a-b);
}
编译:
nadeMacBook-Pro:testc nazhou$ gcc -Wall -S -m32 -o exch.s exch.c
nadeMacBook-Pro:testc nazhou$ ls
a a.c a.s bar.c bar.o exch.c exch.s foo.asm foo.o hello hello.asm hello.o
nadeMacBook-Pro:testc nazhou$ clear
nadeMacBook-Pro:testc nazhou$ cat exch.s
汇编代码:
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 13
.globl _swap ## -- Begin function swap
.p2align 4, 0x90
_swap: ## @swap
.cfi_startproc
## BB#0:
pushl %ebp // 将main的栈底压入栈中
Lcfi0:
.cfi_def_cfa_offset 8
Lcfi1:
.cfi_offset %ebp, -8
movl %esp, %ebp // ebp=esp,就是刚压入的main的ebp,这就开始swap的栈了
Lcfi2:
.cfi_def_cfa_register %ebp
pushl %esi。 // 将esi入栈
subl $12, %esp //
Lcfi3:
.cfi_offset %esi, -12
movl 12(%ebp), %eax // eax = &b
movl 8(%ebp), %ecx // ecx = &a
movl 8(%ebp), %edx // edx = &a,edx相当于变量c
movl (%edx), %edx // edx = a
movl %edx, -8(%ebp) // 将a入栈
movl 12(%ebp), %edx // edx = &b
movl (%edx), %edx // edx = b
movl 8(%ebp), %esi // esi = &a
movl %edx, (%esi) // a = b
movl -8(%ebp), %edx // edx = a
movl 12(%ebp), %esi // esi = &b
movl %edx, (%esi) // b = a (原来的a,它在被改变前压入了栈中)
movl %eax, -12(%ebp) ## 4-byte Spill // &b入栈
movl %ecx, -16(%ebp) ## 4-byte Spill // &a入栈
addl $12, %esp // esp指向esi
popl %esi // esi出栈
popl %ebp // ebp又指向了main的栈底,esp指向了返回地址处
retl // 弹出栈顶处的地址,并跳转到该地址处
.cfi_endproc
## -- End function
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## BB#0:
pushl %ebp // ebp入栈,下一步ebp就要被改变了
Lcfi4:
.cfi_def_cfa_offset 8
Lcfi5:
.cfi_offset %ebp, -8
movl %esp, %ebp // ebp = esp,从现在开始是main的栈桢
Lcfi6:
.cfi_def_cfa_register %ebp
subl $40, %esp // esp = esp -40
calll L1$pb // call把返回地址入栈,并跳转到被调用函数处开始执行
L1$pb:
popl %eax // 虽然是call调用到函数,但没有开始一个新的栈桢,还是main的栈桢,eax存放的是这个函数后面的指令
leal -8(%ebp), %ecx // ecx指向ebp-8处(&a)
leal -12(%ebp), %edx // ebx指向ebp-12处 (&b)
movl $0, -4(%ebp) // 0入栈
movl $16, -8(%ebp) // 16入栈 a(ecx指向)
movl $32, -12(%ebp) // 32入栈 b(ebx指向)
movl %ecx, (%esp) // &a入栈
movl %edx, 4(%esp) // &b入栈
movl %eax, -16(%ebp) ## 4-byte Spill // 返回地址入栈
calll _swap // 返回地址入栈,并跳转到被调用函数开始处执行
movl -16(%ebp), %eax ## 4-byte Reload
leal L_.str-L1$pb(%eax), %ecx
movl -8(%ebp), %edx
subl -12(%ebp), %edx
movl %ecx, (%esp)
movl %edx, 4(%esp)
calll _printf
movl -8(%ebp), %ecx
subl -12(%ebp), %ecx
movl %eax, -20(%ebp) ## 4-byte Spill
movl %ecx, %eax
addl $40, %esp
popl %ebp
retl
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "%d\n"
.subsections_via_symbols