计算机是怎样工作的
本文是菜鸟借助于对C程序的执行过程,以及对反汇编代码的分析来描述计算机是如何工作的,本文不考虑中断,一些异常情况。如果本文分析中有错误,望老师,同学指正,菜鸟将不甚感激!
实验内容:
通过对example.c程序的分析,将example.c代码分别生成example.cpp、example.s、example.o以及可执行的ELF文件,并且加载运行,并分析example.s
汇编代码的执行过程。
实验目的:
通过本次实验解释分析,单任务计算机是怎样工作的,并在此基础上讨论多任务计算机是怎样工作的。
实验环境:
Ubuntu13.04,
Gcc, Gdb, oracle VM virtualBox
实验过程:
1、example.c程序的源代码如下:

2、 对example.c程序进行预处理,生成example.cpp程序,然后将example.cpp
程序编译成汇编程序example.s,然后将生成的example.s程序,编译成二进制example.o的目标程序,最后通过链接器生成example这个可执行文件。
(1)、编写makefile

(2)make之后生成的预编译文件,汇编文件,目标文件,可执行文件如下:

(3)、用cat example.s查看汇编代码不清楚,这里我使用gdb中的disas反汇编查看。
操作如下:gdb
example
disas main //查看main()函数对应的汇编程序代码
disas f //查看f()函数对应的汇编程序代码
disas g //查看g()函数对应的汇编程序代码
main()函数反汇编代码如下:

f()函数的反汇编代码如下:

g()函数的反汇编代码如下:

3、对整个程序的汇编代码进行分析:
(1)
、浅谈汇编程序中子函数调用相关的指令
每一个函数在内存空间中以栈的形式存储,对于堆栈而言有两个十分重要的指针,一个是堆栈的基址指针ebp,另一个是栈顶指针esp。
汇编程序中,调用子函数过程中所用的几条重要指令的介绍:
a. 入栈/出栈指令
Pushl p 等价于以下两条指令:
subl $4, %esp
movl p, (%esp)
Pop p 等价于以下两条指令:movl
(%esp), p
add $4, %esp
b. 转移控制指令
call 子函数的调用指令
例如:call f()
等价于执行,两个操作,先将call f()指令后面的下一条指令的地址入栈,然后再将f()函数的地址赋值给ip,然后开始执行子程序。
call
0x800300 等价于pushl
%eip //下一条指令的地址
movl $0x800300, %eip //子程序入口地址给eip
ret
子函数的调用返回指令
ret 指令用于子函数的调用返回,等价于popl %eip //eip指向子函数调用前下一条指令的地址。
Leave 释放栈操作,等价于:
Movl
%ebp, %esp
Popl %ebp
(2)、对整个程序的汇编代码结合相应的栈操作进行深入分析
在没有执行main()函数前,栈的形式为:

执行main()函数时:
Push %ebp 将main()函数执行前较早帧的ebp压入main()函数的栈帧中

Mov %esp,%ebp 将基址指针赋值给栈顶指针,相关栈操作如下:

Sub $0x4, % esp 执行后栈的变化为:

Movl
$0x8 , (%esp) 将8写入esp所指的内存单元中。

Call
0x800483f7 <f> 执行后,栈的变化情况如下:

注意返回地址为addl指令的地址,实质是将下一条指令的%eip入栈
Push %ebp
将main()函数的栈帧的基址指针入栈

Mov
%esp ,%ebp 将基址指针的值赋值给栈顶指针,f()函数栈帧变化为:

Subl
$4, %esp 执行后f()栈帧的变化为:

Movl
8(%ebp), %eax
这条指令,将ebp+8所指地址里面的值,赋值给寄存器%eax,执行玩这个程序后,%eax里面的值为8.
Movl %eax, (%esp) 这条指令将%eax的值存入%esp指针所指的单元中

Call 0x80483ec <g> 执行后栈的变化情况为:

返回地址为call下面语句leave指令的%eip地址,将其入栈保护现场。
Push
%ebp 将f()函数所处栈帧的基址指针入栈,栈的变化为:

Mov %esp ,%ebp 指令执行完后,栈的变化为:

Movl
8(%ebp), %eax ,这条指令,是将%ebp+8指针所指内容的值赋给%eax寄存器,执行该指令后%eax里面的值为8.
Add $0x3, %eax 该指令执行完后,%eax寄存器里面的值为11
Pop %ebp 该指令,执行后,栈的变化为:

ret 指令执行结束,对应的操作是pop
%eip 对应栈的变化为:

Leave 指令,相当于,Movl %ebp,
%esp 执行后栈的变化为:
Popl %ebp

ret 指令相当于pop %eip , 栈的变化为:

Add
$0x1,%eax这条指令执行后寄存器%eax里面的值为12
Leave 指令执行后,栈的变化为:

ret 指令执行后,栈的变化为:

个人总结:
1. 对这个实验的认识:这个实验,通过在main()函数里调用f()函数,在f()函数里调用g()函数,通过调用子函数,观察相应栈的变化情况,来明白对于cup内指令的执行情况。
2. 对于每一个函数而言,都有这里特定的栈空间,通过%ebp,%esp来执行栈操作,通过leave指令执行,释放栈空间的操作。
3. 对于单任务执行的计算机而言,指令是顺序执行,一个指令周期内执行一条指令,通过CPU里门的%eip寄存器不断的从内存中取指令,执行指令。
4. 对于多任务执行的计算机而言,指令是并行执行,比如通过流水线技术,在一个指令周期内,执行不同的指令。但是在微观上,多任务执行是基于时间片轮转方法的单任务执行,在某种意义上说,多任务执行,在本质上也是单任务的执行。