一、内存区域。
1. 编制程序的四个步骤
编辑,编译,链接,运行
编译:检查语法并对单个文件产生可执行程序(可执行文件内包含二进制机器代码)
链接:将多个可执行文件进行关联(函数调用信息),增加启动程序等
2. 什么是Bug
一种表现为用户部分操作,例如输入,导致程序无法正常执行
3. 内存区域的划分
一个程序可能被分配到不同的内存区域执行。
处理器从代码区取出指令与操作数,送入算术逻辑单元;
开辟动态内存时,从堆区分配一块合适的区域返回给代码区的代码使用;
PS.动态:所需内存大小在程序设计时不能预先确定或内存过大无法在栈区分配。
调用发生时,函数调用信息关系等信息保存在栈区。
4. 堆区与栈区的对比
栈区 | 堆区 |
存储函数运行时的局部变量、数组等 | 程序运行时动态分配的内存 |
由系统自动预留或回收内存空间 | 需要程序员用专有函数进行申请(new) |
向低地址扩展,底高顶低,先进后出 | 向高地址扩展 |
连续 | 不连续(用链表来存储的空闲内存地址) |
向内存地址减小方向延伸 | 向内存地址增大方向延伸 |
声明局部变量int b,系统自动开辟空间 | p = (char*) malloc(10) |
速度快,但无法控制 | 速度慢,易产生内存碎片,但方便 |
5.堆的内存组织
(1)堆块
空闲态堆块:链入空链表,由系统管理。Flink与Blink分别指向前后空闲块的地址。
占有态堆块:返回由程序员定义的堆块指针,从而对内存进行读写、释放,由程序员管理。
指向堆块的指针或句柄指向的是块身的首地址。
大小:包括块首在内。e.g. 若申请32字节,则实际分配40字节。
单位:8字节(不足则按8字节分配)
(2)堆表
占有态堆块被使用其的程序索引,堆表只索引所有空闲态的堆块。
空表索引:大小128的指针数组,数组的每一项包括两个指针,用于标识一条空表。
Free[n] (n>0)标识堆中所有大小为8n字节的空闲堆块。
Free[0]标识零号空表,链入所有大于等于1028字节、小于512KB的堆块,升序排列。
(3)堆块的分配(以空表为例)
· 普通空表:
首先寻找对应大小的最优空闲块进行分配;
若失败,从稍大的块中按请求的大小割出一块进行次优分配,剩余部分重新标注块首链入空表。(找零)
· 零号空表:
分配时先查最后一个最大的,若能满足,再正向搜索最小能满足需求的空闲块。
(4)堆块的释放(以空表为例)
占用态->空闲态,链入相应表尾
(5)堆块的合并(以空表为例)
分配和释放可能引发堆块合并。两空闲堆块相邻时被合并。
流程:卸下,合并,修改块首,链入新表(合并时还有一种操作叫做内存紧缩)
二、函数调用。
1. 基本理解
(1)可执行文件中,代码是以函数为单元存储的。
(2)函数调用时借助系统栈完成函数状态的保存与恢复。
(3)函数被调用时,系统栈会为该函数开辟新的栈帧,并将其压入栈中。每个栈帧对应一个未运行完的函数,保存了该函数返回地址和局部变量。
(4)当函数返回时,系统栈会弹出该函数对应的栈帧。
2. 步骤
(1)参数入栈:先后顺序从右向左压入系统栈!!!
(2)返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中。
(3)代码区跳转:处理器从当前代码区跳转到被调用函数的入口处。
(4)栈帧调整:保存当前栈帧值;将当前栈帧切换到新栈帧。(EBP入栈,ESP赋值给EBP,更新栈帧底部。)
(注意,在上图中,“局部变量”在“返回地址”上方,但这不意味着在函数调用时参数入栈在返回地址入栈之后!函数调用步骤中的参数入栈指的是不在这个函数中定义的变量,而非函数中的局部变量,可以参考Chapter4缓冲区溢出漏洞中提及的windows控制台程序的主函数参数对应的反汇编代码。)
三、寄存器。
1. 基本概念
(1)寄存器是中央处理器CPU的组成部分。(32位CPU指的就是寄存器大小4个字节)
(2)寄存器是有限存贮容量的高速存贮部件,可用于暂存指令、数据和地址。
(3)CPU本身只负责运算,自带一级缓存和二级缓存(视为读写较快的内存)。内存负责储存数据,但最常用、最频繁读写的数据都会放在寄存器中(如循环变量)。CPU优先读写寄存器,再由寄存器与内存交换数据。
(4)每个函数独占自己的栈帧空间,当前运行的函数的栈帧空间总在栈顶。
(5)函数栈帧中一般包括:局部变量,栈帧状态值,函数返回地址。
栈帧状态值:保存前栈帧的(顶部和)底部,用于本帧弹出后恢复上一个栈帧。(前栈帧的顶部即为当前栈帧的底部,所以实际不要存)
函数返回地址:保存函数调用前的指令位置。
2. 寄存器
(1)指针寄存器
ESP | 栈指针寄存器 | 存放指向系统栈最上面一个栈帧的栈顶的指针 | extended stack pointer |
EBP | 基址指针寄存器 | 存放指向系统栈最上面一个栈帧的底部的指针 | extended base pointer |
栈帧调整方法:(1)EBP入栈;(2)将ESP赋值给EBP,更新栈帧底部。
(先保存当前栈帧值,再将当前栈帧切换到新栈帧)
注意:不可分割成8位寄存器。作为通用寄存器,可存储算术逻辑运算的操作数和运算结果。
(2)指令指针寄存器
EIP | 指令指针寄存器 | 存放指向下一条等待执行的指令地址的指针 | extended instruction pointer |
此外:
IR | 指令寄存器 | 存放当前从主存储器读出的正在执行的一条指令 | Instruction Register |
当执行一条指令时,先把它从内存取到数据寄存器(DR,Data Register)中,然后再传送至IR。指令划分为操作码和地址码字段,由二进制数字组成。
在计算机工作的时候,CPU会从IP中获得关于指令的相关内存地址,然后按照正确的方式取出指令,并将指令放置到原来的指令寄存器中。
(3)数据寄存器
EAX | 累加器 | 用于乘、除等使用频率高的操作,存放函数返回值 | Accumulator |
EBX | 基地址寄存器 | 可作为存储器指针使用,用来访问存储器 | Base Register |
ECX | 计数寄存器 | 在循环和字符串操作时,要用它来控制循环次数;在位操作中,当移多位时,要用CL来指明移位的位数。 | Count Register |
EDX | 数据寄存器 | 在进行乘、除运算时,可作为默认操作数参与运算,也可用于存放I/O的端口地址 | Data Register |
32位CPU有4个32位通用寄存器EAX、EBX、ECX和EDX,具有可分可合的特性!
低16位寄存器分别为AX、BX、CX和DX,对低16位数据的存取,不会影响高16位的数据。
4个16位寄存器又可分割成8个独立的8位寄存器AH-AL、BH-BL、CH-CL、DH-DL,每个寄存器都有自己的名称,可独立存取。
(4)变址寄存器
ESI | 在内存操作指令中作为“源地址指针”使用 |
EDI | 在内存操作指令中作为“目的地址指针”使用 |
变址寄存器主要用来存放操作数的地址,用于堆栈操作和变址运算中计算操作数的有效地址。
往往有ESI、EDI存在的时候,就意味着数据的复制和移动。
32位CPU有2个32位通用寄存器ESI和EDI。其低16位对应先前CPU中的SI和DI,对低16位数据的存取,不影响高16位的数据。
(5)段寄存器
CS | 代码段寄存器 | 代码段的段值 |
DS | 数据段寄存器 | 数据段的段值 |
SS | 堆栈段寄存器 | 堆栈段的段值 |
FS/ ES/ GS | 附加段寄存器 | 附加数据段的段值 |
段寄存器是根据内存分段的管理模式而设置的。
内存单元的物理地址标准形式为段:偏移量。
(可以认为一个段是一本书的某一页,偏移量是一页的某一行)
融合变址寄存器:在很多字符串操作指令中,DS:ESI指向源串,ES:EDI指向目标串。
(6)标志寄存器
ZF/ Z-Flag | 零标志 | 可以设成0或者1 |
OF/O-Flag | 溢出标志 | 反映有符号数加减运算是否溢出。如果运算结果超过了有符号数的表示范围,则OF置1,否则置0。 |
CF/C-Flag | 进位标志 | 用于反映运算是否产生进位或借位。如果运算结果的最高位产生一个进位或借位,则CF置1,否则置0。 |
标志寄存器在32位操作系统中大小是32位的,也就是说,它可以存32个标志。
OF、CF主要和运算有关,ZF除了和运算有关还和一些状态有关。