从汇编的角度浅谈计算机是如何工作的?
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