8086 汇编语言
内存¶
段地址和偏移地址¶
逻辑地址为 段地址:偏移地址 物理地址为 10h * 段地址 + 偏移地址
引用变量或标号的偏移地址:offset 变量名或标号名 引用变量或标号的段地址:seg 变量名或标号名
直接寻址与间接寻址¶
- 直接寻址方式:[常数]
- 寄存器间接寻址方式:[寄存器]
- 寄存器相对寻址方式:[寄存器 + 常数] 或 常数[寄存器]
- 基址变址寻址方式:[寄存器 + 寄存器] 或 [寄存器][寄存器]
- 相对基址变址寻址方式:[寄存器 + 寄存器 + 常数] 或 常数[寄存器][寄存器]
- 示例:
es:10h[si][bp]
,es:[bp + si + 10h]
寄存器只允许使用 bx、bp、si、di。若使用的寄存器包含bp,则缺省地址为 ss,否则为 ds。 不可同时使用两个基址寄存器 bx、bp 或两个变址寄存器 si、si。 常数可以出现多次,可以为变量名,编译器会将其替换为该变量的偏移地址。
- 80386间接寻址一般形式:[寄存器 + 寄存器*n + 常数]
前后两个寄存器可以为 eax、ebx、ecx、edx、esi、edi、esp、ebp 中任意两个,且可以重复出现。n 必须为 1、2、4、8 其中一个。
显卡地址映射¶
80x25文本模式
- 段地址 = 0B800h
- 偏移地址 = (y * 80 + x) * 2
- 每两个字节显示一个字符,低字节为字符的ASCII码,高字节为字符的颜色
- 共有 16 色,每个字符的颜色用一个字节表示,其中低四位为文本色,高四位为背景色
- 0100
表示红色,0010
表示绿色,0001
表示蓝色
- 1100
表示亮红色,1010
表示亮绿色,1001
表示亮蓝色
- 0000
表示黑色,1111
表示白色
- 0111
表示浅灰色,1000
表示深灰色
320x200图形模式
- 段地址 = 0A000h
- 偏移地址 = y * 320 + x
- 共有 256 色,每个点的颜色用一个字节表示
- 00h-0Fh
: 等同16色
- 10h-1Fh
: 黑色渐变到白色
- 20h-2Fh
: 纯蓝色渐变到纯绿色,再渐变到纯红色
寄存器¶
通用寄存器¶
- eax、ax、ah、al
- ebx、bx、bh、bl
- ecx、cx、ch、cl
- edx、dx、dh、dl
段地址寄存器¶
- cs: 代码段寄存器,会初始化为代码段的段地址
- ss: 堆栈段寄存器,会初始化为堆栈段的段地址
- ds: 数据段寄存器,会初始化为 PSP (Program Segment Prefix) 的段地址
- es: 附加段寄存器,会初始化为 PSP (Program Segment Prefix) 的段地址
- cs 不能用 mov 指令赋值,ds、es、ss 可以用 mov 指令赋值,但操作数必须为寄存器或变量
偏移地址寄存器¶
- ip: 指令指针寄存器,会初始化为首条指令的偏移地址
- sp: 堆栈指针寄存器,会初始化为堆栈段的长度
- bp: 基址指针寄存器,可用于存放内存中某些数据的偏移地址
- si: 源变址寄存器,可用于存放内存中源数据区的偏移地址
- di: 目的变址寄存器,可用于存放内存中目的数据区的偏移地址
标志寄存器¶
FL 寄存器有状态标志共 6 个,包括 CF、OF、ZF、SF、PF、AF;控制标志共 3 个,包括 DF、IF、TF 第 0、2、4、6、7、8、9、10、11 位分别为 CF、PF、AF、ZF、SF、TF、IF、DF、OF
- CF: 进位标志,当发生以下情况时,CF 置一
- 两数相加产生进位
- 两数相减产生借位
- 两数相乘的乘积宽度超过被乘数宽度
- 移位指令最后移出的那一位为一
- OF: 溢出标志,当发生以下情况时,CF 置一
- 两个正数相加变负数
- 两个负数相加变正数
- 两数相乘的乘积宽度超过被乘数宽度
- 仅移动一位且移动前的最高位不等于移动后的最高位
- ZF: 零标志,当运算结果为零时,ZF 置一
- SF: 符号标志,当运算结果为负时,SF 置一,相当于运算结果的最高位
- PF: 奇偶标志,当运算结果的低 8 位中有偶数个 1 时,PF 置一
-
AF: 辅助进位标志,当低 4 位向高 4 位产生进位或借位时,AF 置一
-
DF: 方向标志
- 当 DF = 0 时,字符串操作指令按正方向进行,每次操作后地址加一
- 当 DF = 1 时,字符串操作指令按反方向进行,每次操作后地址减一
- IF: 中断允许标志
- 当 IF = 0 时,禁止硬件中断
- 当 IF = 1 时,允许硬件中断
- TF: 跟踪标志
- 当 TF = 0 时,CPU 进入常规状态
- 当 TF = 1 时,CPU 进入单步状态,每执行一条指令就自动执行一次 int 01h
端口¶
- CPU 通过向端口地址读取和写入数据来控制 I/O 设备
- 端口地址独立于内存地址,其取值范围为 0000h-0FFFFh
- 0000h-00FFh: 常用于系统板上的设备,如时钟、定时器、键盘接口、中断控制器和DMA控制器等
- 0100h-03FFh: 常用于磁盘控制器、显示器、串行口和并行口等接口
-
0400h-0FFFFh: 常用于用户应用、主板功能和PCI总线
-
读取端口指令:in al/ax/eax, imm8/dx
- 写入端口指令:out imm8/dx, al/ax/eax
- 0-255 号端口可以直接寻址,256-65535 号端口必须使用 dx 间接寻址
CMOS
- out 70h, al
: 向 70h 端口发送信号,表示要读写某号内存单元
- in al, 71h
: 从 71h 端口读取之前访问的内存单元,获得 BCD 码格式的时间信息
- 每个时间信息都用一个字节表示,其中低四位为个位,高四位为十位,即 个位 = 低四位 + 30h
, 十位 = 高四位 + 30h
- 0h 为秒,2h 为分,4h 为时,6h 为星期(01h-07h 依次表示周日、周一到周六),7h 为日,8h 为月,9h 为年的后两位
键盘
- in al, 60h
: 从 60h 号端口获取按键产生的按键编码
- 键盘按下按键发送的按键编码称为为扫描码,每个按键在按下和释放的时候都会产生不同的扫描码,分别称为通码和断码
- 扫描码长度为一个字节,通码的最高位为 0,断码的最高位为 1,即 断码 = 通码 + 80h
- 如果是特殊键,则会在扫描码的前缀中包含一个额外的字节 E0h,变为扩展码
指令¶
算术指令¶
加法指令¶
add a, b
加法,a = a + b
adc a, b
带进位加法,a = a + b + cf
inc a
自加1,不影响 CF 标志位,影响 ZF 标志位
减法指令¶
sub a, b
减法,a = a - b
sbb a, b
带借位减法,a = a - b - cf
-
dec a
自减1,不影响 CF 标志位,影响 ZF 标志位 -
neg a
取反,会影响CF、ZF、SF等标志位,(neg ax) = (not ax) + 1
cmp a, b
做减法运算,仅影响标志位状态- 若
a == b
则ZF = 1
,若a≠b
则ZF = 0
- 若
a < b
则CF = 1
,若a≥b
则CF = 0
- 若
乘法指令¶
mul/imul a
非符号数/有符号数乘法- 当操作数为8位时,
ax = al * a
- 当操作数为16位时,
dx:ax = ax * a
- 当操作数为32位时,
edx:eax = eax * a
- 当操作数为8位时,
imul a, b
32位模式下可用,a = a * b
imul reg16, r/m16
imul reg32, r/m32
imul reg64, r/m64
imul a, b, c
32位模式下可用,a = b * c
imul reg16, r/m16, imm8
imul reg32, r/m32, imm8
imul reg64, r/m64, imm8
imul reg16, r/m16, imm16
imul reg32, r/m32, imm32
imul reg64, r/m64, imm32
除法指令¶
div/idiv a
非符号数/有符号数除法- 当操作数为8位时,
ax % a
,商在al
中,余数在ah
中 - 当操作数为16位时,
dx:ax % a
,商在ax
中,余数在dx
中 - 当操作数为32位时,
edx:eax % a
,商在eax
中,余数在edx
中
- 当操作数为8位时,
浮点数运算指令¶
- FPU共有8个浮点数据寄存器,将指针所处的位置视为逻辑编号0,每压入一个浮点数,指针所处的物理编号减一,其具体值保存在浮点状态寄存器的第11位至第13位
dd
为 32 位小数dq
为 64 位小数-
dt
为 80 位小数 -
fld m32/64/80fp|st(i)
将浮点数压入FPU的寄存器堆栈中 fild m16/32/64int
将整数压入FPU的寄存器堆栈中ftp m32/64fp|st(i)
将st(0)
拷贝到指定位置-
fstp m32/64fp|st(i)
将st(0)
拷贝到指定位置,并将其弹出堆栈 -
fadd
浮点数加法fadd m32/64fp
:st(0) = st(0) + fp
fadd st(0), st(i)
:st(0) = st(0) + st(i)
fadd st(i), st(0)
:st(i) = st(i) + st(0)
fsub
浮点数减法,同上fmul
浮点数乘法,同上fdiv
浮点数除法,同上
逻辑运算指令¶
and a,b
:a = a & b
or a,b
:a = a | b
xor a,b
:a = a ^ b
not a
:a = ~a
test a,b
对两个操作数进行逻辑与运算,并根据运算结果设置相关的标志位,不改变两个操作数- SF置结果的最高位
- 若结果为0,则 ZF 置一
- 若结果低8位中1的个数是偶数,则 PF 置一
- CF 清零
- OF 清零
移位指令¶
shl a, b
逻辑左移指令,低位补0,高位进CFshr a, b
逻辑右移指令,低位进CF,高位补0sal a, b
算数左移指令,低位补0,高位进CF-
sar a, b
算数右移指令,低位进CF,高位不变- 以上四条指令,在移动一位时,OF会受到影响
- 除SAR以外,
OF=最高位和次高位的异或
,即正负号改变时,OF = 1
- 若为SAR,
OF = 0
-
rol a, b
循环左移,高位到低位并送CF ror a, b
循环右移,低位到高位并送CFrcl a, b
带进位循环左移,低位补CF,高位进CFrcr a, b
带进位循环右移,低位进CF,高位补CF
十进制调整指令¶
daa
加法的十进制调整,在 al 被做加法后将结果 al 调整为 BCD 码- 若
AF == 1
或al & 0Fh > 09h
,则al += 6h
- 若
CF == 1
或al & F0h > 90h
,则al += 60h
- 若
-
das
减法的十进制调整,在 al 被做减法后将结果 al 调整为 BCD- 若
AF == 1
或al & 0Fh > 09h
,则al -= 6h
- 若
CF == 1
或al & F0h > 90h
,则al -= 60h
- 若
-
aaa
加法的 ASCII 调整,在 al 被做加法后连带 ah 一起调整 ax 为非压缩 BCD 码- 若
AF == 1
或al & 0Fh > 09h
,则ax += 106h
AF = 1
CF = 1
- 若
aas
减法的 ASCII 调整,在 al 被做减法后连带 ah 一起调整 ax 为非压缩 BCD 码- 若
AF == 1
或al & 0Fh > 09h
,则ax -= 06h, ah -= 1, al &= 0Fh
AF = 1
CF = 1
- 若
传送指令¶
通用传送指令¶
mov dest, src
将 src 赋值给 dest,两个操作数不能全为内存单元。遵循小端规则,在读取时会先读取低位,在写入时会先写入低位push a
将 a 压入栈顶,先送高位,再送低位- 操作数只能是 16 位或 32 位的,sp 会减去相应的值
pop a
从栈顶弹出数据给 a,先送低位,再送高位- 操作数只能是 16 位或 32 位的,sp 会加上相应的值
-
xchg a, b
交换 a 和 b 的值,不能全为内存单元,也不能包含段寄存器 -
pusha
将所有通用寄存器压入堆栈,顺序为 ax cx dx bx sp bp si di(80186) popa
将所有通用寄存器弹出堆栈,顺序为 di si bp sp bx dx cx ax(80186)pushad
将所有32位通用寄存器压入堆栈,顺序为 eax ecx edx ebx esp ebp esi edi(80386)popad
将所有32位通用寄存器弹出堆栈,顺序为 edi esi ebp esp ebx edx ecx eax(80386)
地址传送指令¶
lea r16, mem
装入有效地址,r16 = offset mem
。可用于一句完成简单运算和赋值lds r16/32, m16:16/m16:32
装入远指针,ds:r16 = m16:16
或ds:r32 = m16:32
les r16/32, m16:16/m16:32
装入远指针,es:r16 = m16:16
或es:r32 = m16:32
lss r16/32, m16:16/m16:32
装入远指针,ss:r16 = m16:16
或ss:r32 = m16:32
lfs r16/32, m16:16/m16:32
装入远指针,fs:r16 = m16:16
或fs:r32 = m16:32
lgs r16/32, m16:16/m16:32
装入远指针,gs:r16 = m16:16
或gs:r32 = m16:32
标志寄存器传送指令¶
pushf
将 FL 压入堆栈popf
将 FL 弹出堆栈pushfd
将 EFL 压入堆栈popfd
将 EFL 弹出堆栈lahf
将 FL 存入 ahsahf
将 ah 存入 FL
转移指令¶
无条件转移指令¶
jmp a:b
用 a 修改 CS,用 b 修改 IPjmp a
用 a 修改 IP-
jmp label
用标号的CS和IP修改CS和IPCS
为代码段寄存器,IP
为指令指针寄存器- 8086机中,任意时刻,CPU将
CS:IP
指向的内容当作指令执行 - 读取一条指令后,
IP
会加上所读取指令的长度 - 不可以使用常数
-
短跳转(Short Jmp): 只能跳转到256字节的范围内,对应机器码为 EB
- 近跳转(Near Jmp): 可跳至同一段范围内的地址,对应机器码为 E9
- 远跳转(Far Jmp): 可跳至任意地址,对应机器码为 EA
条件转移指令¶
je/jz label
相等/结果为零,条件为ZF == 1
jne/jnz label
不相等/结果不为零,条件为ZF == 0
js label
结果为负,条件为SF == 1
-
jns label
结果非负,条件为SF == 0
-
jg label
大于,条件为ZF == 0 and SF == OF
jge label
大于等于,条件为SF == OF
jl label
小于,条件为SF != OF
-
jle label
小于等于,条件为ZF == 1 or SF != OF
-
jb/jnae/jc label
小于/不大于等于/有进位,条件为CF == 1
jnb/jae/jnc label
不小于/大于等于/无进位,条件为CF == 0
jna/jbe label
不大于/小于等于,条件为CF == 1 or ZF == 1
ja/jnbe label
大于/不小于等于,条件为CF == 0 and ZF == 0
循环指令¶
loop label
将 cx 减一后,若 cx 不为零则跳转loopz label
将 cx 减一后,若 cx 不为零且之前的运算结果为零则跳转loopnz label
将 cx 减一后,若 cx 不为零且之前的运算结果不为零则跳转loopne label
将 cx 减一后,若 cx 不为零且之前的运算结果不为零则跳转
子程序调用与返回指令¶
call label
近调用,将当前的 IP 压入栈中,然后转移call cs:ip
远调用,将当前的 CS 和 IP 压入栈中,然后转移ret
用栈中的数据,修改 IP 的内容,从而实现近转移retf
用栈中的数据,修改 CS 和 IP 中的内容,从而实现远转移
中断指令¶
int n
利用中断类型码,引发中断过程,等效于IF = 0, TF = 0, pushf, push cs, push ip, jmp (n * 4 + 2):(n * 4)
iret
中断返回,会从栈中弹出数据到 CS、IP 和 EFLAGS 寄存器中,等效于pop ip, pop cs, popf
字符串操作指令¶
rep
重复后面的字符串指令 cx 次repe/repz
当比较相等时继续重复repne/repnz
当比较不相等时继续重复
字符串传送指令¶
movsb
从 ds:[si] 传送一个字节到 es:[di],并以字节为单位移动 si、dimovsw
从 ds:[si] 传送一个字到 es:[di],并以字为单位移动 si、dimovsd
从 ds:[si] 传送一个双字到 es:[di],并以双字为单位移动 si、dimovsq
从 ds:[si] 传送一个四字到 es:[di],并以四字为单位移动 si、di
字符串比较命令¶
cmpsb
比较 ds:[si] 和 es:[di],cmpsw
比较 ds:[si] 和 es:[di],cmpsd
比较 ds:[si] 和 es:[di],cmpsq
比较 ds:[si] 和 es:[di],
字符串扫描命令¶
scasb
比较 al 和 es:[di],并以字节为单位移动 discasw
比较 ax 和 es:[di],并以字为单位移动 discasd
比较 eax 和 es:[di],并以双字为单位移动 di
字符串存入命令¶
stosb
将 al 存入 es:[di],并以字节为单位移动 distosw
将 ax 存入 es:[di],并以字为单位移动 distosd
将 eax 存入 es:[di],并以双字为单位移动 di
字符串读取命令¶
lodsb
从 es:[di] 读取一个字节至 al,并以字节为单位移动 dilodsw
从 es:[di] 读取一个字至 ax,并以字为单位移动 dilodsd
从 es:[di] 读取一个双字至 eax,并以双字为单位移动 di
转换指令¶
xlat
完成一个字节的查表转换,al = ds:[bx + al]
cbw
将 al 符号扩展为 ax-
cwd
将 ax 符号扩展为 dx:ax -
cwde
将 ax 符号扩展为 eax(80386) cdq
将 eax 符号扩展为 edx:eax(80386)movsx reg16/32,reg8/reg16/mem8/mem16
符号扩展传送,将8位符号扩展为16位或32位,或者16位符号扩展为32位(80386)movzx reg16/32,reg8/reg16/mem8/mem16
零扩展传送,将8位零扩展为16位或32位,或者16位零扩展为32位(80386)
控制指令¶
cld
:DF = 0
,自动加一std
:DF = 1
,自动减一cli
:IF = 0
,禁止中断发生, 即屏蔽中断sti
:IF = 1
,允许中断产生clc
:CF = 0
,无进位/借位stc
:CF = 1
,有进位/借位nop
无操作,机器码为90h,占用一个字节空间
伪指令¶
数据定义伪指令¶
[变量名] db/dw/dd/dq/dt 表达式 [注释]
db
: 字节型变量,每个变量分配 1 个内存单元dw
: 字型变量,每个变量分配 2 个内存单元dd
: 双字型变量,每个变量分配 4 个内存单元dq
: 四字型变量,每个变量分配 8 个内存单元dt
: 十字型变量,每个变量分配 10 个内存单元
n dup(表达式)
将表达式重复n
次
运算符伪指令¶
seg x
获得变量或标号的段地址,编译时会会把seg a
编译成seg a - 首段段地址
offset x
获得变量或标号的偏移地址
属性说明伪指令¶
类型 ptr 地址表达式
确定地址表达式的存储单元为指定的类型,即用在地址表达式之前,用于指定或临时改变变量名和标号名的类型byte ptr
word ptr
dword ptr
qword ptr
tbyte ptr
段定义与段分配伪指令¶
.386
表示会使用32位寄存器x segment
说明一个段开始x segment use16
表示使用16位地址x segment use32
表示使用32位地址x ends
说明一个段结束assume reg:seg,[reg:seg,...]
建立段和段寄存器之间的联系,放在代码段内和段定义语句之后
程序模块命名与程序结束伪指令¶
end [起始地址标号]
表示程序结束,后为主模块起始地址,汇编时会将起始地址标号的段地址和偏移地址赋给 CS 和 IP
中断¶
- CPU在收到中断信息后,根据中断信息中的中断类型码,去中断向量表找到相应中断例程的地址,而后跳转执行。
- 中断向量表存放着256个中断源所对应的中断处理程序的入口地址,每个地址占四个内存单元,低字放偏移地址,高字放段地址,即
IP = n * 4
,CS = n * 4 + 2
。 - 当中断结束后,通常需要发送 EOI (End of Interrupt) 信号给中断控制器,弹出之前压入堆栈的寄存器中的值,最后再用
iret
恢复CS、IP和EFLAGS:
; 保存和修改中断向量地址
mov ax, 0
mov es, ax
mov ax, es:[9 * 4]
mov [old_9h], ax
mov ax, es:[9 * 4 + 2]
mov [old_9h + 2], ax
mov word ptr es:[9 * 4], offset int_9h
mov word ptr es:[9 * 4 + 2], code
; 退出中断
mov al, 20h
out 20h, al
pop ds
pop cx
pop bx
pop ax
iret
int 21h¶
int 21h
是 DOS 提供的中断例程,其中包含了 DOS 提供给程序员在编程时调用的子程序
ah = 01h
键盘输入并回显,al = 输入字符
ah = 02h
显示输出,dl = 输出字符
ah = 09h
显示字符串,ds:dx = 串地址
,$
结束字符串ah = 0Ah
键盘输入到缓冲区,按回车键结束输入,ds:dx = 缓冲区首地址, (ds:dx) = 缓冲区最大字符数
,(ds:dx+1) = 实际输入的字符数
,ds:dx+2
起是输入的字符串ah = 4Ch
带返回码结束,al = 返回码
int 16h¶
int 16h
是BIOS提供的键盘服务,提供了读取和控制键盘输入的功能
- ah = 00h
读取键盘输入
- 若键盘缓存区为空,则等待键盘输入
- 若键盘缓存区非空,则从键盘缓冲区读取一个键盘输入并将其删除,使 al = 输入的ASCII码
,ah = 输入的扫描码
- ah = 01h
检测键盘缓存区是否为空,若为空则 ZF = 1
,否则 ZF = 0
- ah = 02h
检测键盘各特殊功能键的状态,放入 al 寄存器中,若对应位为 1,则表示该键为按下状态,否则为断开状态
int 10h¶
int 10h
是BIOS提供的视频服务,提供了屏幕显示、光标操作、颜色设置等与视频相关的功能。
ah = 00h
设置显示模式al = 03h
文本模式,文本分辨率80x25,像素分辨率640x200,16色,显卡段地址B800h
al = 12h
图形模式,文本分辨率80x30,像素分辨率640x480,16色,显卡段地址A000h
al = 13h
图形模式,文本分辨率40x25,像素分辨率320x200,256色,显卡段地址A000h
ah = 01h
设置光标形状ah = 02h
设置光标位置ah = 03h
读取光标信息
其他¶
Turbo Debugger 常用快捷键¶
F2
设置断点F4
运行至光标F7
单步跟踪F8
单步跳过F9
运行程序Ctrl + F2
重载程序-
Alt + x
退出调试器 -
CPU
Ctrl + G
跳转至指定地址Ctrl + O
跳转回程序执行处Ctrl + P
跳转回之前地址
- Dump
Ctrl + N
跳转至下一行Ctrl + G
跳转至指定地址Ctrl + P
跳转回之前地址Ctrl + D
切换显示方式
- Register
Ctrl + I
寄存器加1Ctrl + D
寄存器减1Ctrl + Z
寄存器回0Ctrl + C
寄存器变为指定值Ctrl + R
切换16/32位寄存器
DosBox 调试指令¶
-t
单步执行-p
以汇编指令的格式在内存中写入一条机器指令-
-q
退出 -
-r
查看所有寄存器的值 -
-r reg
修改寄存器中的值 -
-d
从当前或之前查看位置开始查看内存 -d addr
从指定位置开始查看内存-
-d addr n
从指定位置开始查看内存,到指定偏移地址结束 -
-e addr
从指定位置开始修改内存中的内容,按空格进入下一个,按ENTER结束修改 -
-e addr a b c ...
从指定位置开始,用输入内容修改内存中的内容 -
-a
以汇编指令的格式,从当前位置开始写入机器指令 -a addr
以汇编指令的格式,从指定位置开始写入机器指令-u addr
将内存中的机器指令翻译成汇编指令
键盘按键编码¶
Up
:4800h
Left
:4B00h
Right
:4D00h
-
Down
:5000h
-
Esc
:011Bh
Backspace
:0E08h
Tab
:0F09h
Enter
:1C0Dh
Space
:3920h