文章目录
- 1 基本概念
- 2 数据格式
- 3 指令
- 4 寄存器的使用惯例
- 5 存储器的越界引用和缓冲区溢出
- 6 浮点代码和浮点寄存器
- 附录 — linux下,利用gcc生成汇编代码
1 基本概念
- 可执行代码文件:即我们常说的程序,为二进制格式,无法直接阅读
- 汇编代码文件:常见后缀为.s,非常接近于计算机执行的实际机器代码,是可阅读的文本格式
- 机器指令:是CPU能直接识别并执行的指令,只执行非常基本的操作,比如将两个存放在寄存器种的数字相加、在存储寄存器之间传递数据等
- CPU包含以下寄存器:
1 程序计数器:称为%eip,表示将要执行的下一条指令在存储器种的地址
2 整数寄存器:一组8个分布存储32bit值的寄存器,这些寄存器可以存储地址或整数数据类型,下图显示了这8个寄存器,名字都以%开头。%ebp和%esp保存着指向程序栈中重要位置的指针,只有根据栈管理的标准惯例才能修改这两个寄存器的值
3 条件码寄存器:保存着最近执行的算术指令的状态信息,用于实现控制流种的条件变化,比如if或while,最有用的条件码有CF(进位标志)、ZF(零标志)、SF(符号标志)、OF(溢出标志)
4 浮点寄存器:包含8个位置,用来存放浮点数据 - 指令与操作数:大多数指令有一个或多个操作数,用来指出执行一个操作中要引用的源数据值和放置结果的目的位置。操作数可分为三类:立即数,即常数值、寄存器和存储器引用
- 寻址模式:有立即数寻址、寄存器寻址、绝对寻址、间接寻址、(基址+偏移量)寻址、变址、寻址、伸缩化的变址寻址等
对这方面感兴趣的可以看下 汇编程序设计与应用 - 栈帧结构:栈用来传递过程参数、存储返回信息、保存寄存器以供之后恢复和本地存储,为单个过程分配的那部分栈成为栈帧,栈帧的通用结构如下图所示。栈帧的最顶端是两个指针定界的,%ebp为帧指针,%esp为栈指针,在程序执行时,栈指针是可以移动的,因此大多数信息的访问都是相对于帧指针的。
2 数据格式
大多数常用数据类型都是作为双字(= 4字节 = 32bit)存储,浮点数有三种形式:单精度(4字节)值,对应于float;双精度(8字节)值,对应于double;和扩展精度(10或12字节)值。
标准数据类型大小如下所示:
数据类型 | 大小(字节) |
char | 1 |
short | 2 |
int | 4 |
unsigned int | 4 |
long int | 4 |
unsigned long | 4 |
char * | 4 |
float | 4 |
double | 8 |
long double | 10/12 |
3 指令
- 指令按功能分可分为以下几大类,这里不展开说明了,想了解的可以去看一下 汇编程序设计与应用:
1 数据传送:mov、pushl、popl等
2 算术和逻辑操作:addl、subl、imull等
3 控制:控制指令包括判断与跳转指令,如setl、jmp等。控制指令常常会访问条件码,并根据条件码的某个组合设置一个整数寄存器或执行一个条件分支指令
4 过程调用:过程调用包括将数据和控制从代码的一部分传递到另一部分,另外,它还必须在进入时为过程的局部变量分配空间,并在退出时释放这些空间。常用指令有call、leave、ret等
4 寄存器的使用惯例
- 程序寄存器组是唯一一个被所有过程共享的资源,必须保证当一个过程(称之为调用者)调用另一个(称之为被调用者)时,被调用者不会覆盖某个调用者稍后还会要使用的寄存器的值
- 寄存器划分:%eax、%edx和%ecx被划分为调用者保存寄存器,被调用者可以覆盖这些寄存器,而不会破坏任何调用者需要的数据;%ebx、%esi和%edi被划分为被调用者保存寄存器,即被调用者在覆盖它们之前,需要将这些寄存器的值保存在栈中,并在返回前恢复它们
5 存储器的越界引用和缓冲区溢出
- C/C++对数组的引用不进行任何边界检查,而局部变量和状态信息都存放在栈中,对一个错误地址的写操作很容易破坏存储在栈上的信息,当程序使用被破坏的状态,试图重新加载寄存器或执行ret指令时,就会发生很严重的错误
- 缓冲区溢出:是一种常见的栈状态被破坏后的现象,比如在栈中分配一个长度超出栈区大小的数组
6 浮点代码和浮点寄存器
- 历史原因:最早,浮点是由一个独立的协处理器完成的,这个部件有它自己的寄存器和处理能力,能够执行一部分指令
- 浮点寄存器:浮点单元有8个浮点寄存器,但是和普通寄存器不一样,这些寄存器被当成一个浅栈对待
附录 — linux下,利用gcc生成汇编代码
执行以下文件,会生成一个main.s文件
gcc -S main.c