从汇编的角度浅谈计算机是如何工作的?

1.首先我们先温习一下计算机系统的结构

 

1)计算机硬件系统

 

 

 

 

 

2)计算机软件系统

 

 

 2.计算机的内部工作方式

 

这里有一个很重要的寄存器叫做指令寄存器,也就是指令指针寄存器,其存储的是下一个时钟周期将要执行的指令所在的程序的地址。英文形式为IP(Instruction Pointer)。注意这里的IP不是我们常说的网络IP(网络之间互连的协议即Internet Protocol)。这里的IP一般是指16位系统的指令指针,如果是32位的则成为EIP,64位的为RIP。

此外还有一些通用寄存器:

百度计算机工作原理可以查到其工作原理,计算机在运行时,先从内存中取出第一条指令,通过控制器的译码,按指令的要求,从存储器中取出数据进行指定的运算和逻辑操作等加工,然后再按地址把结果送到内存中去。接下来,再取出第二条指令,在控制器的指挥下完成规定操作。依此进行下去。直至遇到停止指令。程序与数据一样存贮,按程序编排的顺序,一步一步地取出指令,自动地完成指令规定的操作是计算机最基本的工作原理。这一原理最初是由美籍匈牙利数学家冯.诺依曼于1945年提出来的,故称为冯.诺依曼原理。

 

3.下面我们要从汇编语言的角度分析一下计算机的工作过程。必不可少的会用到堆栈。以32位系统为例,简单介绍一下EIP,EBP,ESP。

1)EIP寄存器里存储的是CPU下次要执行的指令的地址。

2)EBP寄存器里存储的是栈的栈底指针,通常叫栈基址。

3)ESP寄存器里存储的是栈的栈顶指针。并且始终指向栈顶。

注:以Linux内核使用的AT&T汇编格式为例(右边为目的操作数)。

下面介绍mov指令以及几种常见的寻址方式(寄存器寻址,立即寻址,直接寻址,间接寻址,变址寻址):

      直接寻址:直接访问一个指定的内存地址的数据。

  间接寻址:将寄存器的值作为一个内存地址来访问内存。

  变址寻址:在间接寻址之时改变寄存器的数值。

此外还有几个特殊的指令:

        

注意:ret 相当于pop %eip,eip寄存器不能被直接修改,只能通过特殊的指令间接修改。

下面是一段C语言代码:

 1 int g(int x)
 2 {
 3   return x + 3; 
 4 }
 5 int f(int x)
 6 {
 7   return g(x)+2; 
 8 }
 9 int main(void)
10 {
11   return f(8) + 1;
12 }

将其命名为test.c文件

在linux环境(32位)下输入:

gcc -S -o test.s test.c -m32

将会生成test.s文件。打开test.s文件,删除以“.”开头的行,剩余的就是以上C文件对应的汇编代码。(.起始的行是在链接时才用到)    如图:

注意:

  1.第4行和第12行表示的是基址寄存器的内容加上8后所对应的地址的值存储到eax寄存器,与main函数中的f(8)中的数字8没有任何关系,将8改为0后第4行和第12行仍不变,真正的数字8在第22行。

      2.一般认为的堆栈结构是从上往下地址依次递减的,即push时需要将esp减4(32位,即4个8字节),pop时需要将esp加4(32位)。

下面进行分析(结合示意图):

 1 g:
 2   pushl %ebp ;         12. 把ebp(4)放入位置七, esp指向下一个标号(7)
 3   movl %esp, %ebp ;    13. ebp指向esp的位置(标号7)
 4   movl 8(%ebp), %eax ; 14. ebp + 8指向标号5(位置五), eax = 8
 5   addl $3, %eax ;      15. eax = eax + 3 = 8 + 3 = 11
 6   popl %ebp ;          16. ebp = 4, esp指向上一个标号(6)
 7   ret ;                17. eip = 15, esp指向上一个编号(5)
 8 f:
 9   pushl %ebp ;         6. ebp(1)放入位置四, esp指向下一个标号(4)
10   movl %esp, %ebp ;    7. ebp指向esp的位置(标号4)
11   subl $4, %esp ;      8. esp指向下一个标号(5)
12   movl 8(%ebp), %eax ; 9. ebp + 8指向标号2(位置二), eax = 8
13   movl %eax, (%esp) ;  10. 把8放入位置五
14   call g ;             11. 把eip(15)放入位置六, esp指向下一个标号(6)
15   addl $2, %eax ;      18. eax = 11 + 2 = 13
16   leave ;              19. esp指向ebp的位置(标号4); 然后 popl %ebp ,即ebp = 1, esp = 3(esp = esp - 1)
17   ret ;                20.eip=24, esp上移一个位置(标号2)
18 main:
19   pushl %ebp ;         1. 把ebp(0)放入堆栈,位于位置一,同时esp下移指向标号1
20   movl %esp, %ebp ;    2. 将ebp指向esp的位置(标号1)
21   subl $4, %esp ;      3. esp减4,指向下一个标号(2)
22   movl $8, (%esp) ;    4. 把8放入位置二
23   call f ;             5. 把eip(24)放入位置三, 同时esp指向下一个标号(3)
24   addl $1, %eax ;      21. eax = 13 + 1 = 14
25   leave ;              22. esp指向ebp的位置(标号1);然后popl %ebp,即 ebp = 0, esp = 0(esp = esp - 1)
26   ret ;                23. 回到main函数之前的堆栈


注意:

      1.程序是从main函数出执行的。

      2.初始时ebp和esp都指向标号0(就是某内存地址).

      3.分析时要对ebp、esp和eip的功能要了解,对其值要分析准确。

附:示意图

 

 总结:

  eip,ebp和esp寄存器起到了指示作用,而在内存中可以存储数据也可以存储地址,这在寻址的时候会进行区分。

      函数调用堆栈是高级语言得以运行的基础。

      计算机在执行程序时先将要执行的相关程序和数据放入内存储器中,在执行程序时CPU根据当前程序指针寄存器的内容取出指令,分析并执行指令,然后再取出下一条指令分析并执行,如此循环下去直到程序结束指令时才停止执行。其工作过程就是不断地取指令和执行指令的过程,最后将计算的结果放入指令指定的存储器地址中。

 

个人感悟:

  计算机很复杂,复杂到我们不能想象;计算机又很简单,简单的只有0和1。想要让计算机更好的为自己工作,就要学习从机器的角度分析。不要因为走的太远而忘记了当初为什么要出发。

 

PS:由于时间紧促以及个人能力的不足,不能保证没有错误,欢迎任何人批评指正!      

参考:

 《Linux内核分析》MOOC课程地址:http://mooc.study.163.com/course/USTC-1000029000 

  计算机系统的组成 http://it.dywz.cn/show.aspx?id=339&cid=17

  类似分析 http://www.cnblogs.com/clevercong/p/4321901.html

posted on 2015-03-24 17:37  loto.lt  阅读(441)  评论(0编辑  收藏  举报