04 寄存器
1 总结#
8086 一共有 14 个寄存器,分别为:
- 通用寄存器:AX, BX, CX, DX
- 段地址寄存器:CS, DS, ES, SS
- 偏移地址寄存器:IP, SP, BP, SI, DI
- 标志寄存器:FL
80386 中除了段地址寄存器仍然是 16 位,其余均扩展到 32 位
2 通用寄存器#
用于算数、逻辑、移位运算
| 名称 | AX | BX | CX | DX | 
|---|---|---|---|---|
| 助记 | accumulator | base | count | data | 
3 段地址寄存器#
| 名称 | CS | DS | ES | SS | 
|---|---|---|---|---|
| 助记 | code segment | data segment | extra segment | stack segment | 
| 是否能用 mov赋值 | 不能 只能用 jmp, call, ret, int等间接改变 | 可以 但是 SRC必须是寄存器或变量而不是imm | 可以 但是 SRC必须是寄存器或变量而不是imm | 可以 但是 SRC必须是寄存器或变量而不是imm | 
| 可用的源寄存器 | 无 | AX, BX, CX, DX, SP, BP, SI, DI | AX, BX, CX, DX, SP, BP, SI, DI | AX, BX, CX, DX, SP, BP, SI, DI | 
| 间接赋值 | |
|---|---|
3.1 寄存器内容初始化#
[[ASMF 05 段和堆栈#3 寄存器初始化赋值和
psp段]] 有更多关于psp段的内容
| CS:IP | SS:SP | DS | ES | 
|---|---|---|---|
| 代码段段地址:首条指令偏移地址 | 堆栈段段地址:堆栈段长度 | psp 段段地址 | psp 段段地址 | 
这也就是为什么需要先对 DS 进行赋值,才能引用 data segment 中的变量
4 偏移地址寄存器#
| 名称 | IP | SP | BP | SI | DI | ( BX) | 
|---|---|---|---|---|---|---|
| 助记 | instruction pointer | stack pointer | base | |||
| 用于 []间接寻址? | 不能 | 不能 | 能 隐含 SS段 | 能 隐含 DS段 | 能 隐含 DS段 | 能 隐含 DS段 | 
| 参与算数、逻辑、移位运算? | 不能 | 不能 | 能 | 能 | 能 | 能 | 
5 FL 标志寄存器#
- FL 是 16 位的,但是其中只有每一位的布尔值有效,整体没有意义
| 15 | 14 | 13 | 12 | 
|---|---|---|---|
| x | x | x | x | 
| 0 | 0 | 0 | 0 | 
| 11 | 10 | 9 | 8 | 
| OF | DF | IF | TF | 
| 溢出标志 | 复制方向标志 | 中断标志 | 陷阱标志 | 
| 7 | 6 | 5 | 4 | 
| SF | ZF | x | AF | 
| 符号标志 | 零标志 | 0 | 辅助进位标志 | 
| 3 | 2 | 1 | 0 | 
| x | PF | x | CF | 
| 0 | 奇偶校验标志 | 1 | 进位标志 | 
5.1 Carry Flag 进位标志#
- CF Carry Flag 进位标志- 加法操作的进位、左移出来的 1,都会存到 CF
- jc如果有进位则跳转
 
- 移位操作最后移出去的一位,也会保存到 CF 中
5.1.1 与 CF 相关的指令#
| jc | jnc | adc | clc | stc | 
|---|---|---|---|---|
| 有进位跳转 | 无进位跳转 | 带进位加法 | CF = 0 | CF = 1 | 
adc ax, bx  ; ax = ax + bx + CF
| 16 位转二进制输出 | |
|---|---|
| 16 位转二进制输出 (advanced) | |
|---|---|
Attention
mov 不会改变任何标志,push pop 也不会改变任何标志
5.2 Zero Flag 零标志#
- jz/je在- ZF=1的时候跳转,本质上是相同的
- jnz/jne是相反的指令
- 使用 jz还是je,需要在对应的语境下选择
5.3 Sign Flag 符号标志#
- 每次都保存运算结果的最高位
- js符号跳转,- SF==1则跳转
- jns- SF==0则跳转
5.4 Overflow Flag 溢出标志#
有符号数加法溢出 CF 相当于无符号数加法溢出标志
- 正负相加永不溢出
- jo溢出跳转,- jno不溢出跳转
5.5 Parity Flag 奇偶校验位#
- PF=1表示结果的低八位中有偶数个 1
- jp/jpe如果 parity even 跳转
- jnp/jpo如果 pairty odd 跳转
Note
标准 ASCII 码只有 7 位,多出的第八位就是奇偶校验位;而扩展 ASCII 码没有
5.6 Auxiliary Flag 辅助进位标志#
第三位向第四位产生进位或借位
AF 和 BCD 码有关,用 16 进制表示十进制数
- daa指令 (decimal adjust for addition) 加法的十进制调整- if AF == 1 or (AL & 0Fh) > 9: AL += 6
 
5.7 Direction Flag#
5.7.1 字符串复制的方向#
| 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 
|---|---|---|---|---|---|---|
| A | B | C | D | E | 
| 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 
|---|---|---|---|---|---|---|
| A | B | A | B | C | D | E | 
要将字符串复制到以 1002 位首地址的位置,此时 源首地址<目标首地址,复制应该按反方向,地址从大到小。否则会导致还没遍历到的原始数据被覆写:
| 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 
|---|---|---|---|---|---|---|
| A | B | A | B | A | B | A | 
- 源首地址<目标首地址:反方向
- 源首地址>目标首地址:正方向
5.7.2 Example: 正方向复制#
| 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 
|---|---|---|---|---|---|---|
| A | B | C | D | E | 
细节
- 首先将 A 复制到 1000
- 然后 cx--,si++,di++
- 继续进行,直到 cx == 0
Note
- sisource index 源偏移地址
- didestination index 目标偏移地址
5.7.3 Example: 反方向复制#
| 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 
|---|---|---|---|---|---|---|
| A | B | C | D | E | 
细节
- 这里的 si, di都是从末尾开始的,只要反方向复制,一开始的传入值都应该是末地址
- 每次都是 si--, di--, cx--,其他同理
5.8 Interrupt Flag 中断标志#
- IF=1允许硬件中断,- cli置零
- IF=0禁止硬件中断,- sti置一
Note
- mov ah, 1; int 21h是函数调用,软件中断,代码在显式地用- int n的形式调用函数集的函数
什么是硬件中断
| example: add 1 to 100 | |
|---|---|
- 键盘中断:假如用户在执行上面程序时敲键盘,此时 CPU 必须暂停并处理本次键盘输入:将键盘输入编码保存到系统中的键盘缓冲区队列,int 9h会返回原来的指令
- 时钟中断:约每 55 ms 会在下一条指令前插入一个时钟中断 int 8h,将操作系统内部的一个计数器 +1
- 软件中断是显式的 explicit
- 硬件中断是隐式的 implicit
5.8.1 example: 修改函数指针时的保护操作#
这样能保证 int 9h 不会在地址改了一半的时候被硬件调用,从而产生错误
5.9 Trap Flag 陷阱标志#
- TF=1时,CPU 进入单步模式 (single-step mode),每执行一条指令,就会插入一个- int 1h中断
- int 1h是未定义的,调试器会自定义一个- int 1h的中断函数- 调试器 jmp 到被调试程序,被调试程序取得控制权
- 被调试程序进行一步,调用 int 1h返回调试器
- 调试器可以观察被调试程序的寄存器状态和当前正在执行的命令等
 
| set TF | |
|---|---|
| clear TF | |
|---|---|
如果我要做一个调试器?
- 编写断点 int 1h程序
- 翻译指令,断行并显示汇编代码
- 等待用户输入
5.9.1 int 1h 函数的定义#
那么 int 1h 的函数首地址(函数指针)为 1234h:5678h
Tip
int n 函数的指针,一定保存在 0:n*4 处,这是因为每个函数指针都需要占用 4 个字节
int 21h 一定存放在 0:84h 处
5.9.2 example: antidbg#
在执行的时候替换了
int 1h的函数指针,所以更改了单步模式下调用的程序
5.10 pushf, popf#
专门执行
FL的堆栈操作