Linux内核分析作业(1)——计算机是如何工作得?
根据163MOOC学院中国科学技术大学孟宁孟老师课程所写得博客
作者:肖冲冲 原创作品请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一,计算机的工作过程
计算机的基本原理是存储程序和程序控制(冯﹒诺依曼体系),简单来说,我们需要先把需要进行操作的指令(程序)和数据先输入到计算机的存储设备中,然后计算机将严格执行需要执行的指令,包括从那个地址取数(或指令),进行什么操作(加减移位等),然后再送回到什么地址。
从硬件上来说,组成现代CPU的基础元件的有超大规模的逻辑门,定时器,以及高性能的cache和高精度时钟。逻辑门提供逻辑仲裁功能,运算器精于各种运算,cache提高数据交换效率。
从软件上来说,我认为我们的程序就是与计算机尤其是CPU上面的各种寄存器进行数据交互来使用计算机的硬件功能,比如网络通信,就是我们的程序使用网络接口进行数据交互。或者最经典的C语言打印“hello world”这个程序,简单说来就是我们的程序控制CPU相关的寄存器在显存里面写入了“hello world”这个字符串显示的相关指令和数据。在这个过程中,软件控制硬件目的就是,我们的显示器上面显示出了“hello world”,而这一过程本身可以抽象为软件和显示设备之间的数据交互。
总得来说,在计算机上,软件承担着主动向硬件进行数据交互的任务,而硬件是被动的接收和响应处理这些数据(当然你得按照正确的时序来操作还得保证输入正确的数据)。那么有没有硬件是可以主动去做一些事情呢,当然是有得,在ASIC里面,都是先通过软件(现在大部分是FGPA级实现)来进行模拟验证,然后再通过纯硬件实现相关功能。
而我们计算机的工作过程,我认为就是软件操作这些硬件的过程。
至于如何来操作这些东西,我们需要了解CPU的寄存器,基础的汇编指令等。
下面我跟着孟老师的课程,反汇编一段C语言代码来简单介绍下这个过程和涉及的相关知识。
二,汇编代码工作过程中堆栈过程的变化
至于如何到实验楼进行相关实验,在孟老师的视频讲得非常详细,这里略过不表。
我的过程截图如下:
实验楼里面的虚拟机,使用非常方便。
这个是我们的目标c代码,很简单的一段代码。
编译命令如下:
1 int g(int x){ 2 return x + 3; 3 } 4 int f(int x){ 5 return g(x); 6 } 7 int main(void){ 8 return f(8) + 1; 9 }
使用如下命令产生汇编代码:
注意:此处-m32参数是反编译成32位的汇编指令,因为实验楼所提供的虚拟机是64位的,所以加上该参数。我实验了下,如果不加入该参数,所得到的指令会掺杂64位的指令。比如movq和pushq等。
所生成的汇编代码如下(该代码已经将一些标志符等进行了删减以利于分析):
1 .file "main.c" 2 g: 3 pushl %ebp 4 movl %esp, %ebp 5 movl 8(%ebp), %eax 6 addl $3, %eax 7 popl %ebp 8 ret 9 f: 10 pushl %ebp 11 movl %esp, %ebp 12 subl $4, %esp 13 movl 8(%ebp), %eax 14 movl %eax, (%esp) 15 call g 16 leave 17 ret 18 main: 19 pushl %ebp 20 movl %esp, %ebp 21 subl $4, %esp 22 movl $8, (%esp) 23 call f 24 addl $1, %eax 25 leave 26 ret
下面进入正题。
首先来说下几个基础点:
1,几个寄存器的名称和作用
- EIP 32位指令寄存器 用于存放下一次需要执行的指令地址,在当前的指令地址被取出之后,会自动的+1
- EBP 扩展基址指针寄存器其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部
- ESP 栈指针,用于指向栈的栈顶(下一个压入栈的活动记录的顶部),而EBP为帧指针,指向当前活动记录的底部
- EAX 是一种32位通用寄存器。 EAX寄存器称为累加器,AX寄存器是算术运算的主要寄存器,所有的输入、输出只使用AL或AX人作为数据寄存器
2,函数调用的堆栈是由多个堆栈叠加起来得。我的理解如下图示,函数example()占用了如下的一段空间,而它是由函数内的多个栈叠加起来得(由于当初堆栈学得不太好,此处不知道理解得是否正确?):
3,函数的返回值默认由EAX寄存器存储返回给上一级的函数。
4,pushl %ebp,将当前ebp压栈同时esp的值被修改。
5,在程序中,
- ret = popl %eip
-
enter = push %ebp
movl %esp,%ebp
-
leae = movl %ebp,%esp
popl %ebp
下面看具体分析:
1 .file "main.c" 2 g: ;函数g 3 pushl %ebp ;将ebp压栈,同时esp的值减去4个字节,栈向下增长一段 4 movl %esp, %ebp ;采用寄存器寻址的方式,将esp的值赋给ebp 5 movl 8(%ebp), %eax ;将ebp变址寻址加8,将该地址的栈空间存储的值赋给eax 6 addl $3, %eax ;将eax里面存储的数据加3,即实现 8 + 3的操作,然后将结果赋值给eax,此时eax=11 7 popl %ebp ;将ebp出栈,此时esp加上4个字节,指向了原先存储着leavel的栈位置 8 ret ;将eip出栈,回退到上一个跳转时的位置下一段指令,即f函数中的leave处 9 10 f: ;函数f 11 pushl %ebp ;将ebp压栈,同时esp的值减去4个字节,栈向下增长一段 12 movl %esp, %ebp ;采用寄存器寻址的方式,将esp的值赋给ebp 13 subl $4, %esp ;将esp的值减4个字节,即栈向下再增长一段 14 movl 8(%ebp), %eax ;将ebp变址寻址加8,将该地址的栈空间存储的值赋给eax 15 movl %eax, (%esp) ;将eax的值赋给esp所指向的地址的栈空间 16 call g ;跳转到函数g,执行之后EIP指向leave的值将会保存到栈中 17 leave ;leave指令此时将ebp的值赋值给esp,然后将ebp出栈,然后esp将指向存储着上一个函数跳转时的下一段指令,即函数main中的addl处 18 ret ;将eip出栈,回退到上一个跳转时的位置下一段指令,即main函数中的addl处 19 20 main: ;程序的入口点 21 pushl %ebp ;将ebp压栈,同时esp的值减去4个字节,栈开始向下增长 22 movl %esp, %ebp ;采用寄存器寻址的方式,将esp的值赋给ebp 23 subl $4, %esp ;将esp的值减4个字节,即栈向下增长一段 24 movl $8, (%esp) ;对esp所指向的栈空间赋值为8 25 call f ;跳转入函数f,转向f段代码,此时EIP指向函数f 26 addl $1, %eax ;将eax中的值加1并存入eax,此时eax = 12 27 leave ;此时将ebp的值赋值给esp,然后esp将指向栈尾0,然后将ebp出栈,同样ebp也将指向栈尾0 28 ret ;将eip出栈,因为没有下一条指令,程序至此执行完毕。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。