# 汇编
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
每个又可以分为 H 和 L,比如 AH 和 AL,可以分开独立使用
1word = 2 Byte
16 位 CPU 特征:1. 运算器一次最多处理 16 位 2,寄存器最大宽度为 16 位 3. 寄存器和运算器之间通路 16 位
由于 cpu 内部总线是 16 位,但外部总线是 20 位
8086 内部使用两个 16 位内部地址来将器变为 20 位
第一个地址为段地址,第二个为偏移地址 ,通过地址加法器变为 20 位
物理地址 = 段地址 * 16 + 偏移地址
即地址左移四位,每次左移一位,对应的 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
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 命令以汇编指令的格式在内存中写入一条机器指令。





fff0:0 ff 可以查看 rom 时间
b810:0 可以修改显存
1 地址字单元存放的字型数据时 124EH
可以将 N 和 N+1 两个单元看成一个字单元
# 寄存器(内存)
MOV al,[0] // 将偏移地址为 0 的内存单元送到 AL 寄存器中
不能把立即数直接送入段寄存器 ,需要经过通用寄存器中转
数据 -》通用寄存器 -》段寄存器
将数据从寄存器送入内存单元
mov bx,1000H
mov ds,bx
mov [0],al
-e 1000:0 23 11 22 66
1122 8833 8833
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 的前三个单元的字节型数据累加
累加字型数据
[address] 表示一个偏移地址为 address 的内存单元



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


# 栈
8086 提供的入栈和出栈 push,pop
push ax 将寄存器 ax 中的数据送入栈中
pop ax 从栈顶取出数据送入 ax
通过段寄存器 SS 存放栈顶的段地址,sp 存放栈顶偏移地址,任意时刻,SS:SP 指向栈顶元素
push ax sp=sp-2,SS:SP 指向新栈顶,放入数据
任意时刻・SS:SP 指向栈顶元素。当栈为空的时候,栈中没有元素,也就不存在栈项元素
所 SS:SP 只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址 + 2
栈最底部字单元的地址为 1000:000E,所以栈空时 SP=0010H
pop ax 将数据送入 ax 中,SP=SP+2,数据仍在内存中,下一次 push 或其他写入数据的操作时会覆盖
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. 执行可执行文件,有程序和数据两部分,操作系统加可执行文件中的机器码和数据加载入内存,进行初始化
源程序
汇编指令上图中 mov,add,int 等
伪指令上图中 assume codesg,没有对应的机器码,不被 cpu 执行,被编译器执行
定义一个段
segment 和 ends 是一对成对的伪指令,定义一个段 segment 开始,ends 结束
格式:
1 | 段名 segment |
一个汇编程序由多个段组成
ends 是汇编程序结束标记
assume 假设寄存器和段关联,CS:codesg,假设代码段的名称为 codesg
将源程序文件中内容称为源程序,编译后称为程序,程序放在可执行文件中 PE
标号 一个标号指代一个地址,codesg
编程运算 2^3
1 | assume cs:abc |
DOS 是一个单任务操作系统,一个程序结束后,将 CPU 控制权交还给使他运行的程序,称为返回
返回 程序段
1 | mov ax,4c00H |
masm.exe 1.asm
link 1.obj
可以在命令后加上;简化编译和连接
连接:源程序很大时将他分为多个源程序编译,成文目标文件后在连接到一起。将库文件和程序的目标文件连接在一起才可以生成可执行文件
在 DOS 中运行 1.exe 时,DOS 的 command 将 1.exe 加载进入内存,command 设置 CPU 的 cs:IP 指向程序的第一条指令,从而得以使程序运行。
程序运行结束后,返回到 command 中,cpu 继续执行 command
Debug 将程序加载进入内存,当不放弃对 cpu 控制,使用中断实现
有入口的程序
1 | assume cs:codesg |
debug 将程序 exe 加载入内存后 cx,存放程序长度
程序加载后,ds 中放着程序所在内存区的段地址,这个内存区的偏移地址为 0,这个内存区的前 256 个字节存放的是 psp,dos 用来和程序进行通信,从 256 字节以后存放的是程序
psp 的段地址 SA,偏移地址为 0,表示为 SA+10:0
指定到 int 21 时用 p 执行,此时程序 terminated
使用 q 结束 debug,回到 command 中,因为 debug 由 command 加载
# [bx]&loop
bx:
1 | assume cs:codesg |
实际来说,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++
ax 00be
bx 1002
内存中 00be 00be
bx 1004
内存中 00be 00be 00be
内存中从左到右为从低到高

内存中 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 | assume cs:code |
注意:debug 默认 16 进制,masm 默认十进制
loop 相当于 do … while …
计算 123*236 时使用 236+123 次,减少循环次数
1 | assume cs:code |
汇编时数据不能以字母开头,要在前面 + 0
debug g 命令和 p 命令
将 ffff:0006 单元中的数据 * 123,结果存在 dx 中
1 | assume cs:code |
g 0014 // 直接执行到 IP 为 0014 的位置
p 直接执行到循环结束,如果不是 21H 的位置输入
在程序中
mov al, ds:[0] // == mov bx,0 mov al,[bx], == mov al,[0]
累加 [0]-[b] 的值
1 | assume cs:code |
段前缀
mov ax,cs:[0] //cs 为段前缀
一般类似或 0:200-0:2ff 不会有数据和指令,是一段安全的空间
将内存 ffff:0ffff:b 段元中的数据拷贝到 0:2000:20b 单元中。
1 | assume cs:code |
# 多个段的程序
我们是不能自己随便决定哪段空间可以使用的,应该让系统来为我们分配。我们可以在程序中,定义我们希望处理的数据,这些数据就会被编译、连接程序作为程序的一部分写到可执行文件中。当可执行文件中的程序被加载入内存时,这些数据也同时被加载入内存中。与此同时,我们要处理的数据也就自然而然地获得了存储空间。
dw define word 定义字型数据,在代码段中定义数据,此时代码 的起始位置是数据之后
db 定义字节型数据
数据之间用,分隔
1 | assume cs:code |
标号 start 定义代码段的开始,区分数据和代码
-u 13f8:0
-u 13f8:10
读取结果不一样,如果数据被读成了代码,会导致下面真正的代码读取错误,因此在 debug 加载后,我们需要将 IP 设置为代码段中数据段的结束,比如上面的代码,我们需要将 IP 定义为 10 (10 进制的 16)
end start 可以告诉编译器程序的入口,即标号的地方。我们若要 CPU 从何处开始执行程序,只要在源程序中用 “end 标号” 指明就可以了。
代码段中使用栈
若数据,栈,代码所需空间查过 64kb,就不能放到一个栈中,因此应该用多个段存放数据,代码和栈
1 | assume cs:codesg |
存放数据的 data,存放 stack 栈,cs 存放代码
1 | assume cs:code,ds:data,ss:stack |
在程序中,段名就相当于一个标号,它代表了段地址。所以指令 “mov ax,data” 的含义就是将名称为 “data” 的段的段地址送入 ax。
CPU 处理我们定义的段中的内容,是通过程序中的汇编指令,和汇编指令对 CS:IP,SS:SP,DS 等寄存器的设置来决定
1 | assume cs:b,ds:a,ss:c |
程序占 N 个字节,运行后,该端实际占有空间为 (段为 16 的倍数)
(N/16+1)*16
如果没有 end 后面的标号,CPU 会将第一个数据当做代码段的开始
程序如下,编写 code 段中的代码,将 a 段和 b 段中的数据依次相加,将结果存到 c 段中。
1 | assume cs:code |

# 更灵活定位内存地址
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 | mov ax,datasg |
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 | assume cs:codesg, ds:datasg |
将 datasg 的每个字母变为大写
1 | assume cs:codesg, ds:datasg |
这样做 cx 不会被覆盖导致死循环,进入循环前先保存起来
1 | assume cs:codesg, ds:datasg |
将 cx 放在内存中来解决寄存器不够的问题
更好的办法是使用栈,在调用函数前 push 到栈中,调用结束后 pop 回内存中,防止因为调用函数导致寄存器被篡改
1 | assume cs:codesg, ds:datasg |
将 datasg 段中每个单词的前四个字改为大写字
1 | assume cs:codesg,ds:datasg,ss:stacksg |
# 数据处理的问题
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
寻址方式
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 | mov dx,1 |
1001/100
1 | mov ax,1001 |
dd 定义双字
用 div 计第 data 段中第一个数据除以第二个数据后的结果・商存放在第 3 个数据的存储单元。
1 | assume cs:code,ds:data |
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 | start: mov ax,offset start ;mov ax,0 |
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

此时机器码为跳转的地址,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 | mov ax,0123H |
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 | mov sp,10h |
1 | assume cs:code |

某种意义上来说,call 是调用,ret 是返回 (return)
call 和 ret 要对应
mul 指令
相乘的两个数要么都是 8 要么都是 16
8 位:AL 中或内存中
16 位:AX 中或内存中
结果:
8 位:ax 中
16 位:dx(高位),ax(低位)
mul 寄存器
mul 内存单元
1 | mul byte ptr ds:[0] |
100*10
1 | mov al,100 |
即需要将任意一个乘数放在 ax 或 al 中,另一个乘数放在空闲的通用寄存器中
计算 n 的三次方
1 | cube: mov ax,bx |
dw 的三次方放入 dw 中
1 | assume cs:code,cs:data,ss:stack |
对于批量数据的传递,我们将它存在内存中,然后把首地址放在寄存器中
判断是否以 0 结束,不是就转为大写
1 | capital:mov cl:[si] |
# 标志寄存器
8086CPU 的标志客存器有 16 位,其中存储的信息通常被称为程序状态字 (PSW) 。
标志寄存器结构:
ZF
零标志位,执行指令后如果结果为 0,zf=1
1 | mov ax,1 |
运算指令,大多数会影响 ZF 寄存器
PF
奇偶标志位,指令执行后,如果 1 的个数为偶数,PF=1
1 | mov al,1 |
SF
符号标志位,执行指令后,结果为负,SF=1
1 | mov al,10000001 |
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 | mov ax,2 |
1 | mov al,98h |
加法分为两步
1. 低位相加
2. 高位相加,在加上低位相加产生的进位值
1 | add ax,bx |
就是那种 1EF000H+201000H
1 | assume cs:code |
或者只用两个寄存器
1 | mov ax,001EH |
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
cmp ax,ax
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 的值・
以下都是无符号指令比较时
equal
not equal
below
not below
above
not above
ah=bh,ah=ah+ah,ah=ah+bh
1 | assume cs:code |
cmp 和 je 等可以单独出现
data 中是否小于 8,结果保存在 ax 中
1 | assume cs:code,ds:data |
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 | assume cs:code |
pushf
将标志寄存器压栈
popf 从栈中弹出数据,送入标志寄存器
比如
1 | push 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 | assume cs:code |
单步中断
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 | assume cs:code |
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 | ;编程:在屏幕的5行12列显示3个红底高亮闪烁绿色的‘a’。 |
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 | mov al,01010001b |
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