计算机是如何工作的
反汇编一个简单的C程序
首先,我们新建一个C程序main.c
在vim编辑器中键入
int g(int x)
{
return x + 3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8) + 1;
}
实验部分(64位Linux虚拟机环境下适用,32位Linux环境可能会稍有不同) 使用:
$ gcc –S –o main.s main.c -m32
打开main.s,并手动删除.打头的字符串后得到:
下面,我试着用自己的语言解释一下这23行汇编语言:
/*
** 个人理解,有错望不吝指出
*/
g:
pushl %ebp /*同样,先把f函数的栈底地址存到栈顶*/
movl %esp, %ebp /*再把基指针指向栈顶,也就是g函数的栈底*/
movl 8(%ebp), %eax /*相对寻址,ebp的内容指向的地址倒退2个单元格,指向的是
esp中的数据8,将其存入累加器eax中*/
addl $3, %eax /*教累加器运算8+3,存在累加器eax中*/
ret /*返回到f*/
f:
pushl %ebp /*执行f这个函数之前,同样需要把基指针(main这一段的
栈底)存入栈顶*/
movl %esp, %ebp /*然后将新的基指针指向f这个函数指令的栈底*/
pushl 8(%ebp) /*这里汇编与书中不同,相当于:
subl $4, %esp 堆栈减4,相当于加一个单元格
movl 8(%ebp), %eax 相对寻址,ebp指向的esp的位置在esp上退
两个单元格(2*4)指向的数据(刚刚main那里push进去的8)
存到累加器eax中
movl %eax, (%esp) 把刚刚存到累加器的数据(8)打入栈顶*/
call g /*又是一个中断,去g*/
addl $4, %esp /*中断返回,esp指针退回一个单元格*/
leave /*把上一条指令的栈底地址存到堆栈esp中,并弹出当前ebp的“栈顶”*/
ret /*返回到main*/
main:
pushl %ebp /*ebp,基指针寄存器,指向的是当前指令的栈底(可以
理解为每条指令占据堆栈的一段),在运行的一开始,
我们需要把这段指令的开头位置存到堆栈寄存器esp中
以便后面被别的指令中断之后还能找回来*/
movl %esp, %ebp /*esp,堆栈寄存器,地址指向栈顶,这里把栈顶
的位置存到ebp,是因为当执行main函数时,相当
于一次中断,之前的指令被打断,等会儿还需要
回来执行它,所以把当前的栈顶地址存到ebp中*/
pushl $8 /*现在我们执行这条新的指令,它把8这个数据打入栈顶,
一会儿就盘它*/
call f /*这又是一次中断,main的执行被打断,去f*/
addl $4, %esp /*esp再退一格*/
addl $1, %eax /*eax中的数据是8+3,此时再+1,结果12.eax寄存器是
默认存储函数返回值的寄存器*/
leave /*撤销main函数*/
ret /*回到初始状态*/
(PS:太难画了,求老师推荐一个好用的画图软件)
问题: leave这条指令不是很理解。书中说相当于:
movl %ebp, %esp
popl %ebp
但每次中断去执行新指令的时候,ebp的值是存到堆栈中了的,为什么此时还要movl ebp的值到堆栈中。我认为ebp只是把每次中断调用的初始位置存到堆栈的暂存器,不知道这样理解对不对?