Linux内核分析——x86汇编基础

pianogirl 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

一、计算机是如何工作的
我们平时使用的计算机属于“冯·诺依曼结构”,将程序和数据一起存储在内存中,CPU从内存中不断地取指令并执行指令。除此以外还有“哈佛结构”。
CPU能够执行的指令是二进制指令,为便于理解,人们发明了汇编指令,是对机器指令的简单翻译,增强了可读性。编程语言多属于高级语言,有良好的可读性,却不能被计算机直接执行,因此,高级语言通常都要经过编译或解释说明等过程,转换为计算机能够执行的机器语言。
下面以一个简单的C程序为例,分析计算机如何执行指令。

C程序代码如下,将其命名为main.c

int g(int x)
{
      return x + 2;
}

int f(int x)
{
      return g(x);
}

int main(void)
{
      return f(12) + 1;
}

然后执行一条命令生成对应的汇编代码:
gcc -S -o 5112.s main.c -m32
注意,Linux 采用的是 AT&T 汇编格式,它与 Intel 汇编格式略有不同。

g:
    pushl   %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %eax
    addl    $2, %eax
    popl    %ebp
    ret
f:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $4, %esp
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    g
    leave
    ret
main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $4, %esp
    movl    $12, (%esp)
    call    f
    addl    $1, %eax
    leave
    ret

Linux内核分析——x86汇编基础_第1张图片
二、汇编代码分析
整个程序过程中堆栈以及寄存器的变化如下图:(方便起见,内存编号做了改动)
Linux内核分析——x86汇编基础_第2张图片
Linux内核分析——x86汇编基础_第3张图片
Linux内核分析——x86汇编基础_第4张图片
Linux内核分析——x86汇编基础_第5张图片
Linux内核分析——x86汇编基础_第6张图片
Linux内核分析——x86汇编基础_第7张图片
Linux内核分析——x86汇编基础_第8张图片
Linux内核分析——x86汇编基础_第9张图片
Linux内核分析——x86汇编基础_第10张图片
Linux内核分析——x86汇编基础_第11张图片
Linux内核分析——x86汇编基础_第12张图片
Linux内核分析——x86汇编基础_第13张图片
Linux内核分析——x86汇编基础_第14张图片
Linux内核分析——x86汇编基础_第15张图片
Linux内核分析——x86汇编基础_第16张图片
Linux内核分析——x86汇编基础_第17张图片
Linux内核分析——x86汇编基础_第18张图片
Linux内核分析——x86汇编基础_第19张图片
Linux内核分析——x86汇编基础_第20张图片
Linux内核分析——x86汇编基础_第21张图片
Linux内核分析——x86汇编基础_第22张图片
Linux内核分析——x86汇编基础_第23张图片
Linux内核分析——x86汇编基础_第24张图片

三、思考与总结

1、eip是控制整个程序运行过程的决定因素。
计算机从 eip 指向的地址取出一条指令执行,其情况无外乎三种:
(1、若遇到跳转语句(JMP 等)时,先将 eip 压栈保存,即当前指令的下一条指令的地址,然后将需要跳转的目的地址赋给 eip,实现跳转;
(2、若遇到函数调用call时,除了将 eip 压栈,还要保存当前堆栈的状态,将栈基指针 ebp 压栈,然后将相应函数地址赋给 eip,实现跳转;
(3、若为普通指令,则继续从 eip 指向的地址取出下一条指令执行。

从堆栈分析图中可以看到,该程序的两次函数call调用中,总是先将当前eip值保存在堆栈中,紧接着将栈基指针 ebp 压栈保存,处理好以上两个工作后,才进入新的函数。

2、ebp的移动使得指令执行过程更加层次清晰。
曾经认为堆栈的运动只有栈顶指针在参与,栈底指针无须移动。
然而从这个两次函数调用的例子可以发现,ebp总是保存当前“子堆栈”(我姑且这么称呼)的栈底位置,它和栈顶指针esp一起构成了当前函数用到的“子堆栈”。
进入另一个函数之前,总是保存当前栈的基地址,开始一个新的栈,使每个函数有独立的栈空间,当函数返回时,能恢复到之前函数的栈空间。
从图上可以看到,“ebp地址xxxx”将整个堆栈数据划分为3个区域,分别对应3段函数,结构清晰,层次鲜明,使人不得不佩服机器处理数据的严谨与清晰。

3、总结。
汇编可以用一句话概括:汇编就是在(寄存器和寄存器)或 (寄存器和内存)之间来回move 数据。就是指:数据在内存和寄存器间来回流动,流动的越频繁就代表程序越复杂。

4、其他问题——需要确认的几个问题
(1、同时运行的几个程序,每一个都能在内存中分配到一段堆栈,不会冲突?——保护机制?
(2、5112.s中只把enter指令拆解,不把leave指令拆解——与编译器有关?
(3、32位计算机,pushl指令中,要先做subl $4, %esp。为什么是4?是因为32/8 =4吗?32位计算机里,内存0x000007F(举个例子)指的是几个字节的内容?
(4、从图中可以看到,压栈的是参数,而不是常数。C中的全局变量、形参、实参,究竟在堆栈中怎么体现的?

参考资料:http://blog.csdn.net/liutianshx2012/article/details/50730780 码着,有时间看看。

你可能感兴趣的:(Linux,编程语言)