计算机程序工作原理———简单c程序实例剖析

来源:互联网 发布:电脑能写小说的软件 编辑:程序博客网 时间:2024/06/05 09:00

    目前电子计算机的结构还是以冯·诺依曼提出的以程序存储和程序控制为基础的,其核心是存储程序。

    对于我们程序员写出的程序我们将它存储到内存里面,内存有很多区域,如代码区,数据区,堆栈区等等。我们的代码会存储到代码区中,cpu通过总线与存储器进行连接,cpu中有很多寄存器,在x86中,有一个指令寄存器%eip用来指向下一次执行指令的地址,通过cs和%eip中的内容可唯一指向程序指令的地址。对于我们编写的程序(高级语言),一般都是通过编译,链接两步执行的。高级语言写出来的程序首先汇编成汇编指令,汇编指令在经过编译器编译成目标代码(二进制代码),此时编译的代码还不能完全执行,因为这可能只是一个程序片段其中缺少了许多函数所必须的库,这时通过链接将所需要的程序和库链接起来放在内存中,cpu通过%eip指向的内容依次取指令,这样反复执行一个计算机程序就运行完毕了。

     例如如下一个简单的c程序:

      


    在gcc下汇编成汇编代码如下:



        首先解释一下各个寄存器的含义:在x86中有很多寄存器,%eax累加寄存器,%ebx基地址寄存器,%ecx计数寄存器,%edx数据寄存器,%ebp堆栈基址针寄存器,%esp堆栈顶指针寄存器。

      我们从main函数开始:

pushl %ebp;

首先将%ebp的内容压栈,栈顶指针向下移动4个字节,相当于

         subl $4,%esp;

         movl %ebp,(%esp);

        在x86内存中栈是向下增长的。 其中$表示立即数,%表示寄存器,()表示寄存器中的内容。

movl %esp,%ebp;

        将%esp中的内容赋值给%ebp。相当于堆栈基址针向下移动4个字节。

subl $4,%esp;

%esp中的内容减去4,也就是堆栈顶址针向下移动4个字节。

movl $9,(%esp);

将立即数9赋值给%esp所表示的内容。

call f;

调用f 函数;进入到函数f当中。

一个call命令的执行过程相当于先将当前cpu中%eip的内容压栈,然后将函数f的首地址赋值给%eip,这样就完成了函数的调用。

f:

pushl %ebp;

movl %esp,%ebp;

subl $4,%esp;

movl 8(%ebp),%eax;    将%ebp中的内容加上8然后在赋值给%eax

movl %eax,(%esp);    将%eax值赋值给%esp

call g;    调用g函数;进入到函数g当中。


g:

pushl %ebp;

movl %esp,%ebp;

movl 8(%ebp),%eax;

addl $4,%eax; 将%eax中内容加上立即数4

popl %ebp ;   将%ebp中内容出栈

 一条popl指令相当于如下两条指令:

movl (%esp),%ebp;

addl $4,%esp;

ret;返回原地址,效果相当于popl %eip;


函数f和main函数类似:

leave指令相当于如下指令:movl%ebp,%esp;

                                             popl%ebp;

      

   在每一个函数结束前都会有leave和ret指令,当函数内没有参数传递时,leave指令可以省略。

最后总结一下程序运行的过程:

  1. 调用其它函数时,将指令指针入栈保存,以便函数执行结束能返回来继续下一条指令的执行;

  2. 被调用函数执行时,要将当前栈基地址压栈,以便调用结束后能恢复到调用函数栈空间;

  3. 函数参数入栈,参数入栈顺序是从右到左进栈;

  4. 函数退出时,将 %esp 赋值为 %ebp,释放当前函数所使用的栈空间;

  5. 然后将栈顶元素出栈保存到 %ebp,把%ebp恢复到调用函数(前一个函数)的栈基地址;

  6. %eip退回到上一个函数即将要执行的那条语句的地址上。



陈思宇 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC1000029000

       


0 0