您的位置:首页 > 理论基础

计算机程序的工作原理

2013-05-17 00:28 225 查看

计算机的工作原理-实验一

中科大-SA*****6178-叶*冬

实验代码:
int g(int x)
{
return x+3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(x)+1;
}
分析过程:
对于一个c源程序来说,如果想运行,需要经过一下步骤:
1.预处理
2.编译
3.链接

在Ubuntu10 环境下,来一步一步分析:
1.预处理阶段:
预处理器根据源程序中以#号开头的关键字,在我们的程序中,引用了stdio.h的头文件,
预处理器会把该头文件的内容插入到程序中,得到了一个完整的C源程序。
在linux系统中使用的命令是:gcc -E -o example.cpp example.c。可以查看预处理之后
的程序。
2.编译阶段
编译器将预处理后的example.cpp文件编译成example.s,里面就包含了该程序对应的汇编
指令。
在linux系统中使用的命令是:gcc -s -o example.s example.cpp

然后汇编器进而将example.s翻译成机器语言指令,并且这些指令组成的有意义的二进制代码
按一定的格式存储在example.o中。
生成该文件的linux命令是:gcc -o example.o example.s

4.链接阶段:
在我们的程序中没有用到其他的库函数,或者其他文件中的函数,那么就不需要再链接其他的
.o文件。但是如果我们假设在该程序中有一个调用系统库函数的语句,那么程序在链接时就需要
把该被调用的函数的.o文件加载到内存中来,以使我们的程序完整。
最后在我们的例子中,就可以通过如下的linux命令得到可执行文件:gcc -o example example.o





那么,到现在为止,就可以直接运行了。
运行的命令如下:/example

实际运行结果如下:
mars@Mars:~$ gcc -E -o example.cpp example.c
mars@Mars:~$ gcc -x cpp-output -S -o example.s example.cpp
mars@Mars:~$ gcc example.o -o example

接下来通过反汇编代码分析该过程:
返回汇编代码如下:







析过程如下:

由于函数调用栈格式如下:



注:此图来自百度图片

在我们的例子中:



Main函数的过程分析:

0x08048362 <+0>: lea 0x4(%esp),ecx //保存上一栈帧栈顶地址

0x08048366 <+4>: and $0xfffffff0,%esp //调整栈指针的地址,与内存地址对齐

0x08048369 <+7>: pushl -0x4(%exx) //保存4(%esp)中的参数,以便将来恢复

0x0804836c <+10>: push ebpp //入栈保存上一栈帧的基地址

0x0804836d <+11>: mov %esp,ebp //将调用者函数的栈顶指针ESP赋值给被调函数的EBP作为被调函数的栈底

0x0804836f <+13>: push eax //保存存放在eax中的地址参数

0x08048370 <+14>: sub $0x4,%esp //栈顶指针由高地址向低地址移动四个字节,用来存放即将到来的参数

0x08048373 <+17>: movl $0x8,(%esp) //保存局部变量 8

0x0804837a <+24>: call 0x804834f <f> //调用子函数f ,子函数的入口地址即为0x804834f

0x0804837f <+29>: add $0x1,eax // eax=eax+1 ==12+1 =13

0x08048382 <+32>: add $0x4,%esp // 栈顶指针%esp向高地址移动四个字节,

0x08048385 <+35>: pop ecx // ecx出栈

0x08048386 <+36>: pop ebpp // ebp = %esp,esp= esp-4 ebp指向上一帧函数的基地址

0x08048387 <+37>: lea -0x4(ecx),%esp // 恢复上一栈帧栈顶地址

0x0804838a <+40>: ret // 消毁main函数整个栈

f函数的过程分析:

0x0804834f <+0>: push ebp // 入栈保存main函数的基地址,保存返回地址以便将来返回

0x08048350 <+1>: mov %esp,ebp // 开辟新的函数栈,开始子函数栈操作

0x08048352 <+3>: sub $0x4,%esp //栈顶指针由高地址向低地址移动四个字节

0x08048355 <+6>: mov 0x8(ebp),eax //调用main函数局部变量8(实参副本)并保存在寄存器eax中

0x08048358 <+9>: mov eax,(%esp) //开辟空间 保存实参副本

0x0804835b <+12>: call 0x8048344 <g> //调用子函数g

0x08048360 <+17>: leave //ebp = %esp,esp = esp-4 ebp指向main函数的基地址,释放地址空间

0x08048361 <+18>: ret // %eip执行调用main函数调用地址下一处地址0x0804837f

g 函数的过程分析:

0x08048344 <+0>: push ebp //入栈保存f函数的基地址,以便将来返回

0x08048345 <+1>: mov %esp,ebp //开辟新的函数栈

0x08048347 <+3>: mov 0x8(ebp),eax //传递参数 eax = ebp + 8 内存地址的值(8)

0x0804834a <+6>: add $0x3,eax //eax = eax +3 ==8+3=11

0x0804834d <+9>: pop ebp // ebp = %esp,esp =esp-4 ebp指向f函数的基地址

0x0804834e <+10>: ret //ret之前,esp指向的是返回f函数的下一个指令的地址,即上述说的leave地址,ret后,eip将指向leave地址 0x08048360,此时程序会跳转到该处执行

实验总结:

通过本次实验,

首先:熟悉了c源程序到可执行程序的一个生成流程;

再次:深刻了计算机内存在程序执行过程中发生的故事,明确了栈空间对于函数的正确工作的重要意义。

最后:加深了对单个任务下计算机系统的工作过程,为下一步学习多任务多进程协调工作做好了准备。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐