linux内核分析之-x86汇编原理

时间:2022-09-01 08:03:53

linux内核分析之-x86汇编原理

作者:郎勇

前言

冯·诺依曼提出了在数字计算机内部的存储器中存放程序的概念(Stored Program Concept),这是所有现代电子计算机的范式,被称为“冯· 诺依曼结构”,按这一结构建造的电脑称为存储程序计算机(Stored Program Computer),又称为通用计算机。冯·诺依曼计算机主要由运算器、控制器、存储器和输入输出设备组成,它的的特点是:程序以二进制代码的形式存放在存储器中;所有的指令都是由操作码和地址码组成;指令在其存储过程中按照执行的顺序(摘自百度百科);

学习汇编,有助于我们更好地理解通用计算机的运行原理,因此无论在我们的学习和工作中是否会涉及到汇编,了解汇编的基本知识,对开阔我们程序开发人员的视野,是大有帮助的。编写本博客的初衷是为了完成网易云课堂的随堂作业,希望自己在编写本博客的过程中,对x86汇编原理获得更深一步的认识,与此同时,也希望能帮助所有看到这篇博客的人。

对系统学习linux内核感兴趣的同学,可以前往
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000


通用寄存器的类型

16bit 32bit desc
AX EAX 累加器(Accumulator)
BX EBX 基地址寄存器(Base Register)
CX ECX 计数寄存器(Count Register)
DX EDX 数据寄存器(Data Register)
BP EBP 堆栈基指针(Base Pointer)
SI ESI 变址寄存器(Index Register)
DI EDI 变址寄存器(Index Register)
SP ESP 堆栈顶指针(Stack Pointer)

此外还有一个需要了解的寄存器是IP\EIP指令指针寄存器(Instruction Pointer),它可以存放下次将要执行的指令在代码段的偏移量。可以理解为它总是指向下一条将要执行的指令的编号。IP\EIP寄存器通常是不可以直接操作的,通常只能通过jmp、call或者ret等指令进行间接修改。

常见的汇编指令

  • 数据传输指令 MOV, PUSH, POP等
  • 程序转移指令 CALL, RET, JMP等
  • 算数运算指令 ADD,SUB,MUL,DIV等
  • 逻辑运算指令 AND,OR,XOR,NOT等

一段简单的C程序对应的汇编程序

我们编写一段简单的c程序

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

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

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

执行命令gcc –S –o main.s main.c -m32生成c程序对应的汇编代码

linux内核分析之-x86汇编原理

为了方便查看起见,把所有.开头的行删除,因为这些行是汇编器命令,这里我们并不关心。结果如下:

g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $11, %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 $3, (%esp)
call f
addl $1, %eax
leave
ret

程序从入口函数main开始分析:

pushl   %ebp
movl %esp, %ebp

这两句相当于enter指令,用来在程序开头启用新的栈底地址,将之前的堆栈基地址保存到栈顶指向的内存地址中,然后将esp和ebp指针对齐。

subl    $4, %esp

将esp向栈顶偏移4个字节的距离

movl    $3, (%esp)

将立即数3存储到esp寄存器指向的内存地址中

call f

程序跳转到函数f的入口地址,eip中存放函数f的第一条指令

pushl   %ebp
movl %esp, %ebp
subl $4, %esp

此三条指令跟main函数的前三条指令完成的功能是一样的。

movl    8(%ebp), %eax

将堆栈基地址向栈底偏移8个字节的长度,内容取出后存储到eax中。此时eax中存储的内容是3。

movl    %eax, (%esp)

将eax中的内容(即3)存储到esp对应的内存地址中。

call g

程序跳转到函数g的入口地址,eip中存放函数g的第一条指令。

pushl   %ebp
movl %esp, %ebp

此三条指令跟main函数和f函数的前两条指令完成的功能是一样的。

movl    8(%ebp), %eax

将堆栈基地址向栈底偏移8个字节的长度,内容取出后存储到eax中。此时eax中存储的内容是3。

addl    $11, %eax

将立即数11加到eax中,eax中的内存为14

popl    %ebp

弹出esp中的内容,恢复函数f的堆栈基地址

ret

eip恢复为函数g返回后,函数f继续执行的代码段偏移

leave
ret

弹出esp中的内容,恢复函数main的堆栈基地址,eip恢复为函数f返回后,函数main继续执行的代码段偏移

addl    $1, %eax

将立即数1加到eax中,此时eax的内容为14+1=15

leave
ret

弹出esp中的内容,恢复之前调用函数的堆栈基地址,eip恢复为函数main返回后,调用函数继续执行的代码段偏移

结论

计算机的运行过程,就是一个按次序读取存储器中代码指令,然后通过cpu解释并执行指令的过程。执行时所用到的基本数据结构是堆栈。cpu可以直接操作寄存器中的内容。

原创作品转载请注明出处