[Lab1]计算机是怎样工作的

来源:互联网 发布:mac发送文件给iphone 编辑:程序博客网 时间:2024/05/10 08:12
SA12***266 王星


实验内容:


使用example的c代码分别生成cpp,s,o 和ELF 可执行文件,并分析汇编代码在CPU上的执行过程

example代码见图1:


图1


gcc编译过程可以划分为以下四个阶段:

1.预处理阶段,输出文件为:*.cpp(c preprocess)

2.编译成汇编代码输出文件为:*.s

3.汇编成目标代码输出文件为:*.o

4.链接成可执行文件


下面介绍使用gcc逐步进行以上四个步骤的过程:


1.预处理阶段


通过命令:gcc -E -o example.cpp example.c

结果见图2:


图2


2.编译成汇编代码


可以直接将c源码编译成汇编代码,命令如下:
gcc -S -o example.s example.c
同时也可以将预处理之后的文件cpp编译成汇编代码,命令如下:
gcc -x cpp-output -S -o example.s example.cpp

结果见图3:


图3


3.汇编成目标代码,命令如下:

gcc -x assembler -c example.s -o example.o

结果见图4:



图4

4.链接生成可执行文件,命令如下:
gcc -o example example.o

结果见图5:



图5



以上便解释了gcc在将一个源文件编译成可执行文件时所经过的四个主要过程。
接下来便进入到example.s 中看看里面的汇编代码,以此对程序的执行过程有一个印象

example.s 全部代码见图6:



图6

现在可以直奔main标记处了,在第27行,可以看到main函数马上就要执行了。
然而突然出现了第28,29,30行的代码,而不是直接从pushl %ebp 开始的,这是一个问题
之后上网查阅之后才发现这是gcc 对代码优化产生的,具体分析过程如下:


leal 4(%esp),%ecx
andl $-16,%esp
pushl -4(%ecx)
在普通的情况下,进入一个函数时的初始堆栈如下:
|arg   n|
|arg ...|
|arg   0|

|ret address|<--esp


然而对于 main 函数,进入之后需要堆栈内存对齐,以此提高cpu访问内存的速度
因为gcc中默认的堆栈是16字节对其的,所以此处便会出现
andl $-16,%esp     实则是将esp低4位设为0,从而达到内存对其的目的
leal 4(%esp),%ecx  //将ret address的地址加4赋值给ecx
pushl -4(ecx)      //将ecx中的地址减4指向的值压栈,实则是将retaddress压栈
此时栈的结构如下
|arg0|
|ret address|
|填充|
|填充|
|ret address|<--esp

以上的疑问解决了,便可以继续下去(此处答案引用了一个未知名作者的文档以及瀚海foxman和xhacker的帖子)
接着是
pushl %ebp
mov %esp,%ebp
其作用时将old ebp压栈,并设置新的ebp,结果如下:


|arg0|
|ret address|
|填充|
|填充|
|ret address|

|old ebp|<--esp


pushl %ecx将ecx中的内容入栈,即ret address所在内存的地址-4
subl $4,%esp
movl $8,(%esp) 这两句等价于push $8
call f 调用f
至此,程序的栈结构如下:


|arg0|
|ret address|
|填充|
|填充|
|ret address|
|old ebp|
|8|
|ret address1|<--esp


接着进入函数f执行
代码如下:
pushl %ebp
movl %esp,%ebp
subl $4,%esp
movl 8(%ebp),%eax
call g


此时程序的栈结构如下:


|arg0|
|ret address|
|填充|
|填充|
|ret address|
|old ebp|
|8|
|ret address1|
|8|
|ret address2|<--esp


程序进入g
代码如下:
pushl %ebp
movl %esp,%ebp
movl 8(%ebp),%eax
addl $3,%eax
popl %ebp
ret
此处的代码执行流程比较明朗,故不多言
只需注意的一点是:返回值存放在eax中

接着函数返回到f中,继续执行,代码如下:
leave
ret


leave的作用包括两点
1.addl $4,%esp 释放f的栈空间
2.popl %ebp 为main恢复ebp


经过ret之后,程序回到了main中:
addl $1,%eax 执行加1的操作
addl $4,%esp 释放main使用的栈
popl %ecx 恢复ecx的值
popl %ebp 恢复ebp

leal -4(%ecx),%esp 恢复esp


此时程序的栈结构如下:
|arg0|
|ret address|<--esp
接着程序执行
ret
程序返回,大致流程就是这样的

实验总结


通过example的汇编代码的执行流程,我们可以知道在c语言中,发生函数调用时
系统栈所产生的变化
系统通过栈来保存函数现场,并以argument retaddress ebp的顺序入栈。