
最近,网易云课程开设了一个名为的课程. 我一直对操作系统和计算机的性质很感兴趣,所以我进去看了看. 第一课,老师请学生写一个关于第一课的博客作为作业. 作者对这种新颖的作品形式感到非常惊讶. 好吧,作为一项任务,让我们完成它,只需消化一下即可. 本文将根据要求将一段C语言代码编译为汇编,并进行分析和自己的思考.
首先,列出将涉及的一些CPU寄存器和汇编的基本知识:
准备一段C代码:
int g(int x)
{
return x+5;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(10)+1;
}
使用实验性建筑环境

使用以下命令编译以上c代码
gcc -S -o main.s main.c -m32
删除不重要的部分后,您将得到:


汇编代码的结果是:
g: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax addl $5, %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 $10, (%esp) call f addl $1, %eax leave ret
具体的分步分析保存在这里,老师已详细解释了. 这里的重点是思考和总结.
首先,我们看到3个C函数相应地生成了3个汇编代码部分,这些部分以函数名作为标签分开
int g(int x) -> g: int f(int x) -> f: int main(void) -> main:
我们知道程序是从main函数执行的,因此当程序被加载并运行时,以上的汇编代码将被加载到内存的某个区域中. 此外,CPU中的许多寄存器将被初始化. 当然,最重要的一个是eip,因为eip指的是将执行下一条命令的内存地址,因此此时的eip应该指向主标签下的pushl%ebp:
main: eip -> pushl %ebp
程序开始执行...
让我们一起观看,首先看看这两个:
pushl %ebp movl %esp, %ebp

再次查看整个代码,您是否发现不仅这两个指令是主函数,而且函数f和g的开头也是如此. 对其进行分析,不难发现这两条指令涉及将当前堆栈基地址压入堆栈并将基地址重新定位到堆栈顶部. 这实际上意味着保存当前的基地址并重新启动新的堆栈. 由于函数可以调用函数,因此此处的当前基址实际上是前一个函数的堆栈基址. 例如c语言对应汇编语句,f函数中的两条指令实际上保存了main函数的堆栈基地址.
接下来,分析两个句子:
subl $4, %esp movl $10, (%esp)
与C代码相比,不难发现这是将参数压入堆栈,并且立即数保存到堆栈的顶部(esp指向的内存地址是堆栈的顶部). 堆栈). 在f函数中可以找到类似的语句:
subl $4, %esp movl 8(%ebp), %eax movl %eax, (%esp)
因此,我们可以得出结论,在调用该函数之前,我们需要将参数一个接一个地推入,并且根据作者的测试,堆叠的顺序是从右到左.
接下来调用该调用指令,跳转到f函数,我们知道该调用指令等效于以下伪代码:
pushl %eip+1 movl %eip f
也就是说,在将调用指令的下一条指令压入堆栈后,会将eip分配给目标函数的第一条指令的地址. 这很明显: 被调用函数结束时,您需要返回到当前函数以继续执行,因此必须保存下一条指令,否则返回时将找不到它.
使用f函数时,首先需要保存main函数的堆栈基地址,然后需要调用g函数,因此需要高级堆栈参数:

subl $4, %esp movl 8(%ebp), %eax movl %eax, (%esp)
在这里,我们要关注f函数如何获取主函数传递的参数,
movl 8(%ebp), %eax
为什么从8(%ebp)获得参数?我们知道8(%ebp)意味着8个字节基于ebp追溯到堆栈的底部. 为什么是8个字节?
回想一下,在主函数中c语言对应汇编语句,将参数压入堆栈后,完成了两件事:
由于调用f指令的功能,调用f的下一条指令的地址被压入堆栈. 当一个字节的占用率进入f函数后,主函数的堆栈基地址立即被压入堆栈,而ebp则向栈顶倾斜. esp占据堆栈的顶部,
然后,上一个函数的第一个整数参数的值可以找到8(%ebp).
一张图片告诉你发生了什么事

我已经看到了输入一个函数并调用一个函数的过程,然后看看该函数如何退出. 观察main和f,不难发现以下命令用于退出函数

leave ret
leave命令等效于以下命令:
movl %ebp, %esp popl %ebp
然后ret是等效的,恢复指令指向:
popl %eip
为什么g函数不离开?由于g函数没有任何变量声明,并且函数调用堆栈始终为空,因此编译器会优化指令
最后,通过此示例,总结函数调用的过程:
输入功能:
当前堆栈基地址被压入堆栈(当前堆栈基地址实际上是上一个函数的堆栈基地址)
调用其他功能:
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-163245-1.html
吗哦