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)

实际内存分配法

  • 代码段
  • 数据段
  • 堆栈段

堆栈

堆栈的位置

堆栈由软件设置,一端固定,一端浮动。信息的存取在浮动的一端进行。堆栈的位置和大小由 SSSP 寄存器确定。

  • 堆栈的栈底SP 值确定,它是堆栈中最大单元处的地址加一。随着不断的压栈操作 SP 的值会减小
  • 栈顶在堆栈区外

堆栈操作

堆栈操作遵守先进后出的原则。

PUSH

PUSH AX 的执行过程

  1. SP <= SP - 2
  2. (SP) <= AX

POP

  1. AX <= (SP)
  2. SP <= SP + 2

堆栈图

  • CALL (段内)
  • CALL (段间)

第三章

指令格式

汇编语言格式

基本格式:

机器指令格式

一条指令的长度为 1 ~ 7 字节

  1. 操作码字节

  1. 寻址方式字节

  1. 段超越字节

指令优先级

(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
  1. CX = CX - 1
  2. 若 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

比较大小

扫描字符串

在缓冲区查找回车换行,并将它删掉

在缓冲区改变换行格式

子程序和跳转表