# 汇编

1. 汇编语言组成

汇编指令 ,伪指令,其他符号

2. 汇编不区分大小写

3. 数据总线,控制总线,地址总线 – 外部总线

# 寄存器(CPU)

CPU 由运算器,控制器,寄存器组成,靠内部总线相连

8086 有 14 个寄存器,它们的名称为 AB,BX,CX,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES,PSW,都是 16 位

AX,BX,CX,DX 为通用结存器,最大可以存储 2^16-1

image-20221009203327558

每个又可以分为 H 和 L,比如 AH 和 AL,可以分开独立使用

image-20221009204003372

1word = 2 Byte

16 位 CPU 特征:1. 运算器一次最多处理 16 位 2,寄存器最大宽度为 16 位 3. 寄存器和运算器之间通路 16 位

由于 cpu 内部总线是 16 位,但外部总线是 20 位

8086 内部使用两个 16 位内部地址来将器变为 20 位

image-20221009211023097

第一个地址为段地址,第二个为偏移地址 ,通过地址加法器变为 20 位

物理地址 = 段地址 * 16 + 偏移地址

image-20221009211420365

即地址左移四位,每次左移一位,对应的 16 进制和 10 进制就 * 2,因为地址是二进制

扩展:一个 x 进制左移一位就 * x

段地址长度可以自己指定,一个段的最大长度为 64kb,起始地址一定为 16 的倍数

一个物理地址可以有很多种段地址和偏移地址的组合

2000:1F60 数据在 21F60 中

段寄存器 CS,DS,SS,ES

CS:代码地址

DS:数据地址

SS:堆栈地址

IP:指令指针寄存器

修改 CS IP 中内容使用

jmp 段地址:偏移地址

jmp ax // 仅修改 IP 内容,参数为合法寄存器,类似于 mov IP,AX

8086cpu 工作过程

1.CS:IP 指向内存单元读取执行,读取执行进入指令缓冲器

2.IP 指向下一条指令

3. 执行指令,重复到 1

初始 CS=2000H,IP=0000H

image-20221010210638345

mov ax 6622

jmp 1000:3

mov ax,0000

mov bx,ax

jmp bx

mov ax,0123

mov ax,0000

… 死循环

代码段

将 <=64kb 的一组代码,起始地址为 16 倍数的内存单元作为一个代码段

通过 CS:IP 指向代码段首地址来执行代码段

DEBUG

用 Debug 的 R 命令查看、改变 CPU 寄存器的内容;

用 Debug 的 D 命令查看内存中的内容;

用 Debug 的 E 命令改写内存中的内容;

用 Debug 的 U 命令将内存中的机器指令翻译成汇编指令;

用 Debug 的 T 命令执行一条机器指令;

用 Debug 的 A 命令以汇编指令的格式在内存中写入一条机器指令。

image-20221010213213522

image-20221010213504618

image-20221010213716733

image-20221010213841925

image-20221010214052414

fff0:0 ff 可以查看 rom 时间

b810:0 可以修改显存

image-20221011204621998

1 地址字单元存放的字型数据时 124EH

可以将 N 和 N+1 两个单元看成一个字单元

# 寄存器(内存)

MOV al,[0] // 将偏移地址为 0 的内存单元送到 AL 寄存器中

不能把立即数直接送入段寄存器 ,需要经过通用寄存器中转

数据 -》通用寄存器 -》段寄存器

将数据从寄存器送入内存单元

mov bx,1000H

mov ds,bx

mov [0],al

image-20221011210109223 image-20221011210150336

-e 1000:0 23 11 22 66

1122 8833 8833

image-20221012084245812

2c34

1B12 -d 1000:0

0000

mov 指令

mov 寄存器,数据

mov 寄存器,寄存器

mov 寄存器,内存单元 mov ax,[0]

mov 内存单元,客存器 mov [0],ax

mov 段寄存器,寄存器

mov 寄存器,段寄存器

add,sub

add 寄存器,数据

add 寄存器,寄存器

add 寄存器,内存单元

add 内存单元,寄存器

sub 寄存器,数据

sub 寄存器,寄存器

sub 寄存器,内存单元

sub 内存单元,寄存器

add,sub 不能使用段寄存器为参数

数据段:长度为 N 的地址连续,起始地址为 16 倍数的内存单元定义为存储数据的内存空间

将 123B0-123BA 的前三个单元的字节型数据累加

image-20221012212730685

累加字型数据

image-20221012213223456

[address] 表示一个偏移地址为 address 的内存单元

image-20221012214721228

image-20221012214843733

image-20221012215020183

即 [0] 073F 和 073F [0] 会将 073f 当做偏移地址,验证:

image-20221012215201456

image-20221012215301128

#

8086 提供的入栈和出栈 push,pop

push ax 将寄存器 ax 中的数据送入栈中

pop ax 从栈顶取出数据送入 ax

通过段寄存器 SS 存放栈顶的段地址,sp 存放栈顶偏移地址,任意时刻,SS:SP 指向栈顶元素

push ax sp=sp-2,SS:SP 指向新栈顶,放入数据

image-20221013193051835 image-20221013193356305

任意时刻・SS:SP 指向栈顶元素。当栈为空的时候,栈中没有元素,也就不存在栈项元素
所 SS:SP 只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址 + 2
栈最底部字单元的地址为 1000:000E,所以栈空时 SP=0010H

pop ax 将数据送入 ax 中,SP=SP+2,数据仍在内存中,下一次 push 或其他写入数据的操作时会覆盖

image-20221013194330665

push 和 pop 可以操作段寄存器,通用寄存器,内存单元

push ax

push ds

push [0]

# 栈顶越界

栈满 push,栈空 pop 产生越界

栈满 push 会导致溢出覆盖栈外的数据

栈空 pop 会将别的数据放入寄存器中产生溢出

8086CPU 中没有检测的机制,需要自己注意…

# 练习

将 10000H~1000FH 这段空间当作栈,初始状态是空的,将 AX,BX,DS 中的数据入栈

mov ax,1000

mov ss,ax

mov sp,0010

push ax

push bx

push ds

(1)将 10000H~1000FH 这段空间当作栈,初始状态是空的;

(2)设置 AX=001AH,BX=001BH;

(3)将 AX,BX 中的数据入栈

(4)然后将 AX,BX 清零;

(5)从栈中恢复 AX,BX 原来的内容。

mov ax,1000

mov ss,ax

mov sp,0010

mov ax,001A

mov bx,001B

push ax

push bx

mov ax,0 // 使用 sub ax,ax 只使用两个字节,mov ax,0 机器码需要三个字节,也可以使用 xor ax,ax

mov bx,0

pop bx

pop ax

(1) 将 10000H~1000FH 这段空间当栈,初始状态是空的;

(2)设置 AX=002AH ,BX=002BH;

(3)利用栈交换 AX 和 BX 中的数据。

mov ax,1000

mov ss,ax

mov sp,0010

mov ax,002A

mov bx,002B

push ax

push bx

pop ax

pop bx

我们如果要在 10000H 处写入字型数据 2266H, 可以用以下的代码完成︰

mov ax,1000H

mov ds,ax

mov ax,2266H

mov [0],ax

补在 10000H 处写入字型数据 2266H

mov ax,1000

mov ss,ax

mov sp,2 //push 之前先 - 2,在放输入,2-2=0

mov ax,2266

push ax

要求,不能用 mov 内存单元,寄存器这类指令

push,pop 实质上是一种内存传送指令,push,pop 访问的内存单元的地址是由 ss:sp 指出的

pop,pop 栈操作只修改 sp,sp 最大变化范围为 0-FFFF

如果我们将 10000H~1FFFFH 这段空间当作栈段,初始状态是空的,比时 SS=1000H,SP=0

栈顶变化范围为 0-FFFFH,栈空 sp=0,一直压栈,栈满后 sp=0,再次压栈,栈顶将环绕,覆盖原来内容

对于代码段,数据段,栈段,cpu 通过 CS DS SS 区分

CS:IP

SS:SP

DS:[0]

# 程序编写

1. 编写汇编程序,后缀为.asm

2. 编译 masm.exe 连接 link.exe

3. 执行可执行文件,有程序和数据两部分,操作系统加可执行文件中的机器码和数据加载入内存,进行初始化

源程序

image-20221015201813041

汇编指令上图中 mov,add,int 等

伪指令上图中 assume codesg,没有对应的机器码,不被 cpu 执行,被编译器执行

定义一个段

segment 和 ends 是一对成对的伪指令,定义一个段 segment 开始,ends 结束

格式:

1
2
段名 segment 
段名 ends

一个汇编程序由多个段组成

ends 是汇编程序结束标记

assume 假设寄存器和段关联,CS:codesg,假设代码段的名称为 codesg

将源程序文件中内容称为源程序,编译后称为程序,程序放在可执行文件中 PE

image-20221015203100625

标号 一个标号指代一个地址,codesg

编程运算 2^3

1
2
3
4
5
6
7
assume cs:abc
abc segent
mov ax,2
add ax,ax
add ax,ax
abc ends
end

DOS 是一个单任务操作系统,一个程序结束后,将 CPU 控制权交还给使他运行的程序,称为返回

返回 程序段

1
2
mov ax,4c00H
int 21H
image-20221015204512894

masm.exe 1.asm

link 1.obj

可以在命令后加上;简化编译和连接

连接:源程序很大时将他分为多个源程序编译,成文目标文件后在连接到一起。将库文件和程序的目标文件连接在一起才可以生成可执行文件

在 DOS 中运行 1.exe 时,DOS 的 command 将 1.exe 加载进入内存,command 设置 CPU 的 cs:IP 指向程序的第一条指令,从而得以使程序运行。

程序运行结束后,返回到 command 中,cpu 继续执行 command

image-20221016204217510

Debug 将程序加载进入内存,当不放弃对 cpu 控制,使用中断实现

有入口的程序

1
2
3
4
5
6
7
8
9
10
11
12
assume cs:codesg
codesg segment
start: mov ax,0123H
add bx,0456H
add ax,bx
add ax,ax

mov ax,4c00H
int 21H

codesg ends
end start

debug 将程序 exe 加载入内存后 cx,存放程序长度

程序加载后,ds 中放着程序所在内存区的段地址,这个内存区的偏移地址为 0,这个内存区的前 256 个字节存放的是 psp,dos 用来和程序进行通信,从 256 字节以后存放的是程序

psp 的段地址 SA,偏移地址为 0,表示为 SA+10:0

image-20221016211036041

指定到 int 21 时用 p 执行,此时程序 terminated

使用 q 结束 debug,回到 command 中,因为 debug 由 command 加载

# [bx]&loop

bx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
assume cs:codesg
codesg segment
start: mov ax,2000
mov ds,ax
mov al,[0]
mov bl,[1]
mov cl,[2]
mov dl,[3]

mov ax,4c00H
int 21H

codesg ends
end start

实际来说,CPU 并不认编译器的 [0] 这种东西,他会认成 0

但 debug 中 [0] 可以正常作为偏移地址

[bx] 表示一个内存单元,偏移地址在 bx 中

mov bx,0 // 将 0 给 bx

mov ax,[bx] // 此时 bx 是偏移地址

段地址 SA,偏移地址 EA SA:EA

() 的含义

ax 中的内容为 0010,那么 (ax)=0010

2000:1000 处的内容为 0010,(21000H)=0010

mov ax,[2] (ax)=((dx)*16+2)

idata 含义:

idata 表示常量

mov ax,[idata] 代表 Mov ax,[0] Mov ax,[2] Mov ax,[3] d = 等

inc bx //bx++

image-20221017094727684

ax 00be

bx 1002

内存中 00be 00be

bx 1004

内存中 00be 00be 00be

内存中从左到右为从低到高

image-20221017094440863

内存中 00be 00be 00be be 00

Loop

loop 标号

执行 loop 时 CPU 操作

1.(cx)=(cx)-1

2. 判断 cx 中的值,不为 0 跳至标号处执行,为 0 向下执行

即一般 cx 中存放循环次数

使用 loop 计算 2^2

mov ax,2

add ax,ax

计算 2^12

1
2
3
4
5
6
7
8
9
10
11
12
assume cs:code
code segment
start: mov ax,2
mov cx,11
s: add ax,ax
loop s

mov ax,4c00h
int 21h

code ends
end start

注意:debug 默认 16 进制,masm 默认十进制

loop 相当于 do … while …

计算 123*236 时使用 236+123 次,减少循环次数

1
2
3
4
5
6
7
8
9
10
11
12
assume cs:code
code segment
start: mov ax,0
mov cx,123
s: add ax,236
loop s

mov ax,4c00h
int 21h

code ends
end start

汇编时数据不能以字母开头,要在前面 + 0

debug g 命令和 p 命令

将 ffff:0006 单元中的数据 * 123,结果存在 dx 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
assume cs:code
code segment
start: mov ax,0ffffh
mov ds,ax

mov bx,6
mov ax,[bx]

mov dx,0

mov cx,123
s: add dx,ax
loop s

mov ax,4c00h
int 21h

code ends
end start

g 0014 // 直接执行到 IP 为 0014 的位置

p 直接执行到循环结束,如果不是 21H 的位置输入

在程序中

mov al, ds:[0] // == mov bx,0 mov al,[bx], == mov al,[0]

累加 [0]-[b] 的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
assume cs:code
code segment
start: mov ax,0ffffh
mov ds,ax
mov bx,0

mov dx,0

mov cx,12
s: mov al,ds:[cs]
mov ah,0
add dx,ax
inc bx
loop s


mov ax,4c00h
int 21h

code ends
end start

段前缀

mov ax,cs:[0] //cs 为段前缀

一般类似或 0:200-0:2ff 不会有数据和指令,是一段安全的空间

将内存 ffff:0ffff:b 段元中的数据拷贝到 0:2000:20b 单元中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
assume cs:code
code segment
start: mov ax,0ffffh
mov ds,ax ;ds中是内存ffff:0中的数据

mov ax,0020h
mov es,ax ;es是内存20:0中的数据

mov bx,0 ;bx中为内存偏移值
mov cx,12 ;为循环判断条件

s: mov dl,ds:[bx]
mov es:[bx],dl
inc bx
loop s


mov ax,4c00h
int 21h

code ends
end start

# 多个段的程序

我们是不能自己随便决定哪段空间可以使用的,应该让系统来为我们分配。我们可以在程序中,定义我们希望处理的数据,这些数据就会被编译、连接程序作为程序的一部分写到可执行文件中。当可执行文件中的程序被加载入内存时,这些数据也同时被加载入内存中。与此同时,我们要处理的数据也就自然而然地获得了存储空间。

dw define word 定义字型数据,在代码段中定义数据,此时代码 的起始位置是数据之后

db 定义字节型数据

数据之间用,分隔

1
2
3
4
5
assume cs:code
code segment
dw 0123h,0234h,0123h,0234h,0123h,0234h,0123h,0234h
start: mov ax,0ffffh
mov ds,ax

标号 start 定义代码段的开始,区分数据和代码

-u 13f8:0

-u 13f8:10

读取结果不一样,如果数据被读成了代码,会导致下面真正的代码读取错误,因此在 debug 加载后,我们需要将 IP 设置为代码段中数据段的结束,比如上面的代码,我们需要将 IP 定义为 10 (10 进制的 16)

end start 可以告诉编译器程序的入口,即标号的地方。我们若要 CPU 从何处开始执行程序,只要在源程序中用 “end 标号” 指明就可以了。

代码段中使用栈

若数据,栈,代码所需空间查过 64kb,就不能放到一个栈中,因此应该用多个段存放数据,代码和栈

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
assume cs:codesg
codesg segment
dw 0123H,0123H,0123H,0123H,0123H,0123H,0123H,0123H
dw 0,0,0,0,0,0,0,0 ;将上一行dw的数据入栈和出栈的空间


start: mov ax,cs ;将代码段起始位置给栈段
mov ss,ax

mov sp,32 ;设置栈段的偏移地址为32(0+2*16)+1

mov bx,0 ;bx偏移地址
mov cx,8 ;cx loop条件

s: push cs:[bx] ;将cs中的数据入栈
add bx,2
loop s

mov bx,0
mov cx,8

s0: pop cs:[bx] ;将cs中的数据出栈
add bx,2
loop s0

mov ax,4c00H
int 21H

codesg ends
end start

存放数据的 data,存放 stack 栈,cs 存放代码

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
assume cs:code,ds:data,ss:stack

data segment
dw 0123H,0123H,0123H,0123H,0123H,0123H,0123H,0123H
data ends

stack segment
dw 0,0,0,0,0,0,0,0
stack ends

code segment
start: mov ax,stack
mov ss,ax
mov sp,16

mov ax,data
mov ds,ax

mov bx,0
mov cx,8

s: push [bx]
add bx,2
loop s

mov bx,0
mov cx,8

s0: pop [bx]
add bx,2
loop s0

mov ax,4c00H
int 21H

codesg ends
end start

在程序中,段名就相当于一个标号,它代表了段地址。所以指令 “mov ax,data” 的含义就是将名称为 “data” 的段的段地址送入 ax。

CPU 处理我们定义的段中的内容,是通过程序中的汇编指令,和汇编指令对 CS:IP,SS:SP,DS 等寄存器的设置来决定

1
2
3
4
5
6
assume cs:b,ds:a,ss:c
....
b segment
d: mov ax,c
mov ss,ax
mov sp,20h ;将c当做栈空间,ss:sp指向c:20

程序占 N 个字节,运行后,该端实际占有空间为 (段为 16 的倍数)

(N/16+1)*16

如果没有 end 后面的标号,CPU 会将第一个数据当做代码段的开始

程序如下,编写 code 段中的代码,将 a 段和 b 段中的数据依次相加,将结果存到 c 段中。

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
assume cs:code

a segment
db 1,2,3,4,5,6,7,8
a ends

b segment
db 1,2,3,4,5,6,7,8
b ends

c segment
db 0,0,0,0,0,0,0,0
c ends

code segment
start: mov ax,a
mov ds,ax ;ds中为数据段a中的数据

mov ax,c
mov es,ax ;es中为数据段c中的数据

mov bx,0
mov cx,8

s: mov dx,ds:[bx] ;循环将ds(a)中的数据给c
mov es:[bx],dx
add bx,2
loop s

mov ax,b
mov ds,ax

mov bx,0
mov cx,8

s0: mov dx,ds:[bx]
add es:[bx],dx
add bx,2
loop s0

mov ax,4c00H
int 21H

code ends
end start

image-20221019214952321

# 更灵活定位内存地址

and 逻辑与,按位进行与运算,通过该指令可以将操作对象响应为设为 0,只要和 0 and

or 逻辑或,按位进行或运算,通过该指令可以将操作对象相应位设为 0,只要和 1 or

通过’ ’ 括起来标识一个字符

db ‘unIX’ 相当于 db 75H,6Eh,49H,58H define byte

mov al,‘a’ 相当于 mov al,61H

通过 将第五位二进制数和 0 与变为大写

[bx+idata] 表名一个内存单元,偏移地址为 idata

mov ax,[200+bx]

mov ax,[bx+200]

mov ax,200[bx]

mov ax,[bx].200

以上四个写法等效

1
2
3
4
5
6
7
8
9
10
11
12
13
		mov ax,datasg
mov ds,ax
mov bx,0

mov cx,5
s: mov al,[bx]
and al,11011111b
mov [bx],al
mov al,[5+bx]
or al,00100000b
mov [5+bx],al
inc bx
loop s

SI,DI

和 bx 功能相近,但不能分成两个八位寄存器使用

mov ax,[bx]

mov ax,[si]

mov ax,[di]

一般用 si 指向原始空间,di 指向目标空间

mov ax,[bx+si] == mov ax,[bx][si]

[bx+si+idata] == mov ax,idata[bx][si] == mov ax,[bx][si].200 == mov ax,[bx].200[si]

将 db 中的每个字母第一个变为大写

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
assume cs:codesg, ds:datasg

datasg segment
db '1.file '
db '2.file '
db '3.file '
db '4.file '
db '5.file '
db '6.file '
datasg ends

codesg segment
start: mov dx,datasg
mov ds,dx

mov bx,0
mov cx,6

s: mov al,[bx+3]
and al,11011111b
mov [bx+3],al
add bx,16
loop s

mov ax,4cooH
int 21H
codesg ends
end start

将 datasg 的每个字母变为大写

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
assume cs:codesg, ds:datasg

datasg segment
db 'add '
db 'asm '
db 'and '
db 'ano '
datasg ends

codesg segment
start: mov dx,datasg
mov ds,dx

mov bx,0
mov cx,4

s0: mov ax,cx
mov si,0
mov cx,3
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
mov cx,ax
loop s0


mov ax,4cooH
int 21H
codesg ends
end start

这样做 cx 不会被覆盖导致死循环,进入循环前先保存起来

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
assume cs:codesg, ds:datasg

datasg segment
db 'add '
db 'asm '
db 'and '
db 'ano '
dw 0 ;用于保存cx
datasg ends

codesg segment
start: mov dx,datasg
mov ds,dx

mov bx,0
mov cx,4

s0: mov ds:[40H],cx
mov si,0
mov cx,3
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
mov cx,ds:[40H]
loop s0


mov ax,4cooH
int 21H
codesg ends
end start

将 cx 放在内存中来解决寄存器不够的问题

更好的办法是使用栈,在调用函数前 push 到栈中,调用结束后 pop 回内存中,防止因为调用函数导致寄存器被篡改

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
assume cs:codesg, ds:datasg

datasg segment
db 'add '
db 'asm '
db 'and '
db 'ano '
dw 0 ;用于保存cx
datasg ends

stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends

codesg segment
start: mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax

mov bx,0
mov cx,4

s0: push cx
mov si,0
mov cx,3
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
pop cx
loop s0


mov ax,4cooH
int 21H
codesg ends
end start

将 datasg 段中每个单词的前四个字改为大写字

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
assume cs:codesg,ds:datasg,ss:stacksg

datasg segment
db '1. display '
db '2. display '
db '3. display '
db '4. display '
dw 0 ;用于保存cx
datasg ends

stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends



codesg segment
start: mov ax,stacksg
mov ss,ax
mov sp,16 ;定位栈段

mov ax,datasg
mov ds,ax ;定位内存

mov bx,0
mov cx,4 ;循环条件,bx偏移地址,cx循环条件,因为有四条字母,所以四次循环

s0: push cx ;外层循环,用户列循环,将cx入栈保证cx不受后续内层循环干扰
mov si,0
mov cx,4 ;内层循环,因为大写前四个字母,循环四次
s: mov al,[bx+3+si]
and al,11011111b
mov [bx+3+si],al
inc si
loop s
add bx,16
pop cx
loop s0


mov ax,4cooH
int 21H
codesg ends
end start

# 数据处理的问题

reg 和 sreg 寄存器,段寄存器

只有四个寄存器 bx,bp,si,di 可以用在 [] 中进行内存单元的寻址,可以单个出现或四种组合出现

注意: mov ax [bp+bx] mov ax,[si+di] 不能一起使用

bp 如果没有显示指定段地址,那么段地址默认在 ss 中,可以简单理解为 sp

mov ax,[bp]

mov ax,[bp+si]

mov ax,[bp+idata]

汇编语句用三个概念来表达数据的位置

1. 立即数

2. 寄存器

3. 段地址 (SA),偏移地址 (EA)

段地址寄存器默认

mov ax,[0] //==mov ax,ds:[0] , 注意,只有 0 能这么搞,其他的数字会被当成立即数处理,不会被当场偏移地址处理

mov ax,[bx+si]

mov ax,[bp] // 段地址默认在 ss 中

段地址寄存器显示指出

mov ax,ds:[bp]

mov ax,cs:[bx+si+8] // 显示指出可以随便给,默认指出就给 ss

寻址方式

image-20221022185518717 image-20221022191402201

1. 将 cs:ip 通过地址加法器,算出一个 20 位的地址,通过地址总线,送到内存中,在内存中找到对应的位置

2. 将 1 找到的内存的数据,比如 A10E00 通过数据总线,送到指令缓冲寄存器中

3. 执行指令

4. 将所有偏移地址送到地址加法器中算出一个偏移地址,通过地址总线送到内存中

5. 将 4 中的数据,比如 A10E 通过数据总线,送给对应寄存器

6.IP 指向下一条指令

确定要处理的数据有多长

1. 通过寄存器名指明数据大小 mov ax,1 这时 1 就是 16 位的,因为 ax 也是 16 位的 mov ax,ds:[0] 这时候 ds 偏移地址中数据也是八位的

2. 通过操作符 x ptr 指明内存单元长度,x 可以为 word 或 byte,可以理解为强制转换法

mov word ptr ds:[0],1 // 这时 16 位的,0001H

inc word ptr [bx],2

mov byte ptr ds:[0],1 // 这时 8 位的,01H

double word ptr 双字

3. 其他方法 push [1000H] 入栈,push 指令只对字进行操作 sp=sp-2

用 bx 定位整个结构体,用 idata 定位结构体中的某一个数据项,用 si 定位数组项中的每个元素。

[bx].idata

[bx].idata[si]

div

除数 8/16 位,被除数默认放在 ax,dx 和 ax 中

如果除数为 8 位,被除数需要 16 位 (ax)

如果除数为 16 位,被除数需要 32 位 (ax+dx),dx 是高位,ax 是低位

除数是 8 位的时候,商在 al 中,余数在 ah 中

除数是 16 位的时候,商在 ax 中,余数在 dx 中

div 寄存器

div 内存单元

div btye ptr ds:[0] // (al)=(ax)/((ds)*16+0)的商,(ah)=(ax)/((ds)*16+0)的余数

div word ptr es:[0] // (ax)=[(dx)*10000H+(ax)]/((ds)*16+0)的商,(dx)=[(dx)*10000H+(ax)]/((ds)*16)+0的余数

10001/100

1
2
3
4
mov dx,1
mov ax,86a1
mov bx,100
div bx

1001/100

1
2
3
mov ax,1001
mob bl,100
div bl

dd 定义双字

用 div 计第 data 段中第一个数据除以第二个数据后的结果・商存放在第 3 个数据的存储单元。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
assume cs:code,ds:data
data segment
dd 100001
dw 100
dw 0
data ends

code segment
start: mov ax,data
mov ds,ax
mov ax,ds:[0]
mov dx,ds:[2]

div word ptr ds:[4]

mov ds:[6],ax


mov ax,4c00H
int 21H

codesg ends
end start

dup

db 3 dup (0) 定义 3byte,他们值都是 0

db 3 dup(0,1,2) //0,1,2,0,1,2,0,1,2

db 3 dup(‘abc’,‘ABC’) //abcABCabcABCabcABC

db/dw/dd n dup()

# 转移指令

8086 转移指令分为:

8086CPU 的转移行为有以下几类。

1. 无条件转移

2. 条件转移指令

3. 循环指令

4. 过程

5. 中断

jmp 无条件跳转

只修改 IP 时,称为段内转移,比如: jmp ax。

同时修改 CS 和 IP 时,称为段间转移,比如: jmp 1000:0。

由于转移指令对 IP 的修改范围不同,段内转移又分为:短转移和近转移。

短转移 IP 的修改范围为 - 128~127。

近转移 IP 的修改范围为 - 32768~32767。

操作符 offset

取得标号的偏移地址

1
2
start:	mov ax,offset start   ;mov ax,0
s: mov ax,offset s ;mov ax,3

jmp 指令

jmp short 标号 ;转移到标号处执行指令是种短转移,修改 IP 范围为 - 128-127

然鹅,jmp 0008 的机器码为 EB 03,0008 为需要跳转的命令地址

jmp 000A EB 05

CPU 不需要目的地址就可以实现对 IP 的修改

EB 后面的数字实际是个 offset,说明目的指令离 jmp 五个字节

jmp near ptr 标号 , 用于实现段内近转移位移范围 - 32769-32767

jmp far ptr 标号 // 段间转移,远转移

far ptr 指明了格用标号的段地址和偏移地址修改 CS 和 IP

image-20221024210038120

此时机器码为跳转的地址,IP 在前,CS 在后

jmp 16位寄存器

jmp ax // 实现段内近 / 短转移,ax 中为 ip 的值

转移地址在内存中:

jmp word ptr 内存单元地址 // 实现内存单元地址的段内转移,内存单元地址是目的地址

jmp word ptr ds:[0]

jmp word ptr [bx]

jmp dword ptr 内存单元地址 // 段间跳转,跳转时 IP 是高位,CS 是低位

1
2
3
4
mov ax,0123H
mov [bx],ax
mov word ptr [bx+2],0
jmp dword ptr [bx]

jcxz 标号;有条件跳转指令

IP 修改范围 - 128-127

如果 cx=0,转移到标号处

cx=0 时,IP 位移

cx!=0 向下执行

loop 标号

转移范围 - 128-127,短转移

cx=0 向下执行,cx!=0 转移到标号处

# call 和 ret

call 和 ret 都是转移指令,他们都修改 IP,或者同时修改 CS:IP

ret 用栈中数据修改 IP 实现近转移

IP = SS*16 + SP

SP = SP + 2

pop ip

retf 修改 CS:IP 实现远转移

IP = SS*16 + SP

SP = SP + 2

CS = ss*16 + SP

(SP) = (SP) + 2

pop ip

pop cs

call

将 IP 或 CSIP 入栈,转移 (jmp)

call 不能实现短转移

call 标号 将当前 IP 压栈,转移到标号

sp = sp -2

ss*16 + sp = IP

IP = IP + 位移 (16 位)

相当于: push ip + jmp near ptr 标号

call 只是将当前位置保存到栈了,其余和 jmp 一样

call 16 位寄存器

push IP

jmp bx

转移地址在内存中 call

call word ptr 内存地址

push IP jmp word ptr 偏移地址

call dword ptr 内存地址

push cs push IP jmp dword ptr 内存单元地址

1
2
3
4
5
6
7
8
9
mov sp,10h
mov ax,0123h
mov ds:[0],ax
mov word ptr ds:[2],0
call dword ptr ds:[0]

---
此时ds中低位为0123(IP),高位为0000(CS)
call结束后,cs=0,ip=0123,sp=0ch
1
2
3
4
5
6
7
8
9
10
11
12
13
assume cs:code
code segment
start: mov ax,1
mov cx,3
call s
mov bx,ax ;bx=8
mov ax,4c00h
int 21h
s: add ax,ax
loop s
ret
code ends
end start ;实现2^n

image-20221028213345088

某种意义上来说,call 是调用,ret 是返回 (return)

call 和 ret 要对应

mul 指令

相乘的两个数要么都是 8 要么都是 16

8 位:AL 中或内存中

16 位:AX 中或内存中

结果:

8 位:ax 中

16 位:dx(高位),ax(低位)

mul 寄存器

mul 内存单元

1
2
3
4
5
6
mul byte ptr ds:[0]
ax = al * (ds:[0])

mul word ptr [bx+si+8]
ax=ax*([bx+si+8]) 低16位
dx=ax*([bx+si+8]) 高16位

100*10

1
2
3
mov al,100
mov b1,10
mul bl

即需要将任意一个乘数放在 ax 或 al 中,另一个乘数放在空闲的通用寄存器中

计算 n 的三次方

1
2
3
4
cube:	mov ax,bx
mul bx
mul bx
ret

dw 的三次方放入 dw 中

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
assume cs:code,cs:data,ss:stack

data segment
dw 1,2,3,4,5,6,7,8
dw 0,0,0,0,0,0,0,0
data ends

code segment
start: mov ax,data
mov ds,ax

mov cx,8
mov si,0
s: mov bx,[si] ;bx作为cube的参数
call cube
mov [si+32],ax
add ax,2
loop s

mov ax,4c00H
int 21H

cube: mov ax,bx
mul bx
mul bx
ret

code ends
end start

对于批量数据的传递,我们将它存在内存中,然后把首地址放在寄存器中

判断是否以 0 结束,不是就转为大写

1
2
3
4
5
6
capital:mov cl:[si]
mov ch,0
jcxz ok
and byte ptr[si],11011111b
inc si
jmp short capital

# 标志寄存器

8086CPU 的标志客存器有 16 位,其中存储的信息通常被称为程序状态字 (PSW) 。

标志寄存器结构:

image-20221030202144281 image-20221030205228119 image-20221030205356183

ZF

零标志位,执行指令后如果结果为 0,zf=1

1
2
3
mov ax,1
sub ax,1
此时ZF=1

运算指令,大多数会影响 ZF 寄存器

PF

奇偶标志位,指令执行后,如果 1 的个数为偶数,PF=1

1
2
3
mov al,1
add al,10
;00001011 PF=0 因为3个1

SF

符号标志位,执行指令后,结果为负,SF=1

1
2
3
mov al,10000001
add al,1
;可以当做无符号,130,有符号,-126

CF

进位标志位,无符号运算标志位可能产生结果

CF=NC 无进位,CF=CY 有进位,只记录上一条指令的变化

not carry carry

进位是更高位

OF

溢出标志位,有符号运算可能产生结果

OF=OV 溢出 OF=NV 无溢出

溢出是侵犯了符号位

当成有符号就看 OF 和 SF,当成无符号就看 CF

ADC

带进位加法指令,利用了 CF 上进位值

adc ax,bx ;ax = ax + bx + cf

1
2
3
4
5
mov ax,2
mov bx,1
sub bx,ax
adc ax,1
;ax=4 ax+1+cf=2+1+1=4
1
2
3
4
mov al,98h
add al,al
adc al,3
;ax+3+cf = 30+3+1=34

加法分为两步

1. 低位相加

2. 高位相加,在加上低位相加产生的进位值

1
2
3
4
5
add ax,bx
===
add al,bl
adc ah,bh

就是那种 1EF000H+201000H

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code

code segment
start: mov ax,01EFH ;high
mov bx,0F00H ;low


mov cx,1000H ;low
mov dx,0201H ;high

add bx,cx
adc ax,dx

cube: mov ax,bx
mul bx
mul bx
ret

code ends
end start

或者只用两个寄存器

1
2
3
4
mov ax,001EH
mov bx,0F000H
add bx,1000H
adc ax,0020H

inc 和 loop 不影响 CF 的值,因此不能替换为 add ax,2

sbb

带进位减法指令

sbb ax,bx

ax = ax - bx - cf

cmp

功能类似于减法指令,但不保存结果,只对标志寄存器产生影响

cmp ax,ax

ax-ax ,不保存结果,只影响 flag,ZF=1,PF=1,SF=1,CF=0,OF=0

cmp ax,bx

image-20221031205029865

cmp ax,ax

image-20221031205427616

cmp ah,bh 比较有符号数时,需要比较 SF 和 OF

sf=1,of=0,ah<bh

SF=1,OF=1,ah>bh

SF=0,of=1,ah<bh

sf=0,of=0,ah>=bh

据无符号数的比较结果进行转移的条件转移指令,它们检测 ZF,CF 的值;

和根据有符号数的比较结果进行转移的条件转移指令,它们检测 SF,OF ,ZF 的值・

以下都是无符号指令比较时

image-20221031210918994

equal

not equal

below

not below

above

not above

ah=bh,ah=ah+ah,ah=ah+bh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code

code segment
start: mov ah,45h
mov bh 48h

cmp ah,bh
je s
add ah,ah
jmp short ok
s: add ah,ah
ok: ret

code ends
end start

cmp 和 je 等可以单独出现

data 中是否小于 8,结果保存在 ax 中

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
assume cs:code,ds:data

data segment
db 1,2,3,4,5,6,7,8
data ends

code segment
start: mov dx,data
mov ds,dx
mov ax,0
mov bx,0
mov cx,0

s0: cmp byte ptr [bx],8
jnb s
inc ax


s: inc bx
loop s0

mov ax,4c00h
int 21h

code ends
end start

DF

方向标志位,串处理指令中,控制 si,di 增减

DF=0,si,di 递增

DF=1,si,di 递减

movsb

以字节为单位传送

1. es*16 + di = ds*16+di

2. 如果 df=0,si,di++

如果 df=1,si,di–

movsw

和 movsb 一样,但 si 和 di 是以 2 递增或递减

rep movsb

rep movsw

根据 cx 的值,重复后面的串传送指令

循环实现 cx 个字符的传送,从 ds 送到 es

cld:将 DF=0

std:将 DF=1

将 data 段的第一个字符串送到后面的空间

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
assume cs:code

data segment
db 'welcome to masm!'
db 16 dup (0)
data ends

code segment
start: mov dx,data
mov ds,dx
mov es,dx

mov si,0
mov di,16

mov cx,16

cld

rep movsb

mov ax,4c00h
int 21h

code ends
end start

pushf

将标志寄存器压栈

popf 从栈中弹出数据,送入标志寄存器

比如

1
2
3
4
push ax
popf

那么此时ax中就是标志寄存器中的值

如果学破解,到这边就可以跑路了




# 中断

中断分为硬件中断和软件中断

硬件中断分为外部中断和内部中断

内部中断是不可屏蔽的中断

外部中断时可屏蔽的中断

中断向量表

CPU 用八位类型码通过中断向量表找到中断程序的入口地址

中断向量表可以裂解为一个索引

其中存放着 256 个中断源所对应的中断处理 程序入口

8086 中,中断向量表在 0000:0000 - 0000:03FF 1024 个字节

中断过程

1. 取得中断类型码 N

2.pushf

3.TF=0,IF=0

4.push cs

5.push ip

6. (IP) = (N*4) , (CS) = (N*4+2) N*x 指的是在中断向量表中找 CS 和 IP

中断处理步骤

1. 保存用到的寄存器

2. 处理中断

3. 恢复用到的寄存器

4. 用 iret 指令返回

iret – pop ip pop cs popf

中断寄存器入栈标志,CS,IP。iret 正好和他相反

除法溢出,产生中断类型为 0

我们是可以改变中断后处理的过程的

改变 0 号中断处理程序

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
assume cs:code

code segment
start:
mov ax, cs
mov ds, ax
mov si, offset do0 ;设置ds:si指向源地址
mov ax, 0
mov es, ax
mov di, 200h ;设置es:di指向目的地址
mov cx, offset do0end - offset do0 ;设置cx为传输长度
cld ;设置传输方向为正
rep movsb

mov ax, 0 ;设置中断向量表
mov es, ax
mov word ptr es:[0*4], 200h
mov word ptr es:[0*4+2], 0

mov ax,4c00h
int 21h

do0: jmp short do0start
db "Welcome to Fishc.com!"

do0start:
mov ax, cs
mov ds, ax
mov si, 202h ;设置ds:si指向字符串

mov ax, 0b800h
mov es, ax
mov di, 12*160+36*2 ;设置es:di指向显存空间的中间位置

mov cx, 21 ;设置cx为字符串长度
s: mov al, [si]
mov es:[di], al
inc si
add di, 1
mov al, 02h ;设置颜色
mov es:[di], al
add di, 1
loop s

mov ax, 4c00h
int 21h
do0end: nop

code ends
end start

单步中断

CPU 在执行完一条指令之后,如果检到标志寄存器的 TF 位为 1,则产生单步中断,引发中断过程。

但不中断类型码为 1

中断过程:

取得中断类型码 1

TF=0 ,IF=0

CS,IP 入栈

IP=(1*4) CS=(1*4+2)

TF=1, 执行程一条指令后执行单步中断

使用 t 命令是,debug 将 tf=1,执行完一条指令引发单步中断,显示寄存器,等待输入

也有情况,即使发生中断,也不响应

设置完 ss 后,即使中断,cpu 也不响应,因为 ss:sp 需要连续完成,不然 sp 指向的不是正确的栈顶

我们利用这个特性,将 ss 和 sp 的设置指令连续存放

mov ax,1000h

mov ss,ax

mov sp,0

mov ax,10

如果 ax=10 放在 sp 和 ss 中间,该指令不会被执行

# int

int n n 为中断类型码,引发中断过程

1. 取得类型码 n

2.IF=0,TF=0

3.CS,IP 入栈

4.IP=(n*4) CS=(n*4+2)

可以不做触发,通过 int 执行 0 号中断

int 一般和 iret 指令配合使用

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
assume cs:code
code segment

start:
mov ax,cs
mov ds,ax
mov si,offset capital
mov ax,0
mov es,ax
mov di,200h
mov cx,offset capitalend - offset capital
cld
rep movsb

mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0

mov ax,4c00h
int 21h

capital:
push cx
push si

change:
mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short change
ok:
pop si
pop cx
iret

capitalend:nop

code ends

end start

int 10h 是 bios 中断例程

BIOS 和 DOS 中断例程用 ah 来传递子程序的编号

(ah)=2表示调用第10h号中断例程的 2号子程序,功能为设置光标位置,可以提供光标所在的行号(80*25字符模式下:0~24 ),列号(80*25字符模式下: 0~79),和页号作为参数。(bh)=6 , (dh)=5, (d1)=12,设置光标到第0页,第5行,第12列。

内存地址中 B8000-BFFFFh 共 32kb,为显示缓冲区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
;编程:在屏幕的5行12列显示3个红底高亮闪烁绿色的‘a’。

assume cs:code
code segment
mov ah,2 ;置光标
mov bh,0 ;第0页
mov dh,5 ;dh中放行号
mov dl,12 ;dl中放列号
int 10h

mov ah,9 ;置光标
mov al,'a' ;字符
mov bl,11001010b;颜色属性
mov bh,0 ;第0页
mov cx,3 ;字符重复个数
int 10h

mov ax,4c00h
int 21h

code ends
end

int 21H

int 21h 中断例程是 DOS 提供的中断例程,其中包含了 DOS 提供给程序员在编程时调用为子程序

我们从前一直使用的是 int 21 中断例程 4ch 号功能,即程序返回功能

mov ah,4ch ; 程序返回

mov al,0 ; 返回值

int 21h

# 端口

端口读写指令 in 和 out 分别用于读取和写入数据

in al,60h

从 60h 号端口读入一个字节

在 in out 指令中,只能使用 ax 或 al 来存放从端口中读入的数据或要发送到端口中的数据

in al,20h

out 20h,al

CMOS RAM

包含一个时钟和一个有 128 位存储单元的 RAM 存储区

128 个字节中,内部时钟占用 0-0dh,其余大部分单元用于保存系统配置信息

BIOS 中有两个端口,为 70h 和 71h,通过这两个端口读取 CMOS RAM

70h 为地址,存放 CMOS RAM 单元地址,71 为数据端口,存放选定的 CMOS RAM 读取的数据

shl 和 shr

逻辑移位指令

shl 逻辑左移指令

将一个数据向左移位,最后移除的移位写入 cf,最低位 0 补充,数据可以来自寄存器或内存单元

移位位数大于 1 时,必须将移动位数放入 cl

1
2
3
mov al,01010001b
mov cl,3
shl al,cl ;从al拿出来在放回al去

shr

和 shl 相反

右移 = / 2

CMOS RAM 中时间信息

秒 00H

分 02H

时 04H

日 07H

月 08H

年 09H

他们都占一个字节,以 BCD 码存放

# 外中断

CPU 通过端口和外部设备进行联系

外中断源有两类

1. 可屏蔽中断

2. 不可屏蔽中断

IF=1 响应中断过程

IF=0 不响应中断过程

sti 设置 IF=1

cli 设置 IF=0

PC 机键盘处理过程

引发 9 号中断

— 般将按下一个键时产生的扫描码称为通码,松开一个键产生的扫描码称为断码

断码 = 通码 + 80H

image-20221108214017480
Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

John Doe WeChat Pay

WeChat Pay

John Doe Alipay

Alipay

John Doe PayPal

PayPal