x86 汇编程序设计
第零章 DOSBOX 使用
运行程序
mount c c:\
c:
cd masm\bin
masm test.asm
link test.obj
test
exit
DEBUG
debug test.exe
更多内容见书 pdf 的第 113 页
a (Assemble) 逐行汇编 a [address]
- c (Compare) 比较两内存块 c range address
- d (Dump) 内存16进制显示 d [address]或 d [range]
- e (Enter) 修改内存字节 e address [list]
- f (fin) 预置一段内存 f range list
- g (Go) 执行程序 g [=address][address…]
- h (Hexavithmetic) 制算术运算 h value value
- i (Input) 从指定端口地址输入 i pataddress
- l (Load) 读盘 l [address [driver seetor>
- m (Move) 内存块传送 m range address
- n (Name) 置文件名 n filespec [filespec…]
- o (Output) 从指定端口地址输出 o portadress byte
- p 执行循环、重复的字符串指令、软件中断或子例程。
- q (Quit) 结束 q
- r (Register) 显示和修改寄存器 r [register name]
- s (Search) 查找字节串 s range list
- t (Trace) 跟踪执行 t [=address] [value]
- u (Unassemble) 反汇编 u [address ]或range
- w (Write) 存盘 w [address[driver sector secnum>
第一章 基础知识
数值及数制概念
- D–十进制数
- B–二进制数
- H–十六进制数
输入输出及数值代码转换过程
主要 ASCII 码
0-9,A-Z,a-z,空格,回车
符号 | ASCII 码值 |
---|---|
A-Z | 41H-5AH |
a-z | 61H-7AH |
0-9 | 30H-39H |
* | 2AH |
$ | 24H |
20H | |
换行 | 0AH |
回车 | 0DH |
\0 |
00H |
DOS(Windows) 文本换行 | 0DH, 0AH |
Unix 文本换行 | 0AH |
双字节双高位为1的汉字标点、符号和汉字的国标(GB2312-80)编码。分区、位分别编码共9个汉字符号区(01-09),72个汉字区(16-97)。 每个区内有94个字,共6763(6768,40区空了5个),也称“区位码”。字符区从A1开始,汉字区从B0开始,区内位编码从A1开始
第二章 IBM-PC 计算机组织
寻址范围 & 字长
8088
8088 是一个准 16 位的微处理器,即它的内部结构,如寄存器、寻址和执行机构,都是16 位的,而外部的数据总线是 8 位的,因而它在一个总线周期内只能访问一个 8 位字节而不是一个 16 位的字。
Intel 8088 CPU 内部共有 14 个 16 位寄存器,共有 20 根地址线,可直接寻址的空间为 20 = 1MB。用于访问外部设备的 I/O 端口地址空间为 64KB。
8086
8086 的内部结构和外部总线都是 16 位的。
寄存器
通用寄存器
共有 8 个通用寄存器,其中 4 个(AX, BX, CX, DX)为数据寄存器,4 个(SP, BP, SI, DI)为地址或指针寄存器
数据寄存器 (Data Register)
AX, BX, CX, DX 这 4 个寄存器被称为数据寄存器 (data register),其本意是在汇编语言程序中,主要用它们来寄存运算中的数据。
可将 AX, BX, CX, DX 分别拆成 AH, AL, BH, HL, CH, CL, DH, DL 8 个 8 位寄存器,其中 AH, BH, CH, DH 分别由 AX, BX, CX, DX 的高 8 位组成,AL, BL, CL, DL 分别由 AX, BX, CX, DX 的低 8 位组成。
- AX 是累加器 (accumulator),算数运算时,主要用 AX 来存放操作数和运算结果
- 在乘除法指令中强制使用 AX
- 在输入 / 输出指令中使用 AX 存放数据
- 作为通用寄存器
- BX 一般用作通用寄存器,但在间接寻址时,也用它来存放基地址 (base address)
- CX 常在循环指令及串循环指令中用作计数器,移位指令则使用 CL 位计数器。每次循环时它会自动 -1 或 -2
- DX 一般用作通用寄存器。
- 在乘除法进行 32 位双字运算时,它是 AX 的扩展(因为 AX 只有 16 位)
- 在输入指令中,用它存放 I/O 端口的地址,从而实现间接寻址。
指针及变址寄存器 (Pointer / Index Register)
SP, BP, SI, DI 这 4 个 16 位寄存器称为指针 (pointer) 及变址 (index) 寄存器,因为主要用来存放地址,而地址(包括段地址及偏移地址)都是 16 位的,所以这 4 个寄存器不能拆分成高 8 位及低 8 位的 8 位寄存器,只能当作整体来使用。
- SP (stack pointer) 的作用是指示堆栈栈顶的偏移地址。由于堆栈的操作(压入、弹出)会自动地修改 SP 值,因此在实际使用中,应该只读而不应修改它(虽然实际上可以修改)
- BP (base pointer) 也是一种基地址寄存器(类似于 BX),所不同的是它相对于堆栈段而不是数据段。在实际应用中,可用 BP 作为基址寄存器往子程序中传递参数。
- 源变址寄存器 SI(source index) 和目的变址寄存器 DI(destination index) 通常用作地址指针。
段寄存器
“段” (segment) 是内存空间的一片区域。
- 代码段寄存器 CS (code segment) 存放的是代码段的段地址
- 数据段寄存器 DS (data segment) 存入的是数据段的段地址。一般程序执行时都有一个或多个数据段。使用不同数据段时,必须相应地修改 DS
- 堆栈段寄存器 SS (stack segment) 存放的是堆栈段的段地址。在编程时需要给出合适大小的堆栈段(若不设置,则使用操作系统的堆栈段),只要使用 PUSH,POP 指令,就会使用由 SS 和 SP 确定的堆栈区。
- 附加段寄存器 ES (extra segment) 存放的是附加数据段的段地址。
指令指针
指令指针寄存器 IP(instruction pointer) 是一个专用的 16 位寄存器,表示当前要执行的指令在代码段中的偏移地址。
只有转移指令、子程序调用及返回指令、中断调用及返回指令才能修改 IP(有时还会修改 CS)。
标志寄存器
Intel 8086/8088 CPU 共有 9 个 1 位的标志寄存器(或标志位),用于表示程序运行时的状态或实行一些特殊控制。这 9 个标志寄存器被组合在了一起,放入一个 16 位的程序状态字寄存器 PSW(program status word) 中,如图
- 进位标志 CF(carry flag),当进行算术运算时,如果最高位产生了进位,则 CF=1,否则 CF=0。CF 还用于移位指令、用于保存从最高位(左移时)或最低位(右移时)移出的代码。
- 奇偶标志 PF(parity flag),用来为及其中传送数据时提供奇偶校验值。它只对操作结果的低 8 位进行校验。如果“1”的个数位偶数,则 PF=1;否则 PF=0。
- 辅助进位标志 AF(auxiliary carry flag),进行算数运算时,如果低 4 位产生了进位,则 AF=1,否则 AF=0。此标志用于十进制运算时的调整。
- 零标志 ZF(zero flag),如果运算结果为 0,则 ZF=1,否则 ZF=0
- 符号标志 SF(sign flag),用于记录运算结果的符号,结果为负时 SF=1,否则 SF=0。所以 SF 与运算结果的最高位是一样的。
- 陷阱标志 TF(trap flag),当 TF=1 时,在执行完一条指令后就会产生单步中断,然后由单步终端处理程序把 TF 置为 0。TF 标志用于调试。
- 中断标志 IF(interrupt flag),当 IF=1 时,允许相应可屏蔽中断,否则关闭中断。
- 方向标志 DF(direction flag),DF 用于控制串操作指令地址的增减。如果 DF=1,则每次串操作后使变址寄存器 SI 和 DI -1 或 -2;如果 DF=0,则每次串操作后使变址寄存器 SI 和 DI +1 或 +2。
- 溢出标志 OF(overflow flag),在算数运算时,如果操作结果超过了机器用补码表示范围时,OF=1,否则 OF=0。
内存组织
内存地址与字节、字的存放
PC 机的每个基本内存单元为 8 位组成的一个字节 (byte)
地址从 0 开始编号,顺序地每次加 1 ,范围为 00000H
- FFFFFH
- 字的地址是第一个字节的地址
- 字在内存中,低字节在前,高字节在后
我们称字 1234H 在内存中的地址为 1FFFFH,而不是 20000H,尽管 20000H 是偶数。特别是,将1个字放入 1FFFFH 处时,它占用的是 1FFFFH 及 20000H 处的两个连续地址,而不是 20000H 及 20001H 处的两个连续地址。
内存地址的分段
在 Intel 8086/8088 的分段方法中,段地址和段内地址都用 16 位来表示。
- 段地址负责高 16 位
- 段内地址负责低 16 位
它们相交的 12 位是重叠的。实际求物理地址时,要计算重叠部分。
物理地址和逻辑地址
- 物理地址:与内存单元一一对应的用 20 位二进制数(或 5 位十六进制数)表示的地址,其地址范围为
00000H ~ FFFFFH
- 逻辑地址:用段地址和偏移地址来表示内存单元地址
将段地址乘以16(左移 4位)再加上偏移地址,即可得到物理地址 $段地址 \times 16D(或 16H) + 偏移地址 = 物理地址$ 段地址中增加 1,内存空间就增加 16D (10H)。我们常把 16 字节称为 1 节 (paragraph)
实际内存分配法
- 代码段
- 数据段
- 堆栈段
堆栈
堆栈的位置
堆栈由软件设置,一端固定,一端浮动。信息的存取在浮动的一端进行。堆栈的位置和大小由 SS
和 SP
寄存器确定。
- 堆栈的栈底由
SP
值确定,它是堆栈中最大单元处的地址加一。随着不断的压栈操作SP
的值会减小。 - 栈顶在堆栈区外
堆栈操作
堆栈操作遵守先进后出的原则。
PUSH
PUSH AX
的执行过程
SP
<=SP
- 2- (
SP
) <=AX
POP
AX
<= (SP
)SP
<=SP
+ 2
堆栈图
- CALL (段内)
- CALL (段间)
第三章
指令格式
汇编语言格式
基本格式:
机器指令格式
一条指令的长度为 1 ~ 7 字节
- 操作码字节
- 寻址方式字节
- 段超越字节
指令优先级
(11) SHORT
寻址方式
与数据有关的寻址方式
1. 立即寻址
定义
立即寻址指的是指令所需操作数直接包含在指令代码中,它通常是一个常量或常数(统称为立即数)
- 只能出现在源操作数的位置
- 目的操作数可以是寄存器或内存操作数
- 立即数为常数时,可以直接写在指令中
- 立即数为常量时,需要在指令前面用
EQU
位指令定义,或通过计算得到 - 注意源操作数和目的操作数的位数需匹配
定义操作数:
- 常量:EQU,不存储在内存中,值不能改变
- 字节:DB
- 字:DW
- 双字:DD
正例
VALUE EQU 512
MOV AL, 05H
MOV AL, 00000101B
MOV AX, 512
MOV AX, VALUE
反例
MOV AL, 100H ; 100H 超出字节范围
MOV BL, VALUE ; VALUE 超出字节范围
MOV AX, 10000H ; 10000H 超出 16 位范围
2. 寄存器寻址
定义
寄存器寻址指的是指令中所需的操作数是 CPU 的某个寄存器。
- 存取这类指令完全在 CPU 内部进行,无需动用总线,访问内存,因此执行速度较快
- 8 位操作数:AH, AL, BH, BL, CH, CL, DH, DL
- 16 位操作数:AX, BX, CX, DX, SP, BP, SI, DI, CS, DS, SS, ES
正例
MOV AX, BX
MOV AX, 1234H
ADD X, AX
PUSH DS
PUSHF ; PSW 作为源操作数,是寄存器寻址方式
STD ; 设置 DF = 1,因而是寄存器寻址方式
3. 直接寻址
定义
直接寻址是指操作数的偏移地址直接在指令中给出的寻址方式
- 如果没有段超越,一般的取操作数都是相对于数据段 DS 的,所以 DS 可以省略
正例
MOV AX, [2000H]
x DW ? ; 定义一个字变量,未指定初始值
c DB 'A' ; 定义一个字节变量,制定初值
MOV AX, x
MOV AL, c ; 汇编器会算出 x, c 的偏移值,将它替换掉原来的 x 或 c
MOV AX, x+1 ; 将 x+1 单元的字送入 AX 中
反例
4. 寄存器间接寻址
定义
寄存器间接寻址方式指的是操作数的有效地址 EA 不是位于指令中,而是位于基址寄存器 BX, BP 或变址寄存器 SI, DI 中。因为地址值未在指令中直接给出,而是通过一个寄存器来指明,因而称为间接寻址。
正例
MOV AX, [BX] ; 内存操作数的偏移地址位于 BX 中,在 DS 段内
MOV AH, [BP] ; 内存操作数的偏移地址位于 BP 中,在 SS 段内 **注意:是 SS 而不是 DS**
MOV CX, [SI] ; 内存操作数的偏移地址位于 SI 中,在 DS 段内
MOV DL, [DI] ; 内存操作数的偏移地址位于 DI 中,在 DS 段内
MOV AX, DS:[BX]
MOV BH, SS:[SP]
MOV CX, DS:[SI]
MOV DL, DS:[DI]
反例
MOV AX, [DX] ; DX 不能作为间接寻址寄存器
MOV SL, [BL] ; 只能用 BX,不能用 BL 作为间接寻址寄存器,因为偏移值是 16 位
5. 寄存器相对寻址
定义
寄存器相对寻址指的是操作数的有效地址 EA 是一个基址寄存器或变址寄存器的内容和指令中指定的 8 位和 16 位位移量之和
$EA = 间址寄存器的值 + 8 位或 16 位常量$
先寄存器间接寻址,然后相对寻址
正例
MOV AX, [SI+10H] ; SI 的值加 10H 形成偏移地址,在 DS 段内寻址
MOV AX, 10H[SI] ; 等价于上一条指令
MOV AX, ARRAY[SI]
MOV TABLE[DI], AL
MOV TABLE[SI+1], AL
6. 基址变址寻址方式
定义
基址变址寻址方式指的是,操作数的有效地址 EA 等于一个基址寄存器和一个变址寄存器的内容之和。
正例
MOV AX, [BX][SI]
MOV AX, [BX+SI]
MOV ES:[BX+SI], AL
MOV [BP+SI], AX
MOV AX, [BX+SI+200]
MOV ARRAY[BP+SI], AX
反例
MOV [BX+CS], AX ; CX 不能作为变址寄存器
MOV [BX+BP], AX ; BP 只能用于基址寄存器,不能用作变址寄存器
MOV [BX+DI], ARRAY ; 如果 ARRAY 为变量,则源和目的操作数都在内存中,这在语法中不合法
与转移地址有关的寻址方式
标号与过程名
标号
l1: MOV AX, ARRAY[SI] ; l1 为一个标号,后面跟一个冒号
过程名
p1 PROC near
...
RET
p1 ENDP
p2 PROC far
...
RET
p2 ENDP
1. 段内直接寻址
段内直接寻址指的是要转向指令实际的有效地址是当前 IP 寄存器的内容和指令中指定的 8 位或 16 位位移量之和。
JMP l1
CALL p1
JMP SHORT l1 ; l1 与当前 IP 的位移量是一个 8 位的值
JMP NEAR PTR l1 ; l1 与当前 IP 的位移量是一个 16 位的值
2. 段内间接寻址
段内间接寻址是指有效地址是一个寄存器或一个存储单元的内容。
- 这个寄存器可以是 AX, BX, CX, DX, SI, DI, BP, SP 中的任何一个寄存器。存储单元位于数据段中,它可以使用内存寻址方式中的任何一种寻址方式
MOV AX, OFFSET p1 ; 获取 p1 过程在代码段内的偏移值
CALL AX
上例中的 AX 也可以使用 BX, CX, DX, BP, SI, DI 来代替。
MOV AX, OFFSET p1
MOV ADD1, AX
CALL ADD1 ; 转移至 ADD1 中存放的偏移地址处
MOV BX, OFFSET ADD1
CALL [BX] ; 转移地址的偏移地址位于数据段中,要通过 BX 间接寻址获取
3. 段间间接寻址
段间间接寻址方式类似于段内间接寻址,知识在间接寻址时不能将要转移的地址直接放入寄存器,而必须放入内存单元中,且是一个双字。
JMP DWORD PTR [BX+INTER]
数据传送指令
一般规范
- 源和目的必须类型匹配
- 目的操作数不能为立即数
- 源和目的操作数不能同时为内存操作数(串指令除外)
- 源和目的操作数不能同时为段寄存器
强制类型转换
- BYTE PTR
- WORD PTR
- DWORD PTR
MOV BYTE PTR x, AX
MOV WORD PTR [DI], AX
数据通路与类型匹配
MOV
MOV [BX], 0
:错,类型不匹配
改正:
MOV BYTE PTR [BX], 0 ; 源:立即寻址;目的:寄存器间接寻址
MOV WORD PTR [BX], 0 ; 同上
其他:
MOV AX, AX ; 源 & 目的:寄存器寻址
NOP ; no operation,用于延时,寄存器寻址
MOV 200H, AX ; 错,通路错误
MOV [DX], AL ; 错,DX 不能用作间接寻址寄存器,只能是 BX, BP, SI, DI
MOV X1+1, AX ; 源:寄存器寻址,目的:直接寻址
XCHG
XCHG OPR1, OPR2
作用:互换两个操作数的内容
- 不允许任何一个操作数为立即数
PUSH, POP, PUSHF, POPF
PUSH SRC ; SP = SP - 2, SS:[SP] <- SRC
PUSHF ; SP = SP - 2, SS:[SP] <- PSW
POP DST ; DST <- SS:[SP], SP = SP + 2
POPF ; PSW <- SS:[SP], SP = SP + 2
- 堆栈的存取只能字为单位
- PUSH 指令可以使用 CS 寄存器,但 POP 指令不能
- 可以使用除立即数以外的任何寻址方式
LEA, LDS, LES
LEA REG, SRC ; 将源操作数 SRC 的偏移地址送入 REG 中
LDS REG, SRC ; 将 SRC 中的双字内容依次送入 REG 及 DS 中
LES REG, SRC ; 将 SRC 中的双字内容依次送入 REG 及 ES 中
- REG 不能是段寄存器
算数运算指令
加法和减法指令
ADD dst, src ; dst <- src + dst
ADC dst, src ; dst <- src + dst + CF,带进位加
INC opr ; opr = opr + 1
SUB dst, src ; dst <- dst - src
SBB dst, src ; dst <- dst - src - CF,带借位减
DEC opr ; opr <- opr - 1
- 除了 INC 与 DEC 不影响 CF 外,所有指令都会影响标志寄存器
CMP
CMP OPR1, OPR2 ; OPR1 - OPR2, 结果不送回,影响标志位
MUL
MUL src ; 无符号数乘法
- 字节操作数——8 位 $\times$ 8 位,
AX <- AL x src
- 字操作数——16 位 $\times$ 16 位,
DX:AX <- AX x src
MUL [SI]
错误,可以是内存操作数,但此时不能确定位数
注意
- src 不能是立即数
- 不能以 16 位乘 8 位数
DIV
DIV src ; 无符号数除法
- 字节操作数:AX/src -> AL(商),余数在 AH 中
- 字操作数:DX:AX/src -> AX(商),余数在 DX 中
注意
- src 不能是立即数
- 必须用 32 位数除以 16 位数,16 位数除 8 位数
- 如果产生除法溢出错误,就会触发 0 号中断,程序会运行出错
CBW 与 CWD
CBW ; 无参数
CWD ; 无参数
- CBW: 将 AL 的符号扩展至 AH(若 AL 最高位为 0,则 AH = 00,否则 AH = 0FFH)
- CWD: 将 AX 中的符号位扩展至 DX 中
逻辑指令
逻辑运算指令
AND DST, SRC
OR DST, SRC
XOR DST, SRC
NOT DST
- NOT 指令对标志寄存器均无影响,其它三条指令对标志寄存器都有影响(SF,ZF,PF 会根据运算结果确定,CF,DF 总是 0,AF 不确定)
TEST 测试指令
TEST dst, src
类似于 AND,但它不将运算结果送目的操作数,而是为了产生标志位,主要是 ZF
移位指令
SHL DST, COUNT ; 逻辑左移
SAL DST, COUNT ; 算数左移
SHR DST, COUNT ; 逻辑右移
SAR DST, COUNT ; 算术右移
ROL DST, COUNT ; 循环左移
ROR DST, COUNT ; 循环右移
RCL DST, COUNT ; 带进位循环左移
RCR DST, COUNT ; 带进位循环右移
- 指令格式中的 dst 可以是除立即数以外的任何寻址方式,可以是字,也可以是字节
- 指令中的 count 只能是 1 或 CL
移位实现双字 × 16
控制转移指令
无条件转移指令 JMP
指令格式 | 含义 |
---|---|
段内直接短转移(IP - 128~ IP + 127) | JMP SHORT PTR |
段内直接转移(IP - 2^16~ IP + 2^16 - 1) | JMP NEAR PTR |
段内间接转移(在同一个代码段 CS 中) | JMP WORD PTR |
段间直接转移 | JMP FAR PTR |
段间间接转移 | JMP DWORD PTR |
条件转移指令
JZ(JE) ADDR ; 如果 ZF = 1,则转移至 ADDR 处
JNZ(JNE) ADDR ; 如果 ZF = 0,则转移至 ADDR 处
JC ADDR ; 如果 CF = 1,则转移至 ADDR 处
JNC ADDR ; 如果 CF = 0,则转移至 ADDR 处
JO ADDR ; 如果 OF = 1,则转移至 ADDR 处
JNO ADDR ; 如果 OF = 0,则转移至 ADDR 处
JS ADDR ; 如果 SF = 1,则转移至 ADDR 处
JNS ADDR ; 如果 SF = 0,则转移至 ADDR 处
JP(JPE) ADDR ; 如果 PF = 1,则转移至 ADDR 处
JNP(JPO) ADDR ; 如果 PF = 0,则转移至 ADDR 处
- 转移指令不会改变任何标志寄存器,所以可以连用两条条件转移指令
JA(JNBE) ADDR ; >
JAE(JNB, JNC) ADDR ; >=
JE(JZ) ADDR ; ==
JBE(JNA) ADDR ; <=
JB(NAE, JC) ADDR ; <
JNE(JNZ) ADDR ; !=
循环指令
LOOP ADDR
- CX = CX - 1
- 若 CX != 0,则转移至 ADDR 处,否则顺序往下执行
LOOPZ/LOOPE ; 当结果为 0 时或相等时循环
LOOPNZ/LOOPNE ; 当结果为不 0 时或不相等时循环
DUP
表达式重复定义数据,其格式为:
repeat_count DUP(operand,...,operand)
例子:
; 使用 DUP 定义数组
ARRAY1 DB 2 DUP (0,1,0FFH,?) ; 定义一个二维数组
ARRAY1 DB 0,1,0FFH,?,0,1,0FFH,? ; 同上
ARRAY2 DB 100 DUP(?) ; 定义一个一维数组
$
代表当前位置的偏移地址
x DW $ ; x 中是当前 (x) 的偏移地址
过程调用与返回指令
CALL dst
RET
RETF 2n
,n 是参数个数,或者 RET 2n
处理器控制指令
CLC ; CF <- 0
STC ; CF <- 1
CLD ; DF <- 0
STD ; DF <- 1
CLI ; IP <- 0
STI ; IP <- 1
NOP
其它伪指令
OFFSET 和 SEG
OFFSET
操作符回送的是变量名、标号(及过程名)的偏移值地址SEG
操作符回送的是变量名、标号(及过程名)的段地址
MOV BX,OFFSET ARRAY
MOV AX,SEG ARRAY
PTR
类型 PTR 地址表达式
THIS
THIS 类型
使用 THIS操作符的作用是,为某个变量名指定一个类型(BYTE,WORD,WORD),或为某个标号或过程名指定一种转移距离(NEAR,FAR)。它的实质是为同一个物理位置给定另一个属性。
X1 EQU THIS BYTE
MOV AL,X1
LABEL
与 THIS 类似,指明类型
VAR_BYTE EQU THSBYTE
VAR_BYTE LABEL BYTE
JUMP_FAREQU THIS FAR
JUMP_FAR LABEL FAR
程序设计
基本格式
框架
; 宏指令
MACRO_NAME MACRO
...
ENDM
; 宏指令
; 主程序
STACK SEGMENT PARA STACK
STACK_AREA DW 100h DUP(?)
STACK_TOP EQU $-STACK_AREA
STACK ENDS
DATA1 SEGMENT PARA
...
DATA1 ENDS
CODE1 SEGMENT PARA
ASSUME CS:CODE1, DS:DATA1, SS:STACK, ES:DATA1
MAIN PROC FAR
MOV AX, STACK ; 初始化数据段 & 代码段
MOV SS, AX
MOV SP, STACK_TOP
MOV AX, DATA1
MOV DS, AX ; SET SS,SP,DS
MOV ES, AX
......
CALL PROGRAM_NAME
MACRO_NAME
......
EXIT: MOV AX, 4C00H
INT 21H
MAIN ENDP
PROGRAM_NAME PROC
PUSH BP
MOV BP, SP
PUSH ...
PUSH ...
MOV AX, [BP+4] ; FIRST PARAM
MOV BX, [BP+6] ; SECOND PARAM
...
POP ...
POP ...
POP BP
RET
PROGRAM_NAME ENDP
CODE1 ENDS
END MAIN
; 主程序
常用宏
PRINT_LINE MACRO
PUSH AX
PUSH DX
MOV AH, 02H ; 打印字符
MOV DL, 10 ; 换行的 ASCII 码为 10
INT 21H
POP DX
POP AX
ENDM
PRINT_SPACE MACRO
PUSH AX
PUSH DX
MOV AH, 02H ; 打印字符
MOV DL, 32 ; 空格的 ASCII 码为 32
INT 21H
POP DX
POP AX
ENDM
数制转换及乘除法
数制转换(输出)
内存中的值二进制显示
PUTBIN PROC ; 内存中的值转换为十进制 ASCII 码(倒除 10,参数为 AX)
PUSH AX
PUSH BX
PUSH CX
PUSH DX
MOV BX, 2 ; 除数
MOV CX, 16 ; 循环的初始条件
PUTBIN_LP1:
XOR DX, DX ; 清除高 16 位
DIV BX ; 商在 AX 中,余数在 DX 中 < 10
OR DL, 30H ; 变成 ASCII 码
PUSH DX ; 保存余数
LOOP PUTBIN_LP1
MOV CX, 16 ; 共有 5 个余数
PUTBIN_LP2:
POP DX ; 后进先出
MOV AH, 2
INT 21H ; 显示 DL 中的字符
LOOP PUTBIN_LP2
PRINT_LINE
POP DX
POP CX
POP BX
POP AX
RET
PUTBIN ENDP
内存中的值十进制显示
PUTNUM PROC ; 内存中的值转换为十进制 ASCII 码(倒除 10,参数为 AX)
PUSH AX
PUSH BX
PUSH CX
PUSH DX
MOV BX, 10 ; 除数
MOV CX, 5 ; 循环的初始条件
PUTNUM_LP1:
XOR DX, DX ; 清除高 16 位
DIV BX ; 商在 AX 中,余数在 DX 中 < 10
OR DL, 30H ; 变成 ASCII 码
PUSH DX ; 保存余数
LOOP PUTNUM_LP1
MOV CX, 5 ; 共有 5 个余数
PUTNUM_LP2:
POP DX ; 后进先出
MOV AH, 2
INT 21H ; 显示 DL 中的字符
LOOP PUTNUM_LP2
PRINT_LINE
POP DX
POP CX
POP BX
POP AX
RET
PUTNUM ENDP
内存中的值十六进制显示
PUTHEX PROC ; 内存中的值转换为十六进制 ASCII 码(倒除 16,参数为 AX)
PUSH AX
PUSH BX
PUSH CX
PUSH DX
MOV BX, AX ; 参数移动至 BX 中
MOV CX, 4 ; 循环的初始条件
PUTHEX_LP1:
PUSH CX ; 保存 CX
MOV CL, 4 ; 位移次数
ROL BX, CL ; 每次都将高 4 位移到低 4 位
MOV AL, BL ; BX 下次还要用
AND AL, 0FH ; 屏蔽 AL 的高 4 位
ADD AL, 30H
CMP AL, 39H
JBE PUTHEX_LP2 ; '0' - '9'
ADD AL, 'A'-'9'-1 ; 'A' - 'F'
PUTHEX_LP2:
MOV DL, AL
MOV AH, 2
INT 21H
POP CX ; 弹出 CX = 循环计数
LOOP PUTHEX_LP1
PRINT_LINE
POP DX
POP CX
POP BX
POP AX
RET
PUTHEX ENDP
排序
;;;;;;; DATA ;;;;;;;;;
TABLE_LEN DW 16 ; 待排序的数字数量
TABLE DW 200,300,400,10,20,0,1,8
DW 1923H,40,1133H,3321h,60,0FFFFH,2,3
;;;;;;;;;;;;;;;;;;;;;;
;;;;;; CODE ;;;;;;;;;;
LEA SI, TABLE ; MOV SI, offset table
MOV CX, TABLE_LEN
LP0: ; 打印排序前的数字
MOV AX, [SI]
PUSH CX
CALL PRINT_NUM
POP CX
PRINT_SPACE
ADD SI, 2
LOOP LP0
PRINT_LINE
LP1: MOV BX,1 ; 冒泡排序外层循环
MOV CX,TABLE_LEN
DEC CX
LEA SI,TABLE ;MOV SI,offset Table
LP2: MOV AX,[SI]
CMP AX,[SI+2]
JBE CONTINUE ; 目前排序为从小到大,若要改为从大到小,只需要将此改为 JAE 即可
XCHG AX,[SI+2]
MOV [SI],AX
MOV BX,0
CONTINUE:
ADD SI,2
LOOP LP2
CMP BX,1
JZ INIT
JMP SHORT LP1
INIT:
LEA SI, TABLE ; MOV SI, offset table
MOV CX, TABLE_LEN
LP3: ; 打印排序后的数字
MOV AX, [SI]
PUSH CX
CALL PRINT_NUM
POP CX
PRINT_SPACE
ADD SI, 2
LOOP LP3
;;;;;;;;;;;;;;;;;;;;;;
获得输入
GETNUM PROC ; 入口参数:CX = 十进制数的位数
PUSH SI
PUSH DX
PUSH BX
MOV SI, 0 ; 初值 = 0
MOV BX, 10 ; 乘数
LLP1:
MOV AH, 1 ; 读入一个字符,存储在 AL 中
INT 21H ; 系统中断
CMP AL, 0DH ; 是回车吗
JE RETURN
CMP AL, 30H
JB LLP1 ; < '0' 忽略
CMP AL, 39H
JA LLP1 ; > '9' 忽略
SUB AL, 30H ; 由 ASCII 码变为二进制数
XOR AH, AH ; 清空高位
PUSH AX ; 保存 AX,后面用 AX 做乘法
MOV AX, SI ; 上一次二进制值
MUL BX ; 乘 10
MOV SI, AX ; 回送到 SI
POP AX ; 弹出刚刚输入的值
ADD SI, AX ; 与上次结果相加
LOOP LLP1
RETURN:
MOV AX, SI ; AX = 返回值
POP BX
POP DX
POP SI
RET
GETNUM ENDP
字符串处理
DS:[SI] -> ES:[DI]
CLD / STD DF=0/1; SI,DI++/--
PUSH DS
POP ES
;;; 错误指令:MOV ES, DS
指令
取串指令 LODSB, LODSW
LODSB ; 取字节
LODSW ; 取字
存串指令 STOSB, STOSW
STOSB ; 存字节
STOSW ; 存字
串传送指令 MOVSB / MOVSW
MOVSB ; 传字节
MOVSW ; 传字
串比较指令 CMPSB / CMPSW
CMPSB ; 比较字节
CMPSW ; 比较字
相当于 CMP DS:[SI],ES:[DI]
串扫描指令
SCASB ; 扫描字节
SCASW ; 扫描字
重复前缀指令
REP 串操作指令
条件重复前缀指令 REPE / REPZ 及 REPNE / REPNZ
- REPE 串操作指令
- REPNE 串操作指令
CLD 与 STD
样例
清零
PUSH ES
PUSH DS
POP ES
MOV DI, OFFSET BUF
MOV CX, LEN
CLD
MOV AL, 0
REP STOSB
POP ES
大小写转换(小写变大写)
PUSH ES
PUSH DS
POP ES
MOV DI, OFFSET BUF
MOV CX, LEN
CLD
LP1:
LODSB
CMP AL, 'a'
JB CONTINUE
CMP AL, 'z'
JA CONTINUE
SUB AL, 20H
CONTINUE:
LOOP LP1
POP ES
比较大小
扫描字符串
在缓冲区查找回车换行,并将它删掉
在缓冲区改变换行格式