淘先锋技术网

首页 1 2 3 4 5 6 7

赵连讯 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
本课程是后来补充进来的,因为错过了提交时间。

冯诺依曼体系结构

为了更好的讲清楚计算机的工作原理,老师首先提出了冯诺依曼体系结构这个概念。
从网上总结了部分内容。与冯诺依曼体系结构相对的概念是哈佛体系结构。两者都是存储系统计算机结构。冯诺依曼体系结构将数据和程序存储在同一个存储器上。因此当cpu获得程序和数据的时候,数据宽度都是相同的,从同一个存储器上获得。而哈佛结构的存储系统,是指程序和数据存储在不同的存储器上。cpu从程序存储器上拿到程序后,翻译出来数据的地址,再去数据区域取得数据。这里的灵活性是是程序和数据的宽度可以不同。这两种体系结构都有不同的芯片对应。而对于软件开发者或者操作系统研究者来讲,这两种体系结构的区别是透明的,一般看不到两者的区别。
哈佛结构应用好处是,将程序和数据空间分开,使得程序独立性更好。取值和取数据可以同时进行,此种并行性提高了程序的执行速度。

存储程序计算机

老师从冯诺依曼体系结是为了引出存储程序计算机这样一个概念。存储程序存储在内存中。CPU从存储器中读出每一条语句,依次执行,完成了程序的整个流程。

简单图示:

无论读取数据还是程序,数据宽度都是相同的。
为什么程序知道读取哪一行代码呢?因为在CPU中的EIP中,存储了当前程序的地址。指向了代码段。代码段使用寄存器CS来表示。
EIP具有自动加1的功能,那么就能从程序存储空间一条指令一条指令的取出来,然后执行。

EIP

EIP具有自动加1的功能,自动加一条指令,而不是加1,这里一条指令可能是变长的。硬件会自动处理这种情况。
EIP只能被如下指令修改,JMP,RET,IRET,CALL。EIP寄存器不能使用mov等指令修改。

X86的汇编基础

通用寄存器的列表:
EAX
EBX
ECX
EDX
EBP:堆栈的基指针
ESI
EDI
ESP:
段寄存器
CS
DS
ES
SS:堆栈段的段值
FS
GS
精确的定位一条指令:cs:eip
标志寄存器EFLAGS
虽然老师是略讲了这个寄存器,但是EFLAGS在后续中还会多次提到。所以这里可以查看一些重要的位数。
TF:表示trap,单步执行的用到
IF:interrupt enable flag中断使能寄存器
针对此EFLAGS的操作,有专门的命令:

pushf 把flag的值压栈
popf 把栈顶的值更新到eflag寄存器中

AT&T汇编格式

汇编语言的格式有两种类型,一种是AT&T格式,一种是intel格式,在如下文件文档中详细讲解了两者的不同。
http://sourceware.org/binutils/docs/as/i386_002dMemory.html#i386_002dMemory
在linux中使用AT&T的汇编指令格式。

重要指令

push %eax
等价的指令为:

subl $4,%esp
movl %eax,(%esp)

首先esp的值减去4,然后将eax寄存器中的值存入到esp中。栈顶被压栈。

pop %eax
等价的指令为:

movl (%esp),%eax
addl $4,%esp

把栈顶的值存储到eax中,然后esp加上4.

call 0x1234
等价的指令为:

pushl (%eip)
movl $0x1234,%eip

ret
等价的指令为:

popl (%eip)

汇编分析:
C语言源程序:

#include <stdlib.h>
int g(int x)
{
  return x + ;
}

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

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

gcc -S -o test.s test.c -m32
生成的汇编代码,删除了其中的.开头的信息。

g:
    pushl   %ebp
    movl    %esp, %ebp
    movl    (%ebp), %eax
    addl    $3, %eax
    popl    %ebp//这里没有leave,因为这个函数没有修改esp,所以没必要恢复。
    ret
f:
    pushl   %ebp
    movl    %esp, %ebp //清空栈,此时ebp指向新的栈底部
    subl    $4, %esp//esp下移动
    movl    (%ebp), %eax//ebp+8处的值是入参,放入eax中
    movl    %eax, (%esp)//把参数存储压栈
    call    g
    leave//与开始对应,并且恢复了ebp,esp指向了调用f之前的栈顶位置
    ret//修改eip,esp栈顶继续减少
main:
    pushl   %ebp //保存ebp
    movl    %esp, %ebp//清空栈,此时ebp==esp
    subl    $4, %esp//esp向下移动4字节
    movl    $8, (%esp)//将数据8存储到esp指向的位置
    call    f//此时会将下一行代码的地址压栈,更新eip=f
    addl    $1, %eax//返回值加上1,存储到eax中
    leave //恢复ebp,esp重新指向上一个栈顶位置
    ret//上一条指令恢复给eip

leave是伪代码,等价指令为,撤销栈清空,恢复ebp

movl %ebp,%esp
popl %ebp

enter是伪代码,等价指令为,实现清空栈

pushl %ebp
movl %esp,%ebp

从上述代码我们可以看到ebp指向的栈底,上一个栈的栈顶位置。用于恢复上一个栈的栈顶。