汇编 - IA-32平台
一、基本概念
CPU 由 运算器、控制器、寄存器等组成,由内部总线相连。
在CPU中,运算器进行信息处理,寄存器进行信息存储,控制器控制各种器件进行工作。
寄存器是CPU中程序员可以用指令读写的部件。程序员通过改变各种寄存器中的内容来实现对CPU的控制。
寄存器 | 描述 |
通用 | 8个32位寄存器,用于存储正在处理的数据 |
段 | 6个16位寄存器,用于处理内存访问 |
指令指针 | 单一的32位寄存器,指向要执行的下一条指令码 |
浮点数据 | 8个80位寄存器,用于浮点数学数据 |
控制 | 5个32位寄存器,用于确定处理器的操作模式 |
调试 | 8个32位寄存器,用于在调式处理时包含信息。 |
1. 通用寄存器
8086 CPU 所有寄存都是16位的,可以存放两个字节。
AX, BX, CX, DX 四个寄存器通常用来存放一般性的数据,称为通用寄存器。
所有的寄存器可以分成高位与低位 AH && AL
问: 一个16位寄存器所能存储的数据最大值为多少?
答: 2^16-1
80386 32位寄存器 概览
寄存器 | 描述 |
EAX | 用于操作数和结果数据的累加器 |
EBX | 指向数据内存段中的数据的指针 |
ECX | 字符串和循环操作的计数器 |
EDX | I/O指针 |
EDI | 用于字符串操作的目标的数据指针 |
ESI | 用于字符串操作的源的数据指针 |
ESP | 堆栈指针 |
EBP | 堆栈数据指针 |
2. 字的概念
为了考虑兼容性,8086CPU可以一次性处理两种类型的数据:
字节: 1byte = 8bit , 可以存储在8位寄存器中。
字: 1world = 2byte = 16bit, 由高位字节与低位字节组成。
问题:使用汇编指令计算2的4次方。
2*2*2*2
mov ax, 2
add ax, ax (ax = ax + ax) 2 + 2 = 4
add ax, ax (ax = ax + ax) 4 + 4 = 8
add ax, ax (8 + 8 )
3. 物理地址的计算方法
CPU 访问内存单元时,必须向内存提供内存单元的物理地址,8086CPU在内部用段地址和偏移地址移位相加的方法形成最终的物理地址。
CPU 可以用不同的段地址和偏移地址形成同一个物理地址
32位、64位机表示:
1. 运算器一次最多可以处理32、64位的数据
2. 寄存器的最大宽度为32、64位
3. 寄存器和运算器之间的通路为32、64位
8086 CPU 相关部件逻辑结构
8086 CPU 使用两个 16位的地址 合成一个20位的物理地址
地址加法器采用 物理地址 = 段地址 * 16 + 偏移地址 的方法来合成物理地址
该公式的含义: CPU在访问内存时,用一个基础地址(段地址 * 16) 和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址。
4. 段的概念与段寄存器
在编程时可以根据需要将若干个连续的内存单元看作一个段,用段地址 * 16 定位段的起始地址(基础地址), 用偏移地址定位段中的内存单元。
8086 CPU 有四个段寄存器: CS, DS, SS, ES
CS 与 IP 指示了CPU当前要读取指令的地址。 CS 为代码段寄存器,IP为指令指针寄存器。
在8086 CPU 中, 任意时刻,设CS中内容为M, IP中内容为N, 则 CPU 将从内存 M * 16 + N 单元开始,读取并执行一条指令。
8086CPU的工作过程:
1. 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器。
2. IP = IP+所读取指令的长度,从而指向下一条指令。
3. 执行指令,转到步骤1,重复过程。
mov 指令是传送指令,不能修改段寄存器中的值。
修改CS:IP 寄存器中的值: jmp 命令 【转移指令】
jmp 2AE3:3 执行后, CS=2AE3H, IP=0003H
jmp ax 类似于 jmp IP, ax
jmp 段地址:偏移地址 // 用指令中给出的段地址修改CS,偏移地址修改IP
jmp 某一合法寄存器 // 用寄存器中的值修改IP
段寄存器 | 描述 |
CS | 代码段 |
DS | 数据段 |
SS | 堆栈段 |
ES | 附加段指针 |
FS | 附加段指针 |
GS | 附加段指针 |
CPU 根据什么将内存中的信息看作指令?
CPU将CS:IP指向的内存单元中的内容看作指令。
二、实验 查看CPU 和 内存,用机器指令和汇编指令编程
1. 环境搭建
参考
https://segmentfault.com/q/1010000000366869
https://jingyan.baidu.com/album/da1091fbd26bfe027849d68c.html?picindex=6
使用Debug程序来查看CPU 与 和内存
下载 DOSBox 与 debug.exe 程序
mount c: ~/users/Documents/
debug
使用环境 DOSBox && debug.exe
DOSBox 是一个8086的一个模拟器
debug是 DOS、Windows 提供的实模式程序调式工具。可以查看CPU各种寄存器中内容、内存的情况和机器码级跟踪程序的运行。
第三步: 使用Debug
Debug 的 R 命令查看、改变CPU寄存的内容
Debug 的 D 命令查看内存中的内容
Debug 的 E 命令改写内存中的内容
Debug 的 U 命令将内存中的机器指令翻译成汇编指令
Debug 的 T 命令执行一条机器指令
Debug 的 A 命令以汇编指令的格式在内存中写入一条机器指令。
第四步: 用 R 命令查看、改变CPU寄存器的内容
修改寄存中内容
使用R命令修改寄存器中的值 ,在 R 命令之后加寄存器的名来进行。
第五步:用Debug 的 D 命令查看内存中的内容
安全调试软件:
OllyDbg 反汇编工具
SoftICE 调式器
WinDbg
IDA Pro
内存中字的存储
字单元: 即存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成,高地址内存单元中存放字形数据的高位字节,低地址内存单元中存放字形数据的低位字节。
8086 CPU 中有一个DS奇存器,通常用来存放要访问数据的段地址。
如何把一个数据送入寄存器?
mov bx, 1000H # 将1000存放在bx寄存器 mov ds, bx # 将 bx 存放在在 ds 段寄存器中 mov al, [0] # 将 1000:0 中的数据读到al中
解释如下:
[...] 表示偏移地址。
DS段寄存器只能通过通用寄存器的值存入。
数据 -> 通用寄存器 -> 段寄存器
问题: 写几条指令,将 al 中的数据送入内存单元 10000H?
分析: 怎样将数据从寄存器送入内存单元?
mov bx, 1000H mov ds, bx mov [0], al
CPU提供栈的机制
PUSH && POP 入栈与出栈操作都是以字为单位进行。
CPU 是如何知道栈顶的位置?
8086 CPU 使用 段寄存器 SS 和 寄存器 SP,栈顶的段地址存放在SS中,偏移地址存放在SP中。
任意时刻, SS:SP 指向栈顶元素。 push 指令和pop指令执行时,cpu从SS 和 SP 中得到栈顶的地址。
push ax
第一步: SP = SP - 2, SS:SP 指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶
第二步: 将ax中的内容送入 SS:SP指向的内存单元处,SS:SP 此时指向新栈顶。
[0] 表示内存单元,它的偏移地址是0.
mov ax, [0]
上面指令将一个内存单元内容送入ax, 这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为0,段地址在ds中。
mov al, [0]
上面指令将一个内存单元内容送入al, 这个内存单元的长度为1字节(字节单元),存放一个字节,偏移地址为0,段地址在ds中。
mov ax, [bx]
将一个内存单元内容送入ax, 这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中。(ax) = ((ds) * 16 + (bx))
mov al, [bx]
将一个内存单元的内容送入al, 这个内存单元的长度为1字节(字节单元),存放一个字节,偏移地址在bx中,段地址在ds中。
使用()来表示一个寄存器或一个内存单元中的内容。
(ax) 表示ax中的内容, (al)表示al中的内容。
(20000H)表示内存20000H单元的内容()中的内存单元的地址为物理地址。
((ds) * 16 + (bx)) 表示 ds中内容为ADR1, bx中内容为 ADR2, 内存 ADR1*16+ADR2 单元的内容。