汇编语言

寄存器

  1. 对于8086、8088或80286,阴影区域是不可用的

  2. FS和GS寄存器无专用名称

  1. 通用寄存器

    • 数据寄存器:AX(accumulator)、BX(base)、CX(count)、DX(data)
    • 指针寄存器:SP(stack pointer)、BP(base pointer)
    • 变址寄存器:DI(destination index)、SI(source index)
  2. 控制寄存器

    • IP(instruction pointer)、FLAGS
  3. 段寄存器

    • CS(code segment)、DS(data segment)、ES(extra segment)、SS(stack segment)
  4. 标志寄存器

    • OF溢出标志,运算过程溢出置1,否则置0
    • SF符号标志,结果为负置1,否则置0
    • ZF零标志,运算结果为0置1,否则置0
    • CF进位标志,e.g.加法运算中,有进位置1,否则置0
    • AF辅助进位标志,记录运算时第三位产生的进位值
    • PF奇偶标志。结果操作数中1个数为偶置1,否则置0

标志位

标志名 标志为1 标志为0
OF 溢出(是/否) OV NV
DF 方向(减量/增量) DN UP
IF 中断(允许/关闭) EI DI
SF 符号(负/正) NG PL
ZF 零(是/否) ZR NZ
AF 辅助进位(是/否) AC NA
PF 奇偶(偶/奇) PE PO
CF 进位(是/否) CY NC

80x86寻址方式

与数据有关的寻址方式

  1. 立即寻址方式(immediate addressing)

    • MOV AL,5
    • MOV AX,3064H
    • MOV EAX,12345678H
  2. 寄存器寻址方式(register addressing)

    • MOV AX,BX
    • MOV ECX,EDX
  3. 直接寻址方式(direct addressing)

    • MOV AX,[2000H]
    • MOV AX,VALUE等价MOV AX,[VALUE]
    • MOV AX,ES:VALUE(段跨越前缀,默认为DS)
  4. 寄存器间接寻址方式(register indirect addressing)

    • MOV AX,[BX]
    • MOV AX,ES:[BX](段跨越前缀,默认为DS)

    使用BP、SP时,默认段位SS。其他寄存器的默认段位DS寄存器。

  5. 寄存器相对寻址方式(register relative addressing)(或称直接变址寻址方式)

    • MOV AX,COUNT[SI]等价于MOV AX,[COUNT+SI]
    • MOV DL,ES:STRING[SI]
  6. 基址变址寻址方式(based indexed addressing)

    • MOV AX,[BX][DI]等价于MOV AX,[BX+DI]
    • MOV AX,ES:[BX][DI]
  7. 相对基址变址寻址方式(relative based indexed addressing)

    • MOV AX,MASK[BX][SI]
  8. 比例变址寻址方式(scaled indexed addressing)

  9. 机制比例变址寻址方式(based scaled indexed addressing)

  10. 相对基址比例变址寻址方式(relative based scaled index addressing)

与转移地址有关的寻址方式

  1. 段内直接寻址(intrasegment direct addressing)
  2. 段内间接寻址(intrasegment indirect addressing)
  3. 段间直接寻址(intersegment direct addressing)
  4. 段间间接寻址(intersegment indirect addressing)

指令

数据传送指令

通用数据传送指令

  1. MOV

    • MOV DST,SRC将SRC内容传送至DST

    双操作数指令不允许两个操作数都使用存储器,必须有一个是寄存器

    DST不允许是立即数、不允许是CS寄存区(代码段)

  2. MOVSX

    • MOVSX DST,SRC,带符号拓展传送指令
  3. MOVSX

    • MOVZX DST,SRC,带符号拓展传送指令
  4. PUSH

    • PUSH SRC:先移动指针,后压入数据(所以压完数据后,指针是指向着该数据的)
  5. POP

    • POP DST:先弹出数据,后移动指针
  6. PUSHA/PUSHAD,所有寄存器进栈指令

  7. POPA/POPAD,所有寄存器出栈指令

  8. XCHG

    • XCHG OPR1,OPR2,由于是双操作数指令,必须有一个在寄存器中

累加器专用传送指令

  1. IN
  2. OUT
  3. XLAT

地址传送指令

  1. LEA
    • LEA REG,SRC,指令把原操作数的有效地址送到指定的寄存器中(用于获得地址的)
  2. LDS、LES、LFS、LGS、LSS

标志寄存器传送指令

  1. LAHF、SAHF、PUSHF/PUSHFD、POPF/POPFD

类型转换指令

  1. CBW,字节转换为字
  2. CWD/CWDE,字转换为双字
  3. CDQ,双字转换为4字
  4. BSWAP,字节转换为字指令

算术指令

加法指令

ADD、INC常用

除INC外,其余指令影响标志位

  1. ADD
    • ADD DST,SRC
  2. ADC
    • ADC DST,SRC,带进位加法
  3. INC
    • INC OPR,加一
  4. XADD
    • XADD DST,SRC
    • 执行操作:TEMP←(SRC)+(DST)(SRC)←(DST)(DST)←TEMP

加法指令(除INC指令)会影响标志位,CF位可表示无符号数的溢出,OF位可表示带符号数的溢出

故,若是无符号数的加法运算下,CF=1,则运算错误;若是带符号数的加法运算,OF=1,运算错误。

INC不会影响CF标志位

减法指令

SUB、DEC、CMP常用

除DEC外,其余指令均影响CF标志位

  1. SUB
    • SUB DST,SRC,执行(DST)←(DST-SRC)操作
  2. SBB
    • SBB DST,SRC,带借位减法指令,执行(DST)←(DST)-(SRC)-CF操作
  3. DEC
    • DEC OPR,减一指令,执行(OPR)←(OPR)-1操作
  4. NEG
    • NEG OPR,求补指令,执行(OPR)←-(OPR)操作(或表示为(OPR)←0FFFH-(OPR)+1
  5. CMP
    • CMP DST,SRC,执行(DST-SRC)操作,不保存结果,但影响标志位CF,后常接跳转指令

CF位表示无符号数的溢出,OF代表带符号位数的溢出。

故,减法中:无符号数减法,被减数-减数 < 0,CF设置为1(“不够减”),否则为0;带符号数减法,若被减数与减数符号相同,减后结果符号与被减数(或减数)相反,则OF设置为1,其余情况为0。

乘法指令

  1. MUL
    • MUL SRC,无符号乘法,执行操作如下:
      • 字节操作数:(AX)←(AL)*(SRC)
      • 字操作数:(DX,AX)←(AX)*(SRC)
      • 双字操作数:(EDX,EAX)←(EAX)*(SRC)
    • 执行操作视SRC类型而定
  2. IMUL
    • IMUL SRC,带符号乘法,执行操作与MUL指令一致

以使用MUL指令、SRC是AL为例,若结果(AX)的高8位为0,则CF位和OF位均为0,否则CF、OF位均设置为1

乘法指令中,CF、OF标志位可用于检查字节相乘的结果是字节还是字字相乘的结果是字还是双字、……

除法指令

  1. DIV
    • DIV SRC,无符号数除法,执行操作如下:
      • 字节操作:(AL)←(AX)/(SRC)的商(AH)←(AX)/(SRC)的余数
      • 字操作:(AX)←(DX,AX)/(SRC)的商(DX)←(DX,AX)/(SRC)的余数
      • 双字操作:(EAX)←(EDX,EAX)/(SRC)的商(EDX)←(EDX,EAX)/(SRC)的余数
  2. IDIV
    • IDIV SRC,带符号数除法,执行操作同上

逻辑指令

逻辑运算指令

NOT的操作数不能是立即数,其余指令DST必须是寄存器

  1. AND
    • AND DST,SRC,逻辑与,执行(DST)←(DST)⋀(SRC)操作
  2. OR
    • OR DST,SRC,逻辑或,执行(DST)←(DST)⋁(SRC)操作
  3. NOT
    • NOT OPR,逻辑非,执行(DST)←(OPR)的非操作
  4. XOR
    • OR DST,SRC,异或,执行(DST)←(DST)与(SRC)的异或操作
  5. TEST
    • TEST OPR1,OPR2,测试,执行(DST)⋀(SRC)操作

位测试并修改指令(略)

  1. BT
  2. BTS
  3. BTR
  4. BTC

位扫描指令(略)

  1. BSF
  2. BSR

移位指令

  1. 移位指令

    • SHL

    • SAL

    • SHR

    • SAR

  2. 循环移位指令

    • ROL

    • ROR

    • RCL

    • RCR

  3. 双精度移位指令

    • SHLD

    • SHRD

串处理指令(略)

  1. MOVS
  2. CMPS
  3. SCAS
  4. LODS
  5. STOS
  6. INS
  7. OUTS

与上述配合使用前缀有

  1. REP
  2. REPE/REPZ
  3. REPNE/REPNZ

控制转移指令

无条件转移指令

JMP跳转指令

  1. JMP SHORT OPR
  2. JMP NEAR PTR OPR

条件转移指令

条件设置指令(略)

循环指令

子程序

汇编语言程序(练习)

把BX寄存器内的二进制数以十六机制的形式在屏幕上打印

  1. print_loop中:

    • rol bx, 4会将BX循环左移4位,比如原先为0010,1010,1111,0011B则变为1010,1111,0011,0010B,也就是原先的最高的4位到了BX的末尾的4位上

    • 下一步——使用mov al, bl指令将bl内容移动至al(bl是bx的后八位)

    • 由于一个十六进制数需要4位进行表示,所以此时al中的高4位是不需要的,使用and al, 0Fh指令对高四位进行消除

    • 处理得到0000????这样一个低四位有效的al后,要判断al是属于0-9还是A-F

    • al的数值有效范围是0-15,若是0-9,直接打印(后续加上’0’即可)

    • 若是10-15,先加上7(这样的话,后续也加上’0’,就能对应上ASCII的’A’-‘F’)

  2. 关于在屏幕上打印(固定语句)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
prognam segment           ; 定义一个名为 prognam 的段(Segment)
main proc far ; 声明一个远程(far)过程 main
assume cs:prognam ; 告诉汇编器:此时 CS 寄存器指向 prognam 段

start:
; ---- 初始化 DS ----
push ds ; 保存原 DS
mov ax, cs ; AX ← CS(当前段选择子)
mov ds, ax ; DS ← AX,使 DS 与 CS 同段

; ---- 将 BX 设为要显示的 16 位二进制数 ----
mov bx, 0010101011110011B
; BX ← 二进制 0010?1010?1111?0011?(0x2AF3)

mov cx, 4 ; CX ← 4,表示要输出 4 个半字节(nibble)

print_loop:
rol bx, 4 ; BX 循环左移 4 位,下一个半字节移到 BL 低 4 位
mov al, bl ; AL ← BX 的低字节
and al, 0Fh ; AL ← AL AND 0x0F,只保留最低 4 位

cmp al, 9 ; 判断当前值是否 ≤ 9
jbe is_digit ; 如果是数字(0–9),跳到数字处理
add al, 7 ; 否则(10–15),先加 7 使其对应到 A–F

is_digit:
add al, '0' ; AL += '0',转换为 ASCII 字符

mov dl, al ; DL ← 要打印的 ASCII 字符
mov ah, 02h ; DOS 中断 21h 功能号 2:打印 DL 中的字符
int 21h

dec cx ; CX--,处理完一个半字节
jnz print_loop ; 如果 CX ≠ 0,则继续循环

; ---- 恢复 DS 并退出 ----
pop ds ; 恢复原 DS
mov ah, 4Ch ; DOS 中断 21h 功能号 4Ch:程序退出
mov al, 00 ; 返回码 0
int 21h

main endp ; 过程结束
prognam ends ; 段结束
end start ; 程序入口(指向 start 标签)

从键盘接受十六进制数并存入BX

  1. 关于从屏幕输入
  2. 关于输入的数字的判断——判断是属于’0’-‘9’还是’A’-‘F’(后续还可以拓展为’a’-‘f’)
    • 首先我们从屏幕输入的都是ASCII,比如输入的是’0’,实际上al中的十进制值是48,故减去48以使其范围落到数值0-9(若不是后续判断)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
prognam segment           ; 定义代码段
main proc far ; 定义远程过程 main
assume cs:prognam ; 告诉汇编器:CS 段寄存器关联 prognam

start:
; ---- 初始化 DS 段 ----
push ds ; 保存 DS
mov ax, cs ; 将 CS 的值赋给 AX
mov ds, ax ; 设置 DS ← CS

; ---- BX 初始化为 0 ----
mov bx, 0 ; 用于存储输入的十六进制数

newchar:
mov ah, 1 ; DOS 功能号 1:带回显读取字符
int 21h ; 调用 DOS 中断,输入字符保存在 AL 中

sub al, 30h ; AL -= '0',尝试转为数字 0–9
jl exit ; 如果小于 0,非法输入 → 退出
cmp al, 10
jl add_to ; 如果 AL < 10,说明是 0–9 → 继续处理

sub al, 7 ; AL -= ('A' - 10) = 0x41 - 0x0A = 0x37
; 所以等价于:AL ← AL - 27h

cmp al, 0Ah ; 检查是否是合法十六进制字母(A–F)
jl exit ; 小于 A → 非法 → 退出
cmp al, 10h
jge exit ; 大于 F → 非法 → 退出

add_to:
mov cl, 4 ; 每次移位 4 位,为新位腾出空间
shl bx, cl ; BX ← BX << 4,相当于“进位”
xor ah, ah ; 清除 AH,确保 AX = AL(16 位加法安全)
add bx, ax ; BX += AL,即追加新的十六进制位

jmp newchar ; 继续读取下一个字符

exit:
pop ds ; 恢复原 DS(可选,但良好习惯)
mov ah, 4Ch ; DOS 功能号 4Ch:正常退出程序
mov al, 00 ; 返回代码 0
int 21h

main endp ; 过程结束
prognam ends ; 段结束
end start ; 程序入口

往有序数组中插入数据

  1. 已知是一个有限数组,要求插入一个数据,思路是从后向前找
  2. 逐个比较,每次比较时,若待插入数小于当前数,则将当前位置的数后移
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
datarea segment
x dw ?
array_head dw 3,5,15,23,37,49,52,65,78,99
array_end dw 105
n dw 32
datarea ends

prognam segment

main proc far
assume cs:prognam,ds:datarea
start:
push ds
sub ax,ax
push ax

mov ax,datarea
mov ds,ax

mov ax,n
mov array_head-2,0ffffh ;0ffffh的0是因为十六进制开头添加
mov si,0

compare:
cmp array_end[si],ax
jle insert
mov bx,array_end[si]
mov array_end[si+2],bx
sub si,2
jmp short compare
insert:
mov array_end[si+2],ax
ret
main endp
prognam ends
end start

冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
datarea segment
a dw 100,30,78,99,15,-1,66,45,189,255
datarea ends

prognam segment
main proc far
assume cs:prognam,ds:datarea

start:
push ds
sub ax,ax
push ax

mov ax,datarea
mov ds,ax

mov cx,10 ;设置循环次数
dec cx ; 10个数,需9次即可

loop1:
mov di,cx
mov bx,0
loop2:
mov ax,a[bx]
cmp ax,a[bx+2]
jge continue
xchg ax,a[bx+2]
mov a[bx],ax
continue:
add bx,2
loop loop2

mov cx,di

loop loop1
ret
main endp
prognam ends
end start

例5.5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
datarea segment
x dw 1,2,3,4,5,6,7,8,9,-3
y dw 9,8,-7,-6,-5,4,-3,-2,1,13
z dw 0,0,0,0,0,0,0,0,0,0
logic_rule dw 00dch ;逻辑尺,为
datarea ends

prognam segment
main proc far
assume cs:prognam,ds:datarea

start:
push ds
sub ax,ax
push ax

mov ax,datarea
mov ds,ax

mov bx,0
mov cx,10
mov dx,logic_rule
next:
mov ax,x[bx]
shr dx,1
jc subtract
add ax,y[bx]
jmp short result
subtract:
sub ax,y[bx]
result:
mov z[bx],ax
add bx,2
loop next
ret
main endp
prognam ends
end start

折半查找(二分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
data segment
array dw 12,11,22,33,44,55,66,77,88,99,111,222,333 ; array[0] 存个数,后面是有序数组
number dw 55 ; 要查找的目标数
low_inx dw ? ; 保存 low index
high_inx dw ? ; 保存 high index
data ends

code segment

main proc far
assume cs:code, ds:data

start:
push ds
sub ax, ax
push ax

mov ax, data
mov ds, ax

lea di, array ; DI ← 数组首地址
mov cx, [di] ; CX ← 元素个数(不含 array[0])
mov ax, number ; AX ← 要查找的目标数

mov low_inx, 1 ; low index ← 1(跳过 array[0])
mov high_inx, cx ; high index ← 元素个数

binary_search:
mov cx, low_inx
cmp cx, high_inx
ja not_found ; if low > high,说明找不到

mov cx, low_inx
add cx, high_inx
shr cx, 1 ; CX ← mid index

mov si, cx
mov bx, si
shl bx, 1 ; bx ← si * 2(字节偏移)
add bx, di ; bx ← 元素地址

mov dx, [bx] ; DX ← 当前比较值
cmp ax, dx
je found ; 如果相等,跳转

jb less_than ; 如果 ax < array[si],搜索左半边

; 否则搜索右半边
inc si
mov low_inx, si
jmp binary_search

less_than:
dec si
mov high_inx, si
jmp binary_search

not_found:
stc ; 未找到,CF = 1
jmp finish

found:
clc ; 找到了,CF = 0
shl si, 1 ; 转换为字节偏移
; si 即偏移地址(从 array 起)
finish:
ret

main endp

code ends
end start

已做实验

1.1:在数据段DATA中有两个字数据X和Y, 假设X=1122H,Y=3344H, 编程求两个字的和,结果存放到Z单元中。

1.2:从SOURCE_BUFFER单元开始存放了20个字母A, 编程将这20个字母A的字符串传送到DEST_BUFFER开始的单元中。

2.1:计算1+2+3+…+10,将结果显示在屏幕上。对1-10求和、将结果打印(这个比较关键、利用除法进行输出)(div指令的商在AX、余数在DX)(所以每次得到一个DX就进行PUSH、并同时用CX记录位数)(最后POP DX即可输出——记得转成ascii)

1
2
MOV AH, 1    ; AH存入子功能号1
INT 21H ; 调用21H中断,等待用户键盘输入一个字符,并存入AL

2.2:利用01H号功能调用输入10个一位数字,将其由数字字符转换为相应整数,并依次保存到字节变量BUF。编程求出这10个数中的最大数和最小数,分别存入字节变量MAX和MIN,并分别将其在屏幕上显示出来。

1
2
3
MOV AH, 2     ; AH存入子功能号2
MOV DL, ‘A’ ; DL存入要显示的字符‘A’
INT 21H ; 调用21H中断,屏幕上显示DL中的字符

2.3:输入一个不大于65535的十进制非负整数,判断其是否为素数,如果是素数,输出字符串“It’s a prime.”,否则输出字符串“It’s not a prime.”。

1
2
3
4
5
6
7
数据段:
定义变量,并初始化为字符串:
STR DB “hello world”, ‘$’
代码段:
MOV AH, 9 ; AH存入子功能号9
MOV DX, OFFSET STR ; DX中存入字符串的首地址
INT 21H ; 调用21H中断,屏幕上显示DX指向的字符串“hello world”

3.1:从键盘上输入多个长度小于30的字符串,直到输入空行为止,将其中最长的一行字符串显示输出。

要求:(1)定义子过程GETS,实现从键盘输入字符串到参数数组中,读取包括空格在内的所有字符,直到遇到换行符为止,在字符数组中以‘\0’字符作为字符串末尾标记,该过程的返回值为输入的字符串的长度,返回值用寄存器AX传递。

(2)定义子过程PUTS,实现输出参数数组中的字符串,该过程的返回值为输出的字符个数,返回值用寄存器AX传递。

(3)定义主过程MAIN,调用两个子过程实现程序功能。

(4)输入一个字符通过调用21H号DOS中断的1号功能,输出一个字符通过该中断的2号功能调用。

3.2:编写十进制数到十六进制的转换程序。用户从键盘输入一个十进制无符号整数,然后把该数以十六进制形式(包括后缀字符H)在屏幕上显示出来。

要求:(1)定义两个子过程INPUT和OUTPUT。过程INPUT实现键盘输入一个十进制无符号整数,存入寄存器BX。过程OUTPUT实现以十六进制形式(包括后缀字符H)在屏幕上输出寄存器BX的值。

(2)定义主过程MAIN调用两个子过程实现程序功能。

other

自写:输入一连串数字保存、再输出(每个位上+1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
; multi-segment executable file template.

data segment
num db 20 dup(0)
len dw 0
ends


code segment
start:
; set segment registers:
mov ax, data
mov ds, ax

call INPUT

call OUTPUT

mov ax, 4c00h ; exit to operating system.
int 21h
ends

INPUT proc
push ax
push si

mov si,0 ;用于计数(偏移量)



input_loop:
mov ah,1
int 21h
cmp al,0Dh ;若为回车,则退出输入
je end_loop
sub al,'0' ;这里已经保证输入的数据是0-9
mov num[si],al
inc si
jmp input_loop

end_loop:
mov len,si ;将长度信息保存到len

pop si
pop ax
ret
INPUT endp

OUTPUT proc
mov cx,len
mov si,0
output_loop:
mov ah,2
mov dl,num[si] ;将num转为字符 (为了区别显示,加了1)
add dl,'1'
int 21h ;输出dl中的值
inc si
loop output_loop


ret
OUTPUT endp

end start ; set entry point and stop the assembler.