实验内容:
请使用Example的c代码分别生成.cpp,.s,.o和ELF可执行文件,并加载运行,分析.s汇编代码在CPU上的执行过程
实验报告要求:
通过实验解释单任务计算机是怎样工作的,并在此基础上讨论分析多任务计算机是怎样工作的。
example.c代码如下:
int g(int x)
{
return x+3;
}
intf(intx)
{
return g(x);
}
intmain(void)
{
return f(8)+1;
}
一、代码编译过程
编译,编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序。
C源程序-->预编译处理(.c)-->编译、优化程序(.s、.asm)-->汇编程序(.obj、.o、.a、.ko)-->链接程序(.exe、.elf、.axf等)
流程如图所示:
预处理:使用 -E 参数
输出文件的后缀为“.cpp”
gcc -E -o example.cpp example.c
编译成汇编代码:
预处理文件 → 汇编代码
1. 使用-x参数说明根据指定的步骤进行工作,cpp-output指明
从预处理得到的文件开始编译
2. 使用-S说明生成汇编代码后停止工作
gcc -x cpp-output -S -o example.s example.cpp
也可以直接编译到汇编代码
gcc -S example.c
编译成目标代码
汇编代码 → 目标代码
gcc -x assembler -c example.s
直接编译成目标代码
gcc -c example.c
使用汇编器生成目标代码
as -o example.o example.s
编译成执行代码 即elf文件
目标代码 → 执行代码
gcc -o exampleexample.o
直接生成执行代码
gcc -o exampleexample.c
查看汇编代码:
涉及的几个寄存器有ebp,esp,eip,其中ebp和esp分别指向当前栈的栈底和栈顶,eip是指令寄存器,存储着CPU要执行的下条指令的地址。
从main函数开始分析:
pushl %ebp
movl %esp, %ebp
显示ebp入栈(保存之前的ebp),然后使ebp指向与esp相同,此时栈情况如下:
调用函数之前,要将参数(本例是8)压栈,
subl $4, %esp
movl $8, (%esp)
这两条指令完成参数入栈的作用,此时,栈的情况如下图:
然后是call f;
call f 的过程包括将eip入栈,即保存返回地址,然后将函数f的地址给eip,然后开始执行函数f
执行函数f的过程,首先仍然是保存旧栈,
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
这个过程是参数x(这里是8)入栈,执行完后
接着是call g,
然后开始执行g,同理,先保存ebp,
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $3, %eax
这两条指令进行计算,计算结果放入eax寄存器,内存不发生变化
popl %ebp
ret
完成计算后,出栈
此时,返回到f中,
leave
ret
其中,leave等价于movl %ebp , %esp;popl %ebp;
ret 相当于popl %eip
再次回到main中。
addl $1, %eax
该指令进行计算,结果仍然存在eax中。
leave
ret
函数执行完毕。
关于计算机单任务与多任务的运行:
单任务运行,CPU通过读取EIP寄存器的值得值下一条指令的内存地址,然后读取指令并执行。整个过程中,始终由EIP保存将要执行的下一条指令的地址。函数调用过程中,如上面分析,需要保存现场,执行完后要返回,主要是通过EBP,ESP等寄存器实现的。
多任务情况要复杂一些,依靠进程调度、中断机制等实现。
SA*****232 张轩