SA*****210 胡*
一、实验目的
通过对初级简单程序的逐步分析,了解计算机的基本工作原理,通过分步执行预处理,编译,汇编,链接各步骤,观察各步中生成的文件,并分析.s代码在CPU中的执行过程。
二、实验内容
源代码E.c如代码段1所示,
gcc -E -o E.cpp E.c命令对.c文件进行预处理,生成.cpp文件,如代码段2所示,
gcc -x cpp-output -S -o E.s E.cpp命令对.cpp文件进行汇编,生成E.s汇编文件,如代码段3所示,
gcc -x assembler -c -c E.s -o E.o命令对.s文件进行处理,生成.o文件
gcc -o E.o命令对.o文件进行处理,生成最后的ELF
三、实验步骤
这是给出的源代码E.c
int g(int x)
{
return x+3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8)+1;
}
gcc -E -o E.cpp E.c命令后生的代码如下所示
# 1 "E.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "E.c"
int g(int x)
{
return x+3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8)+1;
}
gcc -x cpp-output -S -o E.s E.cpp命令生成的代码如下所示
每一步命令的作用已详细标示在注释中。
.file"E.c"
.text
.globl g
.typeg, @function
g:
pushl%ebp//将ebp2压栈
movl%esp, %ebp//和上步一起,创建函数g的栈帧,确定ebp3和esp3
movl8(%ebp), %eax//把刚压栈的eip和ebp前的参数,即x=8传到eax寄存器里
addl$3, %eax//eaxl里保存的值加3,即x=8+3=11
popl%ebp//将刚压入栈中的ebp2值弹出到基址寄存器中,即销毁函数g的栈帧
ret//将刚压栈的eip2弹出到eip寄存器中,即继续跳转前的下一条指令
.sizeg, .-g
.globl f
.typef, @function
f:
pushl%ebp//ebp1压栈
movl%esp, %ebp//和上步一起,创建函数f的栈帧,确定ebp2和esp2
subl$4, %esp//esp2下移4位,即在栈顶产生空余的4B空间
movl8(%ebp), %eax//将x的值8存入eax寄存器
movl%eax, (%esp)//将eax中的值x=8存进栈顶空余的4B空间中
callg//将eip2指针压栈,并将函数g的指令地址装入eip寄存器
leave//销毁f函数栈帧中的所有内容,即销毁保存8的空间,并弹出ebp1
ret//弹出eip1返回eip寄存器
.sizef, .-f.globl main
.typemain, @functionmain:
leal4(%esp), %ecx//将最初的esp0加4后保存在ecx寄存器中
andl$-16, %esp//将esp中的内容与-16按位与
pushl-4(%ecx)//将esp0的值压栈
pushl%ebp//将ebp0的值压栈
movl%esp, %ebp//和上步一起,建立main函数的栈帧,确定ebp1和esp1
pushl%ecx//ecx寄存器中的值压栈
subl$4, %esp//esp指针下移4位,栈顶空出4B
movl$8, (%esp)//将立即数8存入空闲的4B位置中
callf//调用函数f,即将现在的eip1压栈,并进入f函数区的代码
addl$1, %eax//eax寄存器中的值加1
addl$4, %esp//esp指针上移4位,即撤销栈顶的4B
popl%ecx//将原先压栈的ecx寄存器中的值弹回ecx寄存器
popl%ebp//将原先保存的ebp0弹回机制寄存器
leal-4(%ecx), %esp//esp回到原先esp0的位置
ret//将之前压栈的eip0弹出到eip寄存器,一切回到原点
.sizemain, .-main
.ident"GCC: (GNU) 4.2.4 (Ubuntu 4.2.4-1ubuntu4)"
.section.note.GNU-stack,"",@progbits
用框图表示程序执行期间,栈区和寄存器的变化如下所示:
首先是main函数部分
然后是f函数部分
g函数部分
调用结束,进入返回部分
四、实验总结
通过以上实验可以得出,最简单的计算机原理图如下所示
由于本实验只是最基本的单任务程序,所以我在这里暂且只分析单任务模型的运行情况:
CPU中有若干寄存器,如eip,esp,ebp,eax,ebx,ecx等。
其中eip保存下一条命令所在的地址,esp保存当前程序栈帧的栈顶地址,ebp保存程序栈帧的基地址。eip指向保存代码段的cs部分,esp和ebp指向保存数据的ss部分,而eax,ebx,ecx等通用寄存器用来保存一些临时变量。
可以看出,计算机为每一个函数均分配一个栈帧用来执行各函数。这表现在汇编代码中,每一个函数的代码均被开头的pushl %ebp;movl %esp,%ebp;和结尾的popl %ebp;ret包裹。这几行代码起到的作用是开始的保存初始基址指针,并让当前的基址指针指向栈顶,即开始一个新的栈帧。后两行代码的意思是恢复原先的基址指针和eip,即销毁之前最后开辟的栈帧,恢复上一个栈帧的环境。
由此可见,call func命令将当前eip压栈,ret将之前压栈的eip弹出,leave销毁当前栈帧。通过循环调用,计算机不断的保护现场,创建新栈帧,直到最里层的函数执行完毕后,一层层返回,不断销毁新创建的栈帧,直到最后回复到初始状态。并将保存在通用寄存器中的运算结果返回,完成程序功能。