g:
1 pushl %ebp //寄存器esp由地址1976开始-4,再将寄存器ebp的内容放入esp;
2 movl %esp, %ebp ///将esp的值放入ebp中,即让ebp=1972;
3 movl 8(%ebp), %eax //将ebp+8=1980中的内容放入寄存器eax中,即eax=9;
4 addl $5, %eax //将当前寄存器eax中的内容+5,即eax=14;
5 popl %ebp //将当前esp中的内容存到寄存器ebp中,即ebp=1984,esp+4=1976;
6 ret //相当于popl %eip,将当前esp的内容存到eip,即eip=13,跳转到地址13;
f:
7 pushl %ebp //寄存器esp由地址1980开始-4,再将寄存器ebp的内容放入esp;
8 movl %esp, %ebp //将esp的值放入ebp中,即让ebp=1984;
9 subl $4,%esp // esp再-4,即esp=1980;
10 movl 8(%ebp),%eax //将ebp+8=1992中的内容放入寄存器eax中,即eax=9;
11 movl %eax,(%esp) //将eax中的内容9存入当前esp中;
12 call g //可以理解为pushl %eip; movl $g, %eip,即将当前寄存器eip的值13入栈,将g函数所在的地址1存入eip寄存器中,然后跳转到g函数;
13 leave //相当于movl %ebp,%esp;popl %ebp;将ebp的内容存到esp中,即esp=1984,再将当前esp中的内容存到寄存器ebp中,即ebp=1996,esp+4=1988;
14 ret //将当前esp的内容存到eip,即eip=20,跳转到地址20;
main:
15 pushl %ebp //寄存器esp由栈底2000开始-4,再将寄存器ebp的内容放入esp;
16 movl %esp, %ebp //将esp的值放入ebp中,即让ebp=1996;
17 subl $4,%esp //esp再-4,即esp=1992;
18 movl $9, (%esp) //将9存入esp中,即栈地址1992中存入9;
19 call f //可以理解为pushl %eip; movl $f, %eip,即将当前寄存器eip的值20入栈,将f函数所在的地址7存入eip寄存器中,然后跳转到f函数;
20 addl $3, %eax //将当前寄存器eax中的内容+3,即eax=17;
21 leave //将ebp的内容存到esp中,清空堆栈;
22 ret
又因为X86是满递减堆栈,所以堆栈的工作方式是先往低地址增长,再进行操作,首先从main函数开始分析,见函数注释;堆栈的变化见下表1-1:
实验总结:
由于汇编语言是最接近机器语言的,所以高级语言需要通过编译器编译成汇编语言,然后再由计算机翻译成机器语言开始运行操作的。而程序的执行都是通过一些寄存器与存储器进行交互操作完成的。上面的例子就是一个很好的说明
作者:叶涛
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000