<<  < 2013 - >  >>
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31




                  计算机是怎样工作的

 

                

  本文是菜鸟借助于对C程序的执行过程,以及对反汇编代码的分析来描述计算机是如何工作的,本文不考虑中断,一些异常情况。如果本文分析中有错误,望老师,同学指正,菜鸟将不甚感激!

 

实验内容:

  通过对example.c程序的分析,将example.c代码分别生成example.cppexample.sexample.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

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

 

 

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.      对于多任务执行的计算机而言,指令是并行执行,比如通过流水线技术,在一个指令周期内,执行不同的指令。但是在微观上,多任务执行是基于时间片轮转方法的单任务执行,在某种意义上说,多任务执行,在本质上也是单任务的执行。

 

 

      

  • 标签:计算机 
  • 发表评论:
    天涯博客欢迎您!