实验内容
参照第 1.3 节,将如下 C 语言代码汇编成“.s”文件,并分析“.s”文件中的汇编代码
的执行过程,其中重点关注 EBP/ESP 寄存器、EAX 寄存器、EIP 寄存器和函数调用堆栈空
间在汇编代码的执行过程是如何变化的。
int g(int x)
{
return x + 3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8) + 1;
}
使用如下命令汇编上述 C 语言代码(以下命令适用于实验楼 64 位 Linux 虚拟机环境):
gcc –S –o main.s main.c -m32
实验过程
(ps.本次实验是在ubutun虚拟机下进行)
1.在ubutun虚拟机的终端下用vi命令编辑main.c文件
2.使用gcc –S –o main.s main.c -m32命令汇编上述代码,在默认目录下得到main.s文件
3.用vi命令打开main.s并对其进行简化
4.分析代码的执行过程
主要参考博客:反汇编分析之函数调用_反汇编调用_、moddemod的博客-CSDN博客
从main入口开始:
首先将ebp压栈,将esp的值存到ebp中,此时esp和ebp指向同一个位置(call和addl行与分析内容无关,不作具体解释)
立即数8是f的参数,将其压入栈后,执行call指令:
首先把call指令的下一条指令addl $4, %esp的地址压入栈中,之后跳转到f的位置:
从pushl %ebp开始,先将调用者main函数的ebp压入栈中,同时将esp的值赋给ebp。然后开辟新的堆栈空间,pushl 8(%ebp)是寄存器基址变址寻址方式,也就是ebp+8所指向的内存空间,将其数值8压入栈中。这里为什么是加8?因为我们是32位系统,mian函数跳转到f的时候,在栈空间存放了main函数的下一条指令的地址,以及ebp的值,两个4字节即8字节就到了我们压入的立即数8的位置。
接着执行call g,同样,将下一条指令的地址压入栈中,跳转到g开始执行:
也是将ebp压栈后,更新esp以及ebp的值;同理movl 8(%ebp), %eax的意思就是将8放到eax寄存器中,addl $3, %eax就是8+3=11存放到eax寄存器中,popl %ebp我们将刚才压入的ebp直接弹栈,恢复ebp的值,同时esp-4指向了f函数call指令压入的下一条指令的地址,执行ret后,将该地址弹到eip中,即实现了跳转,现在回到了addl $4, %esp,这里将esp的指针移动,因为已经恢复eip的值了,到了leave这是一个宏指令,撤销堆栈的意思。因为在该堆栈空间上压入了8,所以需要撤销这块空间,leave等价于
movl %ebp, %esp
popl %ebp
这两条指令,现在esp指向了main函数中call指令的下一条指令addl $4, %esp的地址,执行ret后跳转到该位置,将esp的值加4,addl $1, %eax。因为刚才我们的值保存在eax寄存器中了,这里加1即12+1=13,依然保存在eax寄存器中,leave撤销堆栈,ret直接跳转到调用main函数的系统位置继续后续的处理。