x86-construction
本文改编补充于董佬的dhy2000/x86Course: BUAA X86 汇编程序设计 课程笔记和实验作业 (github.com) ,并添加了一些自己的理解
常用子程序
DOS 功能调用相关
调用指令: INT 21H
,其中 21H
表示 DOS。
用寄存器 AH
指定功能调用号。
在实际编码发现虽然可能只使用了AH也可能AL被修改,所以实际编码中最好清空整个AX或DX
功能调用号(AH) | 输入 | 输出 | 功能 |
---|---|---|---|
1 | console | AL | 输入一个ASCII字符 |
2 | DL | console | 输出一个ASCII字符 |
9 | DX | console | 输出字符串 |
A | console | DX(包含字符串长度及内容) | 输入字符串 |
4C | AL(常设为00H表示返回值为0) | / | 返回DOS |
返回 DOS
1 | MOV AH, 4CH ; AH = 4C, DOS 4C 号功能调用: 返回 DOS |
输入输出
输入一个 ASCII 字符: 1 号功能调用
1 | MOV AH, 1 ; 1 号功能调用 |
输出一个 ASCII 字符: 2 号功能调用
1 | MOV DL, 'A' ; 输出的字符在 DL 中 |
注意: AL
寄存器会改变
输入字符串: 0AH
号功能调用
使用时需要先构造一个特定格式的缓冲区,定义输入的最大允许长度。使用时键入一行字符串并按回车键。
1 | ; ---- DATA SEGMENT PARA ---- |
输出字符串: 9 号功能调用
使用9号功能输出的字符串需要以’$'结尾
1 | LEA DX, STR ; 输出的字符串首地址在 DX 中 |
IO相关
输入不需要保护最后获取数据的寄存器,而输出需要保护需要打印数据的寄存器
从console读入一个十进制数字
将读入数据存储在AX,子程序中需要将BX和CX压栈,分别代表radix和integer cache,所以实际上这个程序通过改BX可以实现任意数值读入(十六进制本身有简单读入)
1 | ; read a decimal integer from console |
用十进制输出一个字的数据
将要输出的字数据存储在AX,子程序需要将AX,BX,CX,DX压栈,AX防止本身要输出的数字被破坏,BX为radix,DX为除以基数得到的商并利用DL完成DOS输出,CX记录AX内数据转化为对应进制后的位数,便于对DX进行出栈输出,所以这个程序可以实现对AX的任意进制输出
1 | ; print a decimal integer to console |
用十进制输出一个双字的数据
将要输出的字数据存储在mem64@[BX],CX为对应radix,AX用于提取mem64@[BX]中的字,DX存储商并利用DL完成DOS输出,利用BP和SP指针位置比较决定是否完成出栈
1 | ; Display 64-bit integer in decimal. |
用十六进制输出一个字节的数据
将要输出的字节数据存储在AL,子程序需要保护CX,DX,SI和AX,AX防止输出数据被破坏,CX利用CL完成位运算,DX利用DL调用DOS完成输出,SI进行寄存器相对寻址获取DL的值
1 | DATA SEGMENT PARA |
以十六进制输出一个字的数据
将要输出的字数据存储在DX,需要调用PUTHEX8,保护AX和DX用于DOS输出
1 | DATA SEGMENT PARA |
以十六进制输出一个双字的数据
将要输出的字数据存储在mem64@[BX],需要调用PUTHEX8,保护AX和DX用于DOS输出
1 | ; Display 64-bit integer in hexadecimal |
打印数组
AX和DX用于DOS输出,CX存储当前打印元素数组下标index,SI用于寄存器相对寻址获取元素
1 | DATA SEGMENT PARA |
字符串相关
指令 | 功能描述 | 操作大小 | 影响寄存器 |
---|---|---|---|
LODSB |
将源串 DS:[SI] 的一个字节取到 AL ,根据 DF 修改 SI |
字节 | SI |
LODSW |
将源串 DS:[SI] 的一个字取到 AX ,根据 DF 修改 SI |
字 | SI |
STOSB |
将 AL 中的一个字节存入目的串 ES:[DI] ,并根据 DF 修改 DI |
字节 | DI |
STOSW |
将 AX 中的一个字存入目的串 ES:[DI] ,并根据 DF 修改 DI |
字 | DI |
MOVSB |
将源串 DS:[SI] 的字节或字传送到目的串 ES:[DI] ,并根据 DF 修改 SI 及 DI |
字节 | SI , DI |
MOVSW |
同 MOVSB ,但传送的是字 |
字 | SI , DI |
CMPSB |
比较源串 DS:[SI] 与目的串 ES:[DI] 的一个字节或字,根据 DF 修改 SI 及 DI |
字节 | 标志寄存器 (CF , SF , ZF ) |
CMPSW |
同 CMPSB ,但比较的是字 |
字 | 标志寄存器 (CF , SF , ZF ) |
SCASB |
在目的串 ES:[DI] 中扫描是否有 AL 指定的字节,根据 DF 修改 DI |
字节 | 标志寄存器 |
SCASW |
在目的串 ES:[DI] 中扫描是否有 AX 指定的字,根据 DF 修改 DI |
字 | 标志寄存器 |
指令 | 等价指令 | 含义 | 执行条件 | 停止条件 |
---|---|---|---|---|
REP |
重复 | CX 不为零 |
CX 为零 |
|
REPE |
REPZ |
相等时重复 | CX 不为零,且 ZF 为 1 |
CX 为零 或 ZF 不为 1 |
REPNE |
REPNZ |
不相等时重复 | CX 不为零,且 ZF 为 0 |
CX 为零 或 ZF 为 1 |
MEMSET
内存初始化(memset(BUFF, 0, LEN)😉,其中DI为BUFF,AL为0,CX为LEN
- 用
REP STOSB
指令将长度为LEN
的缓冲区BUFF
清零。
1 | MEMSET PROC |
MEMCPY
字符串复制(memcpy(BUFF2, BUFF1, LEN)😉
- 用
REP MOVSB
指令将缓冲区BUFF1
内容传送到BUFF2
, 长度为LEN
。
1 | MEMCPY PROC |
STRCMP
字符串比较(由于是相同则继续比较,故使用
REPZ
)
- 情况1:长度不定长比较(strcmp(STRING1, STRING2)😉
比较
STRING1
与STRING2
按字典序排序的大小,改变CF
后返回将
CL
赋值为STRING1
和STRING2
中较短的长度作为比较次数
1 | STRCMP PROC |
- 情况2:长度定长比较(strncmp(STRING1, STRING2, LEN)😉
比较
STRING1
与STRING2
按字典序排序的大小,假定都是大写字母且长度都为LEN
。
1 | STRNCMP PROC |
上述代码还可以通过 CX
的值判断比较操作进行了多少次,如果 CX=0
则比较至最后了。
FIND
字符串查找子串(由于是相同则代表找到,不相同继续比较,故使用REPNZ)
REPNZ SCASB
停下来后,如果ZF=1
则AL=ES:[DI]
,通过CX
的值可以看出 AL 在STRING1
中的位置。如果ZF=0
则已经遍历到最后仍未找到。
1 | FIND PROC |
STRCAT
字符串拼接(strcat(dst, src)😉
1 | ; concat str2 after str1 (both asciiz) |
TOUPPER
字母转大写
1 | int CX = LEN; |
- 将长度为
LEN
的缓冲区BUFF1
中的小写字母变成大写。
注意小写字母和对应大写字母相差20H
1 | TOUPPER PROC |
algorithm相关
冒泡排序
注意LOOP默认减少CX即通过CX控制循环次数即可
1 | ; bubble sort |
数制
-
MASM 中十六进制数的表示方法:以
h
或H
结尾,如果以字母A-F
/a-f
开头还需加前缀0
以避免和标识符(变量/标签/寄存器)混淆。 -
例如:
0AH
,0FFFFFh
,10h
,分别对应 C 语言中的0xa
,0xFFFFF
,0x10
。其中0AH
和0FFFFFh
的前缀 0 是为了区分寄存器名称AH
与标识符名称FFFFFh
。
8086 机器
基本设定
机器字长: 16 位 (ALU,寄存器等的位宽)
地址线宽: 20 位(寻址空间为 1MB)
数据总线: 16 位 (8086), 8 位 (8088)
主要字符的ASCII码
以下为十六进制表示,注意十六进制输出时从9到A需要加7,从A到a需要加20H
- 数字 0-9:30-39
- 大写字母 A-Z:41-5A
- 小写字母 a-z:61-7A
- 空格:20
- 换行:0A
寄存器
-
通用寄存器: 共 4 个,每个寄存器 16 位 (
*X
),分为两个 8 位 (*H
,*L
)AX (AH, AL)
: 累加器 (Add)BX (BH, BL)
: 基址寄存器 (Base)CX (CH, CL)
: 计数寄存器 (Count)DX (DH, DL)
: 数据寄存器 (Data)
-
指针寄存器: 2 个
BP经常用于存取非栈顶的数据或和SP进行位置比较决定是否继续出栈
SP
: 16 位堆栈指针BP
: 16 位基址指针
-
变址寄存器 (字符串指针)
SI
: 源串 (Source)DI
: 目的串 (Destination)
-
段寄存器: 4 个
CS
: 代码段 (Code)DS
: 数据段 (Data)SS
: 堆栈段 (Stack)ES
: 附加段 (Extra)
-
指令指针
IP
, 相当于 RISC 中的PC
-
标志寄存器
PSW
SS:SP
组成堆栈 (SS
是堆栈基地址, SP
是栈顶相对基地址的偏移)
CS:IP
组成当前可执行点 (CS
是代码段基地址, IP
是当前指令相对基地址的偏移)
标志寄存器
1 | 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
标志含义:
OF
溢出标志DF
方向标志 (地址递增/递减)[CLD/STD]IF
中断标志TF
陷阱标志SF
符号标志ZF
结果为零标志AF
辅助进位标志PF
奇偶标志CF
进位标志[CLC/STC]
存储器
8086 地址线共有 20 位,可寻址空间为 1MB。
总体结构
+------------+ --- --- ---
| 00000h | | Byte | |
+------------+ --- | Word |
| 00001h | | |
+------------+ --- | DoubleWord
| 00002h | |
+------------+ |
| 00003h | |
+------------+ ---
| |
| ...... |
| |
+------------+
| FFFFFh | 1MB
+------------+
内存的基本单元为一个字节 (Byte, 8 位),按线性顺序存放;两个字节组合为一个字 (Word, 16 位),两个字组合成为一个双字 (Double Word, 32 位)。
内存中可以任意组合存放字节、字、双字,无需字对齐 (与 MIPS 不同)。
在内存中存放一个字时,低字节在前,高字节在后; 存放双字时,低字在前,高字在后(即: 按字节小端序存储)
例: 在 00000h
地址存放一个双字 12345678h
addr : data
00000h : 78h
00001h : 56h
00002h : 34h
00003h : 12h
字节、字、双字的存取
例题: CL
中有字节 'A'
, BX
中有字 2000h
, DX:AX
中有双字 12345678h
。
-
依次将
CL
,BX
,DX:AX
中的内容按照字节、字、双字的结构存入地址30000h
处。addr : data 30000h : 41h ; 'A' 30001h : 00h ; 2000h 30002h : 20h ; 30003h : 78h ; 12345678h 30004h : 56h ; 30005h : 34h ; 30006h : 12h ;
-
将内存
30000h
处的一个字取出来送入BX
中,将30002
处的一个双字送入DX:AX
中BX = 0041h [BH = 00h, BL = 41h] DX = 3456h [DH = 34h, DL = 56h] AX = 7820h [AH = 78h, AL = 20h] DX:AX = 34567820h
逻辑地址和物理地址
用 16 位的地址寄存器来表示地址,最多可寻址 64KB 空间。要表示 20 位地址,需要对内存进行分段,用一个寄存器 (段寄存器) 表示段地址,用另一个寄存器 (指针寄存器) 表示段内地址偏移。
将 1MB 内存分段,每段最大 64KB。
-
物理地址: 20 位二进制,与内存单元 (字节) 一一对应
-
逻辑地址: 用段地址和偏移值组合表示内存地址, 常写作
段地址:偏移值
的形式 -
物理地址 = 段地址 x 16D(10h) + 偏移地址,一个物理地址可能有多个逻辑地址的组合。
即把段地址左移一个十六进制位再加上偏移地址
-
典型的程序在内存中执行时,一般都有代码段,数据段,堆栈段,其段地址分别用
CS
,DS
,SS
来存放。
堆栈的组成和操作
堆栈: 由 SS
和 SP
确定的一块特殊区域,严格按照先进后出的方式工作。增长方向为从高地址向低地址(与 MIPS 相似)
例: SS = 2000h, SP = 0100h
- 堆栈区域为
2000h:0000h
至2000h:00FFh
- 栈顶指针值为
0100h
(注意栈顶指针值本身不属于堆栈) - 压栈操作
PUSH op
SP = SP - 2
op -> SS:[SP]
- 出栈操作
POP op
op <- SS:[SP]
SP = SP + 2
指令格式与寻址方式
指令格式
Intel 8086/8088 基本指令格式:
op dst, src ; 由 "源" 至 "目的", 结果在目的操作数中
(分号 ;
以后的内容为行注释)
指令可以有 2 个, 1 个或 0 个操作数:
op op2, op1
op op1
op
指令编码由 1-7 个字节组成,为变长编码。编码要素:
- 操作码字节
- 寻址方式字节
- 段超越字节
- 操作数
寻址方式
与数据有关:
寻址方式 | 描述 | 示例 |
---|---|---|
立即寻址 | 指令中直接给出操作数,操作数大小通常与指令中操作码所指定的一致。 | MOV AX, 1234H 将立即数0x1234移动到AX寄存器 |
寄存器寻址 | 操作数直接存储在寄存器中,指令中指定寄存器名。 | ADD AX, BX 将BX寄存器的内容加到AX寄存器 |
直接寻址 | 操作数的地址直接给出,指令中包含操作数的完整地址。 | MOV AX, [1000H] 将地址0x1000处的值移动到AX寄存器 |
寄存器间接寻址 | 操作数的地址存储在寄存器中,指令中指定寄存器名,实际操作的是该寄存器中的地址所指向的内存单元。 | MOV AX, [BX] 将BX寄存器指向的内存地址处的值移动到AX寄存器 |
寄存器相对寻址 | 基址寄存器中存储着一个地址,指令中给出的是一个偏移量,实际操作的地址是基址寄存器中的地址加上偏移量。 | MOV AX, [BX + 5] 将BX寄存器的值加上5后得到的地址处的值移动到AX寄存器 |
基址变址寻址 | 结合基址寄存器和变址寄存器,形成最终的操作数地址,通常是基址寄存器的值加上变址寄存器的值再加上指令中的偏移量。 | MOV AX, [BX + SI + 10h] 将BX寄存器的值加上SI寄存器的值再加上16进制数10h后得到的地址处的值移动到AX寄存器 |
与转移/调用指令有关:
寻址方式 | 描述 | 示例 |
---|---|---|
段内直接寻址 | 直接使用段内偏移量进行跳转或调用,适用于同一数据段内的跳转。 | JMP p1 跳转到label p1 中存储的偏移地址继续执行 |
段内间接寻址 | 通过寄存器间接访问段内地址进行跳转或调用,适用于间接访问地址的情况。 | JMP [BX] 跳转到BX寄存器指向的内存地址继续执行 |
段间直接寻址 | 使用段地址和偏移地址进行跳转或调用,适用于不同段间的跳转。 | JMP FAR PTR p2 跳转到label p2 地址继续执行 |
段间间接寻址 | 通过段寄存器和间接寻址方式,结合段选择器和偏移量进行跳转或调用。 | CALL DWORD PTR [BX] 调用BX寄存器指向的远指针地址 |
与 IO 有关:
- 直接 I/O 端口寻址
- 寄存器 DX 间接寻址
与数据有关的寻址方式 (6 种)
(目的, 源) 操作数可以来自:
- 寄存器: 8 个通用寄存器, 4 个段寄存器, 1 个标志寄存器
- 立即数: 指令本身给出的立即数 (常量)
- 内存单元: 直接寻址/间接寻址
- 定义操作数:
EQU
: 常量DB
: 字节DW
: 字DD
: 双字
寻址方式:
- 立即寻址: 指令操作数包含在指令中,为一个常量或常数
- 寄存器寻址: 指令操作数为 CPU 的寄存器
- 直接寻址: 操作数偏移地址
EA
在指令中给出,如变量名 - 寄存器间接寻址: 操作数地址
EA
位于间指寄存器 (BX
,BP
,SI
,DI
) 中 - 寄存器相对寻址: 操作数地址
EA
由间指寄存器 + 8 位或 16 位的常量组成 - 基址变址寻址: 操作数地址
EA
为一个基址寄存器和一个变址寄存器之和
掌握和理解寻址要点:
- 寄存器的使用规则
- 类型匹配
- 数据通路
- 操作的是"内容"还是"地址"(指针)
立即寻址
指令所需操作数直接包含在指令代码中,可以是一个常量 (由 EQU
定义) 或者一个常数,成为立即数。
立即数可以是 8 位或 16 位,需要看与之对应的另一个操作数的类型(二者需要匹配)
1 | VALUE EQU 512 ; 定义一个常量, 名称为 VALUE, 值为 512 |
错误示例:
1 | MOV AL, 100H ; 100H 超出了 1 字节的范围 |
寄存器寻址
指令中所需的操作数是 CPU 的某个寄存器,取操作数完全在 CPU 内部进行,不需要访存。
- 对于 8 位操作数,寄存器可以是
AH
,AL
,BH
,BL
,CH
,CL
,DH
,DL
中的一个。 - 对于 16 位操作数,寄存器可以是
AX
,BX
,CX
,DX
,SP
,BP
,SI
,DI
及CS
,DS
,SS
,ES
的任何一个(没有IP
)
寄存器寻址方式示例:
1 | MOV AX, BX ; 源操作数和目的操作数都是寄存器寻址 |
当使用 CS
, DS
, SS
, ES
段寄存器时,必须遵循数据通路要求。
操作数中的寄存器可能是隐含的寄存器(没有明确出现在指令的源或目的操作数中),例如:
1 | PUSHF ; PSW (标志寄存器) 作为源操作数,是寄存器寻址方式 |
直接寻址
操作数的偏移地址直接在指令中给出。例如:
1 | MOV AX, [2000H] ; 源操作数 [2000H] 为直接寻址 |
以上示例中源操作数 [2000H]
是直接寻址。(加 []
表示其内部的 2000H
是地址,如不加则是立即寻址)
如没有段超越,通常以这种方式直接寻址去操作数都是相对数据段 DS
的。
如使用变量(符号)定义内存中的单元,在指令中直接使用符号也是直接寻址,虽然操作数中并未直接出现地址,但汇编语言程序经过汇编器后,汇编器计算出符号的偏移值并进行替换。例如:
1 | ; 以下两行为伪指令, 定义了两个变量 |
寄存器间接寻址
操作数的有效地址 EA 不位于指令中,而是位于**基址寄存器 BX
, BP
或变址寄存器 SI
, DI
**中(不能是其他寄存器)。因为地址值未在指令中直接指出,而是通过一个寄存器来指明,因此称为间接寻址。效果上,这个寄存器相当于一个地址指针。
例如下面指令的源操作数的寻址方式都是间接寻址:
1 | MOV AX, [BX] ; 内存操作数的偏移地址位于 BX 中,在 DS 段内 |
错误示例:
1 | MOV AX, [DX] ; DX 不能作为间接寻址寄存器 |
隐含段规则
使用以上 4 个寄存器进行间接寻址时,如果未显式指定段寄存器,则 BX
, SI
, DI
是相对 DS
段的偏移地址,而 BP
是相对 SS
段的偏移地址。
上述四条指令分别与下面四条指令等价:
由于BP经常与SP配合使用,所以不难理解BP是相对于SS的
1 | MOV AX, DS:[BX] |
注意: 堆栈指针 SP
不可以用来间接寻址!
段超越
如果寻址是不用寄存器默认隐含的段,而是显式地指定一个段寄存器,则称为段超越。例如:
1 | MOV AX, SS:[BX] ; BX 默认相对段 DS,此处指定用段 SS 代替默认的 DS |
寄存器相对寻址
操作数的有效地址 EA 是一个基址寄存器或变址寄存器的内容和指令中指定的 8 位和 16 位位移量之和。
即: EA = 间址寄存器的值 + 8 位或 16 位常量
也就是在间接寻址的基础上增加了一个常量(间接寻址 + 相对寻址)。
可用来寻址的寄存器与隐含段规则同间接寻址, BX
, SI
, DI
寄存器寻址的默认段是数据段 DS
, BP
寄存器寻址的默认段是堆栈段 SS
。
寄存器相对寻址示例(以下指令中的源操作数):
1 | MOV AX, [SI+10H] ; SI 的值加 10H 形成偏移地址, 在 DS 段内寻址 |
其中 [SI+10H]
也可以写成 10H[SI]
,即上面的指令和下面的指令等价:
1 | MOV AX, 10H[SI] |
与直接寻址一样,相对寻址的 16 位偏移量也可以是个符号名或变量名。因为符号名和变量名在段内的位置(偏移值)是固定的,所以作用等同于常量。
采用符号名进行相对寻址的示例:
1 | MOV AX, ARRAY[SI] ; EA = SI 的值 + ARRAY 相对 DS 的偏移值 |
基址变址寻址
操作数的有效地址 EA 等于一个基址寄存器和一个变址寄存器的内容之和。
显著特点: 两个寄存器均出现在指令中。
基址寄存器为
BX
或BP
, 变址寄存器为SI
或DI
。如果基址寄存器为
BX
, 则缺省段寄存器为DS
; 如果基址寄存器为BP
, 则缺省段寄存器为SS
。
示例:
1 | MOV AX, [BX][SI] ; 源操作数 EA = BX + SI, 段为 DS |
可以将基址变址寻址方式理解为寄存器相对寻址方式加上一个变址寄存器。例如:
1 | MOV AX, [BX+SI+200] ; 源操作数的 EA = BX + SI + 200, 段为 DS |
注意: 基址变址寻址方式的基址寄存器只能为 BX
或 BP
, 变址寄存器只能是 SI
或 DI
。
错误示例:
1 | MOV [BX+CX], AX ; CX 不能作为变址寄存器 |
最后一条指令如果 ARRAY 是变量, 则源操作数是直接寻址, 源和目的操作数不能都在内存中(此部分详见数据通路),但 ARRAY 如果是立即数,则源操作数是立即寻址,没问题。
与转移地址有关的寻址方式
与转移地址有关的寻址方式主要运用于转移指令 JMP
和过程调用指令 CALL
,寻址方式共有四种:
- 段内直接寻址
- 段内间接寻址
- 段间直接寻址
- 段间间接寻址
标号与过程名
标号示例:
1 | ... |
上例中 l1
是一个标号,后面跟有一个冒号,一般位于一条指令的前面。标号的作用与变量名类似,确定了标号后的指令在代码段中的偏移地址。
过程名示例:
1 | ... |
或
1 | ... |
过程位于程序代码中,上述两个例子的 p1
和 p2
是过程名,确定了该过程第一条指令在代码段中的偏移值。其中 p2
还同时指出了它所处的 CS
段值(用 far
表示)
段内直接寻址
要转向(由 JMP
, 条件转移,CALL
等)指令实际的有效地址是当前 IP
寄存器的内容和指令中指定的 8 位或 16 位位移量之和。
在定义了前面的标号 l1
或子程序名 p1
, p2
后,段内直接寻址的示例:
1 | JMP l1 ; 转移至标号 l1 处 |
与操作数的直接寻址方式不同的是,上述指令在汇编后,指令的机器码不会直接出现 l1
或 p1
的偏移地址,而是相对于当前 IP 的位移量,是一种相对寻址。
根据位移量是 8 位还是 16 位,可以加 SHORT
和 NEAR PTR
操作符, 如下例所示:
1 | JMP SHORT l1 ; l1 与当前 IP 的位移量是一个 8 位值 |
对于条件转移指令,只能是 8 位位移量,省略 SHORT
操作符。
如果 JMP 指令省略了 NEAR PTR
或 SHORT
操作符,则使用 16 位位移量。使用 8 位位移量的转移称为短跳转。
段内间接寻址
转向的有效地址是一个寄存器或存储单元的内容。
- 寄存器: 可以是
AX
,BX
,CX
,DX
,SI
,DI
,BP
,SP
中的任何一个。 - 存储单元: 位于数据段中,可以使用四种内存寻址方式中的任何一种。
在转移指令中使用寄存器间接寻址时,寄存器保存的是相对**代码段 CS
**而不是数据段的偏移值。(与数据相关的寻址不同!)
示例: 用寄存器 AX
作为间接寻址寄存器
1 | MOV AX, OFFSET p1 ; 获取 p1 过程在代码段内的偏移值 |
上例中 AX
也可以用 BX
, CX
, DX
, BP
, SI
, DI
来代替, 且都是相对于代码段的。使用 SP
在语法上也允许,但逻辑上通常不这样使用。
注意段内间接寻址和数据寄存器间接寻址的差别:
1 | MOV AX, [BX] ; 数据寄存器间接寻址 |
数据寻址的寄存器间接方式中, [BX]
有方括号,相对于数据段寻址。
当间接寻址使用内存单元存放时, 要转移的偏移地址位于数据段的某个位置,而要转移至的位置则位于代码段中。
在下列指令中,转移地址同样使用的是段内间接转移:
1 | MOV AX, OFFSET p1 ; 获取过程 p1 的偏移地址,存入 AX |
对于 CALL ADD1
指令,当 ADD1
是数据段中的一个地址而不是一个过程名/标号时,获取转移地址的方式是间接的,而不是直接的。它先从数据段 ADD1
确定的偏移地址中取出要转移去执行的地方的偏移值,然后再转移到代码段的此地址中。所以,当 ADD1
为变量名, p1
为过程名时,如下两条指令的差别是很大的:
1 | CALL p1 |
前者是段 (CS) 内直接转移,后者是段内间接转移,其转移地址存放在数据段的 ADD1 处。
如果假设 p1
在代码段内的偏移值为 000AH
, ADD1
在数据段内的偏移值为 000AH
, 则上述两条指令在 DEBUG 下变为:
1 | CALL 000A ; 直接转移至代码段 00A 处 |
对于 CALL BX
指令,按照同样的道理,其转移至的代码段的偏移地址不是在 BX
中,而是在数据段的某个位置,这个位置由 BX
指出。[BX]
是普通的数据寄存器间接寻址,获取的数据单元的内容才是要转移至代码段内的偏移地址。所以如下两条指令虽然都是段内间接转移,但转移地址是不一样的:
1 | CALL BX ; 转移到的偏移地址位于 BX 中 |
段间直接寻址
与段内直接寻址不同的是,段间直接寻址在指令中给出了要转移至(由 JMP
和 CALL
完成)的地址的代码段和偏移值内容。例如,如果 p2
为由 FAR
属性定义的过程,则如下指令为段间直接转移:
1 | CALL FAR PTR p2 |
要转移至的标号或过程名必须具备 FAR
属性。
段间间接寻址
类似于段内间接寻址,但间接寻址时不能将要转移至的地址直接放入寄存器,而必须放入内存单元中,且是一个双字。格式如下:
1 | JMP DWORD PTR [BX+INTERS] |
其中 [BX+INTERS]
为数据的寄存器相对寻址方式,DWORD PTR
后可以是除立即寻址和寄存器寻址以外(也就是内存寻址)的任何一种方式。
内存单元中的转移地址是一个双字,低位在前高位在后。转移后,低位字变成 IP
,高位字变成 CS
。
指令系统
数据通路
8086 CPU 数据通路图示:
图中有 4 种实体,实体之间有 12 条 “可能” 的数据通路。其中 9 条实心箭头,对应 9 条数据通路。另有 3 条带 X
号的虚线箭头不是有效的数据通路。
根据数据通路定义,以下的九种 MOV
指令是合法的(其中 ac
为立即数, reg
为通用寄存器, segreg
为段寄存器, mem
为内存):
1 | MOV reg, ac |
注意: 使用段寄存器作为目的操作数时,不允许用 CS
(修改 CS
只能通过段间跳转指令)。
段超越
采用数据的寄存器间接寻址或跳转的寄存器寻址时,寄存器中存储的都是相对于某个段的偏移量。特定寻址场景、特定寄存器对应有默认的段(隐式,一般不需要写出):
- 数据访问:
BX
,SI
,DI
-->DS
BP
-->SS
- 转移指令:
CS
- 串指令的
DI
默认相对于ES
如需改变默认相对的段,则使用段超越,即在 []
前加 段寄存器:
以指定偏移量寄存器参考的段,例如 SS:[BX]
, CS:[DI]
。
类型转换
有时仅凭符号名/变量名很难准确知道内存操作数的类型。由于内存单元的基本单位是字节,所以无论变量如何定义,都可以进行类型转换。
通过 BYTE PTR
, WORD PTR
, DWORD PTR
三个操作符,明确地指定内存操作数的类型,或进行强制类型转换。
例如:
1 | MOV BYTE PTR x, AL ; 将 8 位的 AL 存入 x 对应的内存 |
在判断操作数与数据变量是否定义匹配时只检查变量名是否匹配,见下例:
1 | EXAMPLE_DATA SEGMENT PARA |
1 | MOV AL,EXM_BYTE ; AL=41H,match |
第1个语句两个操作数都是8位的,直接匹配。
第2个语句的两个操作数也都是8位的,直接匹配。
在第3个语句中,虽然EXM_BYTE+3的地址已到了EXMWORD的单元,但汇编器检查类型匹配时查看的是变量名,所以第3个语句的操作数类型仍直接匹配。
类似的道理,第5个语句的两个操作数的类型也直接匹配。
第4个语句通过变量名EXM_DWORD来访问一个字,所以必须加上WORD PTR的属性转换操作符
主要指令
-
传送指令
1
2MOV, XCHG, PUSH, POP, PUSHF, POPF
LEA, LDS, LES操作符:
1
2OFFSET, SEG ; 获取变量符号的偏移量和段地址
BYTE PTR, WORD PTR, DWORD PTR ; 类型转换 -
算术运算指令
1
2
3ADD, ADC, SUB, SBB, INC, DEC, CMP, NEG
MUL, IMUL, CBW
DIV, IDIV, CWD -
逻辑运算指令
1
2
3AND, OR, XOR, NOT
TEST
SHL, SHR, SAL, SAR, ROL, ROR, RCL, RCR -
控制转移指令
1
2
3
4
5
6JMP (short, near, word, far, dword)
JA/JB/JE 系列, JG/JL/JE 系列
LOOP, LOOPZ, LOOPNZ
CALL (near, word, far, dword)
RET, RETF
INT, IRET -
处理器控制指令
1
CLC, STC, CLI, STI, CLD, STD, NOT, HLT
-
其他指令
1
2LODS, STOS, MOVS, CMPS, SCAS, REP ; 串处理
IN, OUT
传送指令
1 | MOV, XCHG, PUSH, POP, PUSHF, POPF |
类型转换: 打破类型匹配约定, 按照希望的类型来寻址
BYTE PTR
,WORD PTR
,DWORD PTR
段超越: 打破操作数的段缺省约定, 转向指定的段来寻址
CS:
,DS:
,ES:
,SS:
MOV
指令
语法为 MOV DST, SRC
, 必须遵守数据通路和以下规则:
- 源和目的操作数必须类型匹配(8 位对 8 位,16 位对 16 位)
- 目的操作数不能是立即数
- 源和目的操作数不能同时为内存操作数(串指令除外)
- 源和目的操作数不能通识为段寄存器
以下指令是错误的:
1 | MOV AX, BL ; 将 BL 赋值给 AX, 扩展成 16 位 -- 类型不匹配 |
类型转换:
1 | MOV BYTE PTR x, AL |
MOV
指令的运用(也称 MOV
体操):
- 遵循寻址方式规则: 在哪里,怎么存取,access
- 遵循数据通路规则: 可以 / 不可以
- 注意默认段 (
DS
,SS
) 和段超越 - 类型匹配和类型转换:
DB
,DW
,DD
的范围, 相互如何 access
MOV
指令不改标志位,只有运算指令改标志位。
交换指令 XCHG
格式为 XCHG OPR1, OPR2
,使两个操作数互换。不允许任何一个操作数是立即数。
示例:
1 | XCHG BX, [BP+SI] ; 交换寄存器 BX 与堆栈段 SS:[BP+SI] 的操作数 |
堆栈指令
包括 PUSH
, POP
, PUSHF
, POPF
。示例:
1 | PUSH SRC ; SP=SP-2, SS:[SP]<-SRC |
其中 SRC 和 DST 都可以是寄存器及内存操作数。
其他传送指令
1 | LEA reg, src ; 将源操作数 src 的偏移地址送入 reg 中 |
上述三条指令中的 reg 不能是段寄存器。
LEA
指令: 获取 src 的偏移地址。LDS
和LES
获取的是该单元处的双字内容,不是地址。LDS
: 将低字送入 src, 高字送入DS
寄存器LES
与LDS
类似,高字使用ES
寄存器- 使用
LDS
及LES
时,src 处保存的双字通常是某个形如seg:offset
的逻辑地址。
取地址还是取内容
当变量名(使用 DW
, DB
, DD
等伪指令定义的变量)直接位于 MOV
等指令中时,都是直接寻址,得到的是变量的内容。如果需要得到该变量地址,需要使用 LEA
指令。
1 | x DW ? ; 假定 x 在 DATA 段内,偏移地址为 000AH, 内容为 1234H |
OFFSET
和 SEG
操作符也可用于获取地址。
1 | MOV BX, OFFSET x ; 与 LEA BX, x 相同, 获取 x 的偏移地址 |
传送指令示例
数据段:
1 | X1 EQU 100 |
内存图 ( 地址:数据
):
DS: 0000: 34 X2
0001: 12
0002: 78 X3
0003: 56
0004: 00
0005: 20
指令示例:
1 | MOV X2, X1 ; 源: 立即寻址, 目的: 直接寻址 |
另一组示例
数据段:
1 | X1 DW 2000h |
内存图 ( 地址:数据
):
DS: 0000: 00 X1
0001: 20
0002: 31 X3
0003: 78 X4
0004: 56
0005: 34
0006: 12
0007: ?? X5
0008: ??
0009: ??
000A: ??
指令示例 (将 X4
的值赋给 X5
):
1 | LEA DI, X5 ; DI = &X5 |
一些错误的指令:
1 | MOV X5, X4 ; 错误, 不能内存操作数直接赋值 |
指令示例 (将 X3
的值赋给 X5
, X5
高位置零):
注意 X3
是 BYTE
, X5
是双字 DD
。
采用直接寻址:
1 | ; 搬运最低位 |
其中置零 X5
的高字也可以写成
1 | XOR AX, AX ; 将 AX 清零 |
仍然是 X3
赋给 X5
, 采用间接寻址:
1 | MOV BX, OFFSET X5 ; BX=0007h, 取地址 |
注意: 上述代码中的 BX
不能换成 BP
, 因为 BP
默认相对 SS
寻址,破坏堆栈段且 X5
没有改变。
算术运算指令
加减法指令
1 | ADD dst, src ; dst += src |
算术运算影响符号位:
ZF
: 如果运算结果为零则ZF=1
SF
: 等于运算结果 dst 的最高位, 即符号位CF
: 加法有进位或减法有借位, 则CF=1
OF
: 若操作数符号相同且相加结果符号与操作数相反, 则OF=1
加法举例
1 | ; 数据定义 |
减法举例
1 | ; 数据定义 |
求补和比较
1 | NEG opr ; opr = -opr |
指令 | 影响的标志位 | 标志位含义及设置条件 | 相关的J类跳转指令 |
---|---|---|---|
CMP |
ZF |
零标志位 - 如果操作结果为零,则设置 | JE , JZ (如果 ZF 设置,则跳转,即操作结果为零) |
SF |
符号标志位 - 如果操作结果为负数,则设置 | 无直接相关,但可辅助其他标志位确定有符号比较的结果 | |
OF |
溢出标志位 - 如果有符号整数溢出,则设置 | 无直接相关,但可辅助确定有符号操作是否溢出 | |
AF |
辅助进位标志位 - 如果在低四位之间有进位或借位,则设置 | 无直接相关,但可辅助确定是否在低四位有进位或借位 | |
CF |
进位标志位 - 如果最高位产生了进位或借位,则设置 | JB , JNAE , JC (如果 CF 设置,则跳转,即有进位或借位) |
|
JA , JNBE (如果 CF 未设置且 ZF 未设置,则跳转,即无进位且结果非零,无符号大于) |
|||
JAE , JNB , JNC (如果 CF 未设置,则跳转,即无进位,无符号大于等于) |
|||
JBE , JNA (如果 CF 设置或 ZF 设置,则跳转,即有进位或结果为零,无符号小于等于) |
|||
PF |
奇偶标志位 - 根据操作结果的低八位的奇偶性设置 | 无直接相关,但可辅助确定结果的奇偶性 |
乘除法
无符号乘 MUL
- 字节操作数: 8 位 x 8 位,
AX = AL * src
- 字操作数: 16 位 x 16 位,
DX:AX = AX * src
其中 src
为 8 位或 16 位的 reg 或 mem, 不能是立即数。
无符号除法 DIV
- 字节操作数:
AX / src
, 商在AL
, 余数在AH
- 字操作数:
DX:AX / src
, 商在AX
, 余数在DX
同理,src
不能是立即数。
举例:
1 | MUL AL ; AX = AL * AL |
举例 Y=X*10
1 | X DW ? |
举例 X=Y/10
1 | X DW ? |
注意这里不能用 8 位除法,否则会溢出。
逻辑运算
1 | AND dst, src ; dst &= src |
可以用这些指令实现组合/屏蔽/分离/置位,例如:
1 | AND AL, 0FH ; 清零高 4 位 |
移位指令
1 | SHL dst, count ; 逻辑左移 |
注意: count
只能为 1
或 CL
上述结论来自熊老师的课本P51,但网上说count可以为int8的立即数,存疑
示例 X = X * 10
, X = (X << 3) + (X << 1)
1 | X DW ? |
示例 双字 X
,X << 4
1 | X DD ? |
需要注意CF所在的位置是转移方向的最高位,而不是数据的最高位,具体见下方图例
转移指令
条件转移
指令 | 别称 | 含义 | 无符号比较条件 | 有符号比较条件 | 标志位依据 |
---|---|---|---|---|---|
JA |
JNBE |
跳转如果高于 | 无符号大于(CF=0 且ZF=0 ) |
CF , ZF |
|
JAE |
JNB 、JNC |
跳转如果高于等于 | 无符号大于等于(CF=0 ) |
CF |
|
JE |
JZ |
跳转如果等于 | 等于(ZF=1 ) |
等于(ZF=1 ) |
ZF |
JBE |
JNA |
跳转如果低于等于 | 无符号小于等于(CF=1 或ZF=1 ) |
CF , ZF |
|
JB |
JNAE 、JC |
跳转如果低于 | 无符号小于(CF=1 ) |
CF |
|
JNE |
JNZ |
跳转如果不等于 | 不等于(ZF=0 ) |
不等于(ZF=0 ) |
ZF |
JG |
跳转如果大于 | 有符号大于(ZF=0 且SF=OF ) |
ZF , SF , OF |
||
JL |
跳转如果小于 | 有符号小于(ZF=0 且SF≠OF ) |
ZF , SF , OF |
- 无符号比较:
JA
/JB
/JE
系列 (Above / Below / Equal) - 有符号比较:
JG
/JL
/JE
系列 (Greater / Less / Equal)
指令格式: JX 标号
,比较的依据是标志位(紧跟 CMP
指令)。标号位于指令前面,实质是一个段内偏移值。
无符号数的条件转移指令:
JA
(JNBE
): 无符号高于时转移JAE
(JNB
/JNC
): 无符号高于等于时转移 (CF=0
时转移)JE
(JZ
) 等于时转移 (ZF=1
时转移)JBE
(JNA
): 无符号低于等于时转移JB
(JNAE
/JC
): 无符号低于时转移 (CF=1
时转移)JNE
(JNZ
): 不等于时转移 (ZF=0
时转移)
示例 求 Z=|X-Y|
,X
, Y
, Z
都是无符号数。
1 | MOV AX, X |
循环指令
格式: LOOP 标号
。
LOOP
: 先 CX -= 1
, 然后当 CX
不为零时转移。(与循环体无关,只是个转移指令)
JCXZ
: 当 CX=0
时转移(不执行 CX -= 1
)
示例
1 | MOV CX, 4 |
无条件转移指令
JMP
指令, 格式: JMP 标号|寄存器操作数|内存操作数
- 段内直接短转移:
JMP SHORT PTR 标号
, EA 是 8 位 - 段内直接转移:
JMP NEAR PTR 标号
- 段内间接转移:
JMP WORD PTR 寄存器或内存
- 段间直接转移:
JMP FAR PTR 标号
- 段间间接转移:
JMP DWORD PTR 寄存器或内存
子程序调用
CALL
指令: 子程序调用RET
指令: 从子程序中返回
CALL
指令:
- 段内直接调用:
CALL dst
SP=SP-2
,SS:[SP]
: 返回地址偏移值IP=IP+dst
- 段内间接调用:
CALL dst
SP=SP-2
,SS:[SP]
: 返回地址偏移值IP=*dst
- 段间直接调用:
CALL dst
SP=SP-2
,SS:[SP]
: 返回地址段值SP=SP-2
,SS:[SP]
: 返回地址偏移值IP=OFFSET dst
CS=SEG dst
- 段间间接调用:
CALL dst
SP=SP-2
,SS:[SP]
: 返回地址段值SP=SP-2
,SS:[SP]
: 返回地址偏移值IP
为EA
的低 16 位CS
为EA
的高 16 位
段内调用,dst
应为 NEAR PTR
, 段间调用则为 FAR PTR
。
示例
1 | CALL P1 ; 段内直接调用 P1, P1 为 NEAR |
RET
指令:
- 段内返回:
RET
IP=[SP]
,SP=SP+2
- 段内带立即数返回:
RET exp
IP=[SP]
,SP=SP+2
SP=SP+exp
- 段间返回:
RET
IP=[SP]
,SP=SP+2
CS=[SP]
,SP=SP+2
- 段间带立即数返回:
RET exp
IP=[SP]
,SP=SP+2
CS=[SP]
,SP=SP+2
SP=SP+exp
过程的定义
1 | 过程名 PROC [near | far] |
应用举例
将内存中的值 x 显示为 10 进制
1 | MOV AX, X ; 取 AX |
以上代码也可以写成循环(次数为 4),不如展开了效率高
程序结构
三段式程序结构
堆栈段, 数据段, 程序段。
定义堆栈段:
1 | STACK SEGMENT PARA STACK |
定义数据段:
1 | DATA SEGMENT PARA |
定义段
伪指令: SEGMENT
和 ENDS
格式:
段名 SEGMEMT [对齐类型] [组合类型] [类别名]
; 本段中的程序和数据定义语句
段名 ENDS
对齐类型: 定义了段在内存中分配时的起始边界设定
PAGE
: 本段从页边界开始,一页为 256BPARA
: 本段从节边界开始,一节为 16BWORD
: 本段从字对齐地址(偶地址)开始,段间至多 1B 空隙BYTE
: 本段从字节地址开始,段间无空隙
组合类型: 确定段与段之间的关系
STACK
: 该段为堆栈段的一部分。链接器链接时,将所有同名的具有STACK
组合类型的段连成一个堆栈段,并将SS
初始化为其首地址,SP
为段内最大偏移。(正确定义段的STACK
属性可以在主程序中省略对SS
和SP
的初始化)
定义过程
伪指令: PROC
, ENDP
, END 标号
格式:
过程名 PROC [NEAR|FAR]
; 过程代码
RET
过程名 ENDP
- 如果过程为
FAR
则RET
被编译为RETF
(段间返回) RET 2n
: 在RET
或RETF
后SP+=2n
END 标号
为程序总结束, 标号为入口(被设置为初始的 CS:IP
值)
定义数据
格式:
变量名 伪指令 值
用 DB
, DW
, DD
伪指令定义内存中的变量,变量名对应内存地址。
用 EQU
伪指令定义常量,不占内存,变量名被翻译成立即数。
值的表示:
- 常数
DUP
重复操作符(用于定义数组,或定义堆栈空间)?
不预置任何内容- 字符串表达式
- 地址表达式
$
当前位置计数器
DUP
表达式
1 | ARRAY1 DB 2 DUP (0, 1, 0FFH, ?) |
其中 ARRAY1
定义了一个二维数组,等价于
1 | ARRAY1 DB 0, 1, 0FFH, ?, 0, 1, 0FFH, ? |
ARRAY2
定义了一段长度为 100 字节的连续空间,值未初始化。
DUP
表达式可以嵌套,相当于定义多维数组。
1 | ARRAY3 DB 2 DUP(1, 2 DUP ('A', 'B'), 0) ; 'A', 'B' 为字符 ASCII 码 41H 和 42H |
对应的内存图:
ARRAY3+ 0000: 01H
0001: 41H
0002: 42H
0003: 41H
0004: 42H
0005: 00H
0006: 01H
0007: 41H
0008: 42H
0009: 41H
000A: 42H
000B: 00H
地址表达式
使用 DW
及 DD
伪指令后跟标号,表示变量的偏移地址或逻辑地址。使用 DD
存储逻辑地址时,低字为偏移地址,高字为段地址。
当前位置计数器: 用 $
表示,为当前标号所在的偏移地址。
1 | X1 DW ? ; X1 内容未定义 |
字符串表达式
1 | STR1 DB 'ABCD', 0DH, 0AH, '$' |
其中 STR1
分配了 7 个单元(字节),按顺序存放。其中 $
为字符串的结束(使用 DOS 9 号功能调用输出字符串,结果为 ABCD\r\n
)
STR2
分配了两个字(4 个字节),按顺序其值分别为 42H
, 41H
, 44H
, 43H
。对于 DW
和 DD
伪指令,不允许使用两个以上字符的字符串作为其参数。
初始化寄存器
1 | MOV AX, STACK |
ASSUME
伪指令: 告诉汇编器,将 CS
, DS
, SS
分别设定成相应段的首地址(仅仅是"设定",并没有真正将地址存入段寄存器,仍然需要使用上述几条指令来初始化段寄存器)
1 | ASSUME CS:CODE, DS:DATA, SS:STACK |
HELLOWORLD示例
1 | STACK SEGMENT PARA STACK |
字符串处理
串操作指令
使用方法
串操作隐含着间接寻址: DS:[SI] --> ES:[DI]
使用串指令的常见过程:
- 设置
DS
,SI
(源串),ES
,DI
(目的串)DS
和ES
是段寄存器,不能直接用MOV ES, DS
赋值- 借助其他寄存器 (
MOV AX, DS
,MOV ES, AX
) 或堆栈 (PUSH DS
,POP ES
)
- 设置
DF
标志 (Direction Flag)- 通过
CLD
(DF=0
) 和STD
(DF=1
) 指令 DF=0
则每次SI/DI++
(或+=2
),DF=1
则每次SI/DI--
(或-=2
)
- 通过
- 选用重复执行指令
REP*
以及设置重复次数CX
- 重复指令包括无条件重复
REP
, 有条件重复REPE/REPZ
,REPNE/REPNZ
CX
表示最大的重复次数- 重复的串指令每执行一次则
CX--
- 重复指令包括无条件重复
指令种类
串指令种类:
- 取串指令
LODSB
/LODSW
- 将源串
DS:[SI]
的一个字节或字取到AL
或AX
,同时按照DF
修改SI
的值。 LODSB
取字节,LODSW
取字
- 将源串
- 存串指令
STOSB
/STOSW
- 将
AL
中的一个字节或AX
的一个字存入目的串ES:[DI]
,并根据DF
修改DI
。 STOSB
存字节,STOSW
存字
- 将
- 串传送指令
MOVSB
/MOVSW
- 将源串
DS:[SI]
的字节或字传送到目的串ES:[DI]
,并根据DF
修改SI
及DI
。 MOVSB
传送字节,MOVSW
传送字
- 将源串
- 串比较指令
CMPSB
/CMPSW
- 比较源串
DS:[SI]
与目的串ES:[DI]
的一个字节或字。执行完后根据DF
修改SI
及DI
。 - 用源串的字节或字减去目的串的字节或字,影响标志寄存器
CF
,SF
,ZF
。 - 相当于
CMP DS:[SI], ES:[DI]
- 后面往往跟着条件转移指令
- 比较源串
- 串扫描指令
SCASB
/SCASW
- 在目的串
ES:[DI]
中扫描是否有AL
或AX
指定的字节或字。执行完后根据DF
修改SI
及DI
。 - 相当于
CMP AL/AX, ES:[DI]
- 比较结果不保存,只影响标志寄存器。
- 在目的串
重复前缀指令 REP
:
- 格式:
REP 串操作指令
,例如REP MOVSB
- 每执行一次串操作,
CX--
,直到CX
为零时停止。
条件重复前缀指令 REPE
/ REPZ
, REPNE
/ REPNZ
-
格式与
REP
指令相似 -
每执行一次,
CX--
,当CX
等于零或ZF
不满足条件时停止。 -
REPE
/REPZ
: 当CX != 0
且ZF=1
时执行串操作并CX--
-
REPNE
/REPNZ
: 当CX != 0
且ZF=0
时执行串操作并CX--
修改 DF
方向标志指令
CLD
:DF=0
,执行串操作后SI
和DI
增加(根据字节或字操作决定+1
还是+2
)STD
:DF=1
,执行串操作后SI
和DI
减少(根据字节或字操作决定-1
还是-2
)
基本应用
-
扫描长度为
LEN
的串STRING1
中是否含有字母A
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16PUSH ES
PUSH DS
POP ES ; ES = DS
MOV DI, OFFSET STRING1
MOV CX, LEN
MOV AL, 'A'
CLD
REPNZ SCASB
; while (CX != 0 && AL != ES:[DI]) CMP AL, ES:[DI] / SI++,DI++
JZ FOUND ; 找到
POP ESREPNZ SCASB
停下来后,如果ZF=1
则AL=ES:[DI]
,通过CX
的值可以看出A
在STRING1
中的位置。如果ZF=0
则已经遍历到最后仍未找到。
综合应用
- 在缓冲区中查找
\r\n
(0DH
,0AH
) 并将其删掉(将若干行文本拼接成不换行的文本) - 在缓冲区查找换行符
0AH
并将其补成回车换行0DH, 0AH
。
调试器
调试程序: DEBUG.EXE HELLO.EXE
常用调试命令:
命令 | 功能描述 | 使用示例 |
---|---|---|
d |
显示内存单元内容 | d 显示从 CS:IP 开始的内存内容 |
d DS:0000 显示DS段地址0000开始的内存内容 |
||
e |
修改内存单元内容,每次修改一个字节,可以连续修改多个字节 | e 078A:0000 修改地址078A:0000开始的内存内容 |
r |
查看和修改寄存器 | r AX 查看或修改AX寄存器的值 |
u |
反汇编,查看机器码对应的汇编程序 | u 反汇编从 CS:IP 开始的机器码 |
u DS:0000 反汇编DS段地址0000开始的机器码 |
||
a |
修改汇编指令,可以连续修改多条指令 | a 修改 CS:IP 指令 |
a 078A:0000 修改078A:0000地址的汇编指令 |
||
t |
执行一条指令,同 Step In | t 单步执行当前指向的指令 |
p |
执行完子程序/循环/功能调用,同 Step Over | p 执行到下一个断点或程序结束 |
g |
执行到某个指令地址处,相当于断点,不加参数则执行到结束 | g 078A:0000 执行到078A:0000地址处的指令 |
l |
装入文件 | l myfile.asm 装入名为myfile.asm的文件 |
w |
写回文件 | w myoutput.asm 将修改后的内容写回到myoutput.asm文件 |
q |
退出调试器 | q 退出当前的调试会话 |
- Title: x86-construction
- Author: Charles
- Created at : 2024-05-04 09:25:12
- Updated at : 2024-05-11 14:01:39
- Link: https://charles2530.github.io/2024/05/04/x86-construction/
- License: This work is licensed under CC BY-NC-SA 4.0.