声明:原创作品转载请注明出处
参考文档:《Linux内核分析》MOOC课程 http://mooc.study.163.com/course/USTC-1000029000
本文将以一个C代码片段来刨析x86函数调用栈变化过程,进而理解高级语言是如何在计算机上运行的。
C代码片段如下:
int g(int x)
{
return x+30;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(28) + 100;
}
众所周知,计算机不能直接执行高级语言,高级语言必须通过编译器翻译成汇编代码,再通过汇编器翻译成二进制代码,最后链接为可执行程序,方可在计算机上执行,目前使用最多的编译器是gcc,故使用gcc -S -o main.s main.c -m32命令,将C代码翻译成相应的汇编代码。
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $30, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
ret
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $28, (%esp)
call f
addl $100, %eax
leave
ret
好了,准备工作做完了,下面就让我们从熟悉的main()开始一步步分析函数调用栈的变化吧?
我们假设调用main()之前,函数调用栈如下:
当执行下面语句后,
main:
pushl %ebp
函数调用栈如下:
当执行下面语句后,
main:
....
movl %esp, %ebp
函数调用栈如下:
当执行下面语句后,
main:
....
subl $4, %esp
函数调用栈如下:
当执行下面语句后,
main:
....
movl $28, (%esp)
函数调用栈如下:
由于call f相当于pushl %eip,movl f %eip,所以当执行下面语句后,
main:
.....
call f
函数调用栈如下:
当执行下面语句后,
f:
pushl %ebp
函数调用栈如下:
当执行下面语句后,
f:
.....
pushl %esp, %ebp
函数调用栈如下:
当执行下面语句后,
f:
.....
subl $4, %esp
函数调用栈如下:
当执行下面语句后,
f:
.....
movl 8(%ebp), %eax
函数调用栈如下:
当执行下面语句后,
f:
.....
movl %eax, (%esp)
函数调用栈如下:
当执行下面语句后,
f:
.....
call g
函数调用栈如下:
当执行下面语句后,
g:
pushl %ebp
函数调用栈如下:
当执行下面语句后,
g:
....
movl %esp, %ebp
函数调用栈如下:
当执行下面语句后,
g:
....
movl 8(%ebp), %eax
函数调用栈如下:
当执行下面语句后,
g:
....
addl $30, %eax
函数调用栈如下:
当执行下面语句后,
g:
....
popl %ebp
函数调用栈如下:
由于ret语句相当于popl %eip,所以当执行下面语句后,
g:
....
ret
函数调用栈如下:
由于leave语句相当于movl %ebp %esp,popl %ebp,所以当执行下面语句后,
f:
....
leave
函数调用栈如下:
由于ret语句相当于popl %eip,所以当执行下面语句后,
f:
....
ret
函数调用栈如下:
当执行下面语句后,
main:
....
addl $100, %eax
函数调用栈如下:
由于leave语句相当于movl %ebp %esp,popl %ebp,所以当执行下面语句后,
main:
....
leave
函数调用栈如下:
main()中ret语句将eip返回至操作系统,至此,函数调用栈变化过程分析完成!