汇编学习笔记
劳动成果声明:
我是跟着B站小甲鱼和海蓬莱学的,我主要做的是是整理思维和总结,笔记中部分图片和代码并非我原创,有网上的也有这两位大佬的劳动成果。
开机BIOS和DOS工作流程
(1)
开机后, CPU一加电,初始化(CS)=0FFFFH,(IP)=0,自动从 FFFF:0单元开始执行程序。FFFF:0处有一条转跳指令, CPU执行该指令后,转去执行BIOS中的硬件系统检测和初始化程序。
(2)
初始化程序将建立 BIOS所支持的中断向量,就是将 BIOS提供的中断例程的入口地址登记在中断向量表中。
(3)
硬件系统检测和初始化完成后,调用 int19h 进行操作至统引导。从此将计管机交由操作统控制。
(4)
DOS启动后,除完成其它工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量。
汇编指令
汇编语言寄存器的英文全称中英对照表
reg(寄存器)
#ax、bx、cx、dx、sp、bp、si、di
sreg(段地址寄存器)
#ds、ss、cs、es
AH&AL=AX(accumulator):累加寄存器
BH&BL=BX(base):基址/偏移地址寄存器
CH&CL=CX(count):计数寄存器
DH&DL=DX(data):数据寄存器
SS(Stack Segment):堆栈段地址寄存器
SP(Stack Pointer):堆栈指针寄存器 # SS:SP
CS(Code Segment):代码段地址寄存器
IP(Instruction Pointer):指令指针寄存器(代码段偏移地址) # CS:IP
DS(Data Segment):数据段地址寄存器,配合偏移段地址[]使用;# DS:0000
ES(Extra Segment):附加段寄存器,用于
#缓冲区,也可以写入中断代码,然后在code段初始化时修改中断向量表0000:0000指向ES
movsb指令的功能是将DS:SI指向的内存单元中的字节送入ES:DI中,然后根据标志寄存器DF位的值,将SI和DI递增或递 减。同理应有movsw,movsd,movsq ES的值在asm文件中可声明segment或在代码里mov es,xx
常和rep搭配使用,
cld
rep movsb #rep的作用重复执行后面的movsb,次数为cx的值,传输方向用cld,和std指令控制
DF direcion flag,DF=l时,每次操作后使SI和DI减小,DF=O时则增大, cld std
# 修改方式,
cld 指令df=0增大,增大的尺度取决于是movsb(自增1),还是movsw(自增2)
std 指令df=1减小
SI(Source Index):源偏移址寄存器
DI(Destination Index):目的偏移址寄存器
# 这两个和BX都能当作偏移地址,即[BX] [SI] [DI] [1] [BX+1] [BX+SI] [BX+DI+3], 错[BX+SI+DI]
# [BX+1]——[BX][1]——[BP].1——1[DI]——[BX][SI],都表示相加,BX、BP只用一个,DI、SI只用一个
BP(Base Pointer):(栈段偏移地址)基址指针寄存器
# 可以代替BX的偏移地址作用,在[]中使用BP时(mov cx,[bp]),
# 若指令没有规定段地址(如[bp],而没写cs:[bp]),则默认段地址为SS.
# 也就是说[BX]->DS:BX;[BP]->SS:BP ,BP作用临时替代SP,实现保护SP的作用不会被更改
# 用于把栈段当数据段读写
lp: push bp # 此时sp-1了
mov,bp,sp
………… 此过程凡是涉及SS修改的操作全用bp。mov [bp],'xx' 或 mov ss:[bp],'xx'
call far ptr lpret
pop bp # 此时sp+1了,正好恢复为初始状态
lpret:
………… 此过程凡是涉及SS修改的操作全用bp。mov [bp],'xx' 或 mov ss:[bp],'xx'
retf
# 运算结果记录 标志位--------------------------标志寄存器----------------------------
OV UP EI NG ZR NA PE CY
符号溢出进位 负正 零 偶奇 进位
OF UP EI SF ZF NA PF CY
ZF zero flag零标志运算结果等于0时为l否则为0 ZR(Zero) NZ(Not zero)
#等于0
SF sign Flag符号标志记录运算结果的符号,结果负时为1, NG(负数Negteive) PL(正数Positive)
# -127存储为(01111111-反码>10000000-加1>10000001), (11110000)既表示+240也表示-16(00010000)
CF carry flag进位标志最高有效位产生进位时为1,否侧为0, CY(溢出进位Carry) NC(未溢出Not carry)
#进位溢出
AF auxiliary carry flag辅助进位标志运算时,第3位向第4位产生进位时为1,否则为0
# 和CF的区别是不判断 mov al,xx
OF overflow flag溢出标志操作数超出机器能表示的范围表示溢出,溢出时为1 OV(符号溢出) NV(未溢出)
#符号溢出,对于8位的有符号数据,机器所能表示的范围就是-128~127。对于16位是-32768~32767
PF parity flag奇偶标志运算结果操作数位为1的个数为偶数个时为1否则为0 PE(偶Even) PO(奇Odd)
# 奇偶
IF interrupt flag 中断标志IF=l时,允许CPU响应可屏蔽中断,否则关闭中断
TF trap flag 用于调试单步操作
IF/TF修改的操作如下图
标志寄存器(0000 00IT 0000 0000)
-e 修改内存数据
-e 交互模式修改 0000回车,AB空格
h 高8位
l 低8位
AX=0008 BX=0008 CX=1000 CS=073F IP=010B
-a
073F:010B mov ah,13
073F:010D mov bl,33
073F:010F mov ch,al
073F:0111
-t
AX=1308 BX=0008 CX=1000 CS=073F IP=010D
-t
AX=1308 BX=0033 CX=1000 CS=073F IP=010F
-t
AX=1308 BX=0033 CX=0800 CS=073F IP=0111
AX=1308 BX=0033 CX=0800 CS=073F IP=0111
-a
073F:0111 add ax,03
073F:0114 mov bx,04
073F:0117 mov cx,ax
073F:0119
-t
AX=130B BX=0033 CX=0800 CS=073F IP=0114
-t
AX=1308 BX=0037 CX=0800 CS=073F IP=0117
-t
AX=1308 BX=0037 CX=0837 CS=073F IP=0119
-- 加
add bh,F1
如果加和结果为1F236,只显示为F236-- (无论高八位还是低八位有进借位都会舍给CF记录(CY,NC))
-- 减
sub减
sub ax(000F),10 结果为FFFF
(减的结果越界会向前借1,即使没有,如果操作的是高低八位,不会互相影响)
-- 含进位加
adc ax,1 即 ax=ax+1+cf
-- 含进位减
sbb ax,1 即 ax=ax-1-cf
-- mul乘(低于255的数相乘,AX=AL*BL)
计算100*10
AX=0064
BL=0A
-a
mul bl
会使
AX=03E8
-- mul乘(高于255的数相乘,结果为DXAX)
计算100*10000(结果为64*2710=F4240)
AX=0064
BX=2710
-a
mul bx
输出
DX=000F AX=4240
总结
AX=AL*BL
DXAX=AX*BX
# imul,idiv是对有符号负数运算的乘除
div除
-- 10000(2710)/100(64)=100(64)
AX=2710
BL=64 除以 商 余
-a
div bl AX / BL AL AH
结果为AX=0064 AH=00(余数)
-- 1000000(F4240)/10000(2710)=100(64)
DX=000F
AX=4240
BX=2710
-a
div bx DXAX / BX AX DX
结果为AX=0064 DX=0000(余数)
-- 与and运算
mov al,01100011B(debug中要写成十六进制)
and al,00111011B
结果
al=00100011B
-- 或or运算
mov al,01100011B
or al,00111011B
结果
al=01111011B
-- 异或xor运算
xor ax,bx (对ax和bx进行异或运算,对应两位相异则为1,相同为0,结果赋值ax)
-- 左移一位(右侧补0,进的位覆盖给标志寄存器的cf), 乘2
shl al,1
想一次性移动2位
mov cl,2
shl al,cl
-- 右移一位(左侧补0,最后一位移出覆盖给cf), 除以2
shr al,1
循环移动(不补0)(0110->1100->1001->0011)
rol ax,1
ror ax,1
移动(带进位)
rcl和rcr
-- 加1减1(自增自减,越位会借inc FFFF即0000,同add和sub)
inc ax 加1
dec ax 减1
-- 交换数据
xchg ax,bx
-- 取反,反码
neg ax
-- 中断
int
-d 段地址:偏移位/地址 查看多少位
段地址*16
(即左移一位)+偏移位/地址=物理地址
段 偏 物理
2000 1F60 21F60
2100 0F60 21F60
21F0 0060 21F60
21F6 0000 21F60
1F00 2F60 21F60
mov ax,[60] -- 将ds:0060处的数据(高低八位)赋给ax
mov al,[60] -- 将ds:0060处的数据(八位)赋给al
mov ax,[bx] -- 将ds:[bx]处的数据赋给ax
mov ax,[bx+1] -- 也合法,因为bx有做偏移地址的功能,将ds:[bx+1]处的数据
mov [si],bx -- 将bx的值x写到ds:[si]处,推荐写法 mov word ptr [si],bx
DS修改方式
mov ax,21F0
mov ds,ax
修改寄存器数据
-r cs
CS 073F
:输入要修改为数据(直接回车不修改)
jmp指令(跳转CS偏移地址)
CS=2000 IP=0000
-t执行
经分析后,可知指令执行序列为:
(1)mov ax,6622H
(2)jmp 1000:3
(3)mov ax,0000
(4)mov bx,ax
(5)jmp bx -- 相当于 ip <-- bx(mov ip,bx不合法)
(6)mov ax,0123H
(7)转到第3步执行(死循环)
cmp指令(比较大小,条件跳转)
————————————————————————————————————————————非重点作为了解—————————————————————————————————————————————
mov ax,8
mov bx,3
cmp ax,bx(判断 ax-bx 的结果)
执行后:zf=0,cf=0。说明 ax-bx 的结果不为 0 且不用借 1减,所以 ax>bx
如果(ax)=(bx)则(ax)-(bx)=0, 所以: zf=1;
如果(ax)≠(bx)则(ax)-(bx)≠0,所以: zf=0;
如果(ax)<(bx)则(ax)-(bx)将产生借位,所以: cf=1;
如果(ax)≥(bx)则(ax)-(bx)不必借位,所以: cf=0;
如果(ax)>(bx)则(ax)-(bx)既不必借位,结果又不为0,所以: cf=0 && zf=0;
如果(ax)≤(bx)则(ax)-(bx)既可能借位,结果也可能为0,所以: cf=1 || zf=1。
下面是常用的根据无符号数的比较结果进行转移的条件转移指令。
例子:
cmp ah,bh
je 073F:0100
————————————————————————————————————————————重点——————————————————————————————————————————————————————
指令 含义 检测的相关标志位
je/z 等于则跳转 zf=1 equal
jne 不等于则跳转 zf=0
jb 低于则跳转 cf=1 below
jnb 不低于则跳转 cf=0
ja 高于则跳转 cf=0且zf=0 above
jna 不高于则跳转 cf=1或zf=1
jcxz 此时先判断 cx==0 才跳转
栈自动跟位补位特性
SS:SP指向栈顶(左栈顶60 10 00 F0 08 00栈底右)
高八位先入栈
-d 0000:0000 5
0000:0000 60 10 00 F0 08 00
-r
AX=0000 BX=0000 DS=073F
-a
mov ds,bx
-t
AX=0000 BX=0000 DS=0000
-a
mov ax,[0000]
-t
AX=1060 BX=0000 DS-0000 -- 60 10 00 的10即高八位,60低八位
-- 入栈(push入栈的位置取决于SS:SP)
push ax 也可以push [0000]参考当前段地址ds
-- 出栈并赋值(pop出栈的值取决于SS:SP,pop空格后的用于接收值,pop完SP重新指向栈顶,原位置填充数据A301)
pop ax 也可以pop [0000]
pushf -- 把标志寄存器的值入栈
popf -- 出栈给标志寄存器的赋值
栈的内存标志
已知操作完栈的下一条指令的cs:ip 1234:5678
则栈为 78 56 34 12 A3 01 xx (xx即sp位置)A3 01 会一直包裹栈顶
编译程序
1、工作目录创建code.asm文件,此文件的汇编代码不区分大小写
写入
assume cs:codesg -- 声明代码段cs为codesg,同理 ds:xxx,ss:xxx
codesg segment -- codesg segment 和 codesg ends 包裹汇编指令
mov ax,0123H
mov bx,0456H -- 不写H编译器会认为是十进制数,写B代表二进制数,O代表八进制
add ax,bx
add ax,ax
mov ax,4c00H -- 用于程序退出时返回之前的启动程序
int 21H
-- 编译器可以操作 +-*/ 运算
codesg ends
end
2、用masm.exe编译asm文件
C:>\masm.exe
Source filename [.ASM]:键入code.asm
Object filename [code.OBJ]:
Source listing [NUL LST]:
Cross-reference [NUL CRF]:
51800 + 464744 Bytes symbol space free
0 Warning Errors
0 Severe Errors
同级目录生成一个code.obj文件 -- 乱码诞生
3、用link.exe将code.obj转成exe文件
C:>\link.exe
Object Modules [OBJ]:code.obj
Run File [CODE.EXE]:
List File [NUL.MAP]:
Libraries [.LIB]:
LINK warning L4021:no stack segment -- 此警告是因为没写基段地址
同级目录生成一个code.exe文件
4、此exe文件可以用debug打开——C:>\debug.exe code.exe
计算2**12
(loop指令相当于jmp指令不过会牵扯cx计数)
# --------------------------------------------方法一----------------------------------------------#
assume cs:code
code segment
mov ax,0002H
mov bx,0002H
mov cx,000BH -- 计数器,每次loop时,自减1
s:
mul bx -- ax=ax*bx,2*2---这是起始状态2^2,说明还需乘10次才是2^12,则loop应运行11次,故cx=B
loop s -- 循环s定义的代码段,每次修改IP为s:的偏移地址(debug界面显示loop 0006,0006即s:的偏移), loop时先dec cx,当结果为0时,不在跳转ip,继续往下执行其他代码
mov ax, 4C00H
int 21H
code ends
end
# --------------------------------------------方法二----------------------------------------------#
assume cs:code
code segment
mov ax,0002H
mov cx,000BH -- 计数器,每次loop时,自减1
s:
add ax,ax -- ax=ax*2
loop s -- 循环几次cx就写几
mov ax, 4C00H
int 21H
code ends
end
-- 死循环
s:inc cx
调试时如果想直接看运算结果,-u查看代码终止的偏移地址为0012
当前 IP=0000
输入 -g 0012
即可直接运算完之间的指令
-p 回车 可以直接运算完循环,IP指向循环下一条指令(pass)
call和ret指令
assume cs:code
code segment
mov ax,2
mov cx,11
call s # ip指向s:的偏移地址,即add ax,ax debug实例 CALL 000F
int 21H
s: # s字符可以自定义,作用标注
add ax,ax
loop s
ret # ip指向call的下一条指令,即int 21H
code ends
end
-- call和ret的本质
call 实质是 push ip (call的下一条指令对应的的ip)
ret 实质是 pop ip (当前ip接收值)
如果call和ret需要修改段地址CS,
写法与实质:
-- call far ptr s
先
push cs
push ip(call的下一条指令对应的的ip)
再把
cs:ip指向s:的段地址:偏移地址,debug实例 CALL 076A:000F
-- retf
pop ip
pop cs
数据存储类型
类型 debug命令(define byte) 长度
BYTE (1字节) db 12,23 8 # 内存里是 12 34
WORD (2字节) dw 1234,0033 16 # 内存里是 34 12 33 00
DWORD (4字节) dd 32
QWORD (8字节) dq 64
主要用于往地址中写数据时,
如 mov word ptr [si],bx
如 mov dword ptr [si],000A,0057
如 mov dword ptr [si],056A,5729
如 mov qword ptr [si],568A,5789,568A,5789
assume cs:code
codesg segment
dw 123H,456H,0ABCH,0F569H -- 语法十六进制数不能以字母开头,头要加0
start:
mov ax,0 -- start 用于区分数据和指令防止混肴,将cs:ip指向mov而不是dw
add ax,1
code ends
end start
分段存储
assume ss:stack,ds:data,cs:code
-- 先写栈,方便代码初始化sp
stack segment
db 6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0 可简写为 db 16 dup(0) -- 不加h就是十进制数
# 写字符 db 3 dup('hello','aliak') 接着前面会自动根据ascii码写入
# db 1,2,3,1,2,3,1,2,3 可简写为 db 3 dup(1,2,3),接着前面写,不满16个后面会写入0
stack ends
data segment
#dw 012fH,0235H,666
#db '你好HeLLo' -- 写入的字节取决于asm文件的编码是UTF还是GBK
db 1 dup("hello",'aliak') -- 接着前面写,不满16个后面会写入0,单双引号都兼容
data ends
code segment
start:
mov ax,stack -- 将栈段地址赋给ax
mov ss,ax -- 段寄存器只能接收普通寄存器的mov
mov sp,0010H -- 如果stack写的是dw,则此处为0020H,相应start调整的cs更靠后了
mov ax,data
mov ds,ax -- 将数据段地址赋给ds
-- 上面代码必须写作为初始化条件,下面才是真正要做的事的写代码的位置
mov ax, 4C00H
int 21H
code ends
end start
最终连续存储
数据段(076A)076A:0000 68 65 6C 6C 6F 61 6C 69-61 6B 00 00 00 00 00 00 helloaliak
和栈段(076B)076A:0010 06 06 06 06 00 00 00 00-00 00 00 00 00 00 00 00
代码段(076C)076A:0020 B8 6B 07 8E DO BC 20 00-B8 6A 07 8E D8 FF FF 74
谁大谁小取决于 asm 文件写的代码顺序(不是assume那一行而是下面的),
代码篡改可能,疯狂pop
—————————————————————————————栈的安全性取决于 ss,sp无论怎么变都不会改变 sp,会循环
防止溢出,应在
stack db 0fff1h dup(0) ,
sp == 8000h
data db 0fff1h dup(0)
这样于段和段之间的物理地址才不会有重叠
code segment
如下图,当 sp=ffff时,再 pop 往后不够两个字节,结果会是 01A3而不是 A3A3, 但 sp 会变成 0001
同理, mov [ffff],2345h 会把下一个代码段的第一个字节改为 23
所以初始化 -----------------------------------------
mov ax,stack
mov ss,ax
mov sp,8000h
mov ax,data
mov ds,ax
----------------------------------------------------------
offset指令获取偏移地址,多用DI、SI、BP、BX接收
-- 运行两次mov,bx,bx来学习offset和nop
code segment
start:
mov si,offset s :0000 # 将s对应指令的偏移地址赋给si, 即mov si,0006
mov di,offset s0 :0003 # 将s0对应指令的偏移地址赋给di,即mov di,000E
s: mov bx,bx :0006 debug里下面有个CS:命令,机器码2E
mov cx,cs:[si] :0009 # 把cs:[si]对应的数据赋给cx,此数据就是mov bx,bx的机器码(89DB)
mov cs:[di],cx :000C # 将nop指令改为mov bx,bx,即当执行到nop指令处,nop指令变为 mov,bx,bx
s0:
nop -- 占位,机器码为00,常用于计算代码长度,比如offset s0 - offset s,就是
nop -- s:代码的长度,然后配合movsb,cld来拷贝指令代码到其他位置
-- 为什么写两次,因为mov cs:[di],cx是传入的word,而nop机器码为90只有一个
code ends -- 其实写一个nop也没关系,反正会覆盖掉后面的机器码
-----------------------------------------
mov cx,cs:[di] 是伪指令,debug要写成
-a
cs:
mov cx,[di]
但asm不能这样写,因为此时cs:会被解析类似s:的含义
jmp的机器码与长短跳转
## 近跳转
jmp short s -- EB 03 此处的03 代表ip(下条指令)加几的数,取决于此指令和s对应指令之间的机器码个数
add ax,1 -- 05 01 00 |
s: inc ax -- 40 |
# V
# 因此最大机器码个数为FF个(255)
近跳转也可以
jmp near ptr s -- 区别 EB XX 90,也就是在后面会补一个nop(机器码90)指令
## 远跳转
jmp far ptr s -- EA 0C 01 6A 07 -u 显示为 jmp 076A:010C 不再是相对此指令的距离
db 256 dup(0)
s: inc ax 076A:010C
跳转目的到内存地址
一直数据段 23DF:0000 12 32 55 00
jmp word ptr ds:[1] -- word 表示读取ds:[1]的两个字节(高低八位)作为jmp的偏移地址 即 jmp 5532
对应debug版本写法
-a
ds:
jmp word ptr [1]
若要远跳转即想改变cs
已知ds:0000 01 23 66 55
jmp dword ptr ds:[0] -- 此指令效果相当于, ip=2301, cs=5566
-- 想要给ds:0000写入 00 00, 写法为:
mov word ptr ds:[0],0
数组(本质就是偏移字节[]
和偏移地址的转换,只有db的[]
才真是数组下标)
data:
arr db "a234"
arr1 dw "哈","在" -- asm是gbk编码才可以,就是相当于 arr1 dw 0B9FEh,0D4DAh
arr2 dd 2222h,"哈","哈"
arr3 dq 2222h
code:
mov ax,type arr 即 mov ax,0001 -- 一维数组
mov ax,type arr1 即 mov ax,0002 -- 二维数组
mov ax,type arr2 即 mov ax,0004 -- 四维数组
mov ax,type arr3 即 mov ax,0008 -- 八维数组
___________________________________________________________________________________________
data:
arr db "a234"
arr1 dw "哈","在" -- asm是gbk编码才可以
arr2 dd 2222h,36
arr3 dq 2222h
code:
-- 中括号内代表arr的字节位,不是字符位,作此运算前一定要先把stack-->ds
mov al,arr[0] /[arr+0] 即 mov al,'a'的ASCII值(位置在此时的ds:xx处,xx是arr[0]所在的偏移地址)
mov arr[0],al 相当与写入数组的操作
mov ax,arr1[0] 即 mov ax,'哈'的gbk值(位置在此时的ds:xx处,xx是arr1[0]所在的偏移地址)
mov ax,arr1[2] 即 mov ax,'在'的gbk值(位置在此时的ds:xx处,xx是arr1[2]所在的偏移地址)
mov word ptr arr[0],ax
mov byte ptr arr1[0],al
mov ax,word ptr arr2[0]
mov al,byte ptr arr3[0]
用debug写一个打印AAA.com程序
-e 0130 "AAA$" # 0130会当作ds的偏移地址
-d 100
0F2F:0100 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
0F2F:0110 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
0F2F:0120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
0F2F:0130 41 41 41 24 00 00 00 00-00 00 00 00 00 00 00 00 -- 即AAA$
0F2F:0140 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
-a
0F2F:0100 mov dx,0130 -- (数据地址)
0F2F:0103 mov ah,09 -- (代表输出ds:dx的字符串,$结束标志)
0F2F:0105 int 21 -- (退出程序)
0F2F:0107 mov ah,4c -- (扫尾)
0F2F:0109 int 21
0F2F:010B
-d 100
0F2F:0100 BA 30 01 B4 09 CD 21 B4-4C CD 21 00 00 00 00 00
0F2F:0110 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
0F2F:0120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
0F2F:0130 41 41 41 24 00 00 00 00-00 00 00 00 00 00 00 00 -- 即AAA$
0F2F:0140 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
-r
CX 0000
:34 -- 表示机器码的长度
-n C:\AAA.com #保存为文件,其中没有ds的信息,因为dubug默认打开DS=CS
-w
-q
C:\>AAA.COM
AAA
C:\>
-- DOS默认打开时DS=CS,而IP默认等于0100
以上代码还可以进一步压缩和优化,0110-0120空间用不到都浪费了
还有一点性能快慢问题, 立即数>寄存器>内存,com文件默认打开的CS=DS,IP偏移为0100,优化后的com文件用winhex打开如下:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000000 BA 0B 01 B4 09 CD 21 B4 4C CD 21 41 41 41 41 24
BA 0B 01 表示dx=010B
B4 09 表示AH=09
CD 21 表示int 21H
B4 4C 表示AH=4c
41 41 41 41 24 表示 AAAA$ 的ASCII
用编译器写一个打印程序,直接运行编译后的exe文件
assume ds:data,cs:code
data segment
str db "hello world","$"(36、24H均可) -- 打印时遇到24会停止,想换行用0ah
data ends
code segment
start:
mov ax,data -- 将数据段地址赋给ax
mov ds,ax -- 将数据段地址赋给ds
mov dx,offset str
mov ah,9
int 21h -- 根据ah值,会自动打印ds:dx往后的字节直到24h
mov ah,4ch
int 21h -- 根据ah值,做程序退出处理,移交CPU控制权还给启动自身的程序
code ends
end start
8086cpu打印原理
b800h段存放程序退出中断时的屏幕输出信息,可以用es把数据写到这个区域,写其他区域不行
屏幕
左上角字符对应的ASCII码存放在偏移00a0
左上角第二行偏移是 00a0+00a0
右上角偏移是00a0h+00a0-0002
左下角偏移是00a0h*0018h #其实左下角是00ah*0019h,不过程序返回时接手程序又把最后一行像素重写了
右下角偏移是00a0h*0019h-2h #同理右下角想回显也只能往上提一行
每一行像素的和正好也是是AOh个,0b800:00a0的 word值高位是属性信息,低位是 Ascii
高八位解析
7 6 5 4 3 2 1 0
BL R————————G————————B I R————————G————————B
闪烁为1 背景色 高亮为1 字符颜色
mov ax, 0b800h
mov es, ax
mov ah,01010000b
mov al,"L"
mov es:[00a0h*0010H+8*8],ax
mov ah,01010000b
mov al,"O"
mov es:[00a0h*0010H+8*8+2],ax
mov ah,01010000b
mov al,"V"
mov es:[00a0h*0010H+8*8+2+2],ax
mov al,"E"
mov es:[00a0h*0010H+8*8+2+2+2],ax
-- 可以用loop循环或者栈或者movsb/movsw
mov ah,00110000b
mov al,"H"
mov es:[00a0h*0018H+8],ax
mov ah,00110000b
mov al,"E"
mov es:[00a0h*0018h+8*2],ax
mov ah,01010000b
mov al,"L"
mov es:[00a0h*0018h+8*3],ax
mov ah,00000111b
mov al,"L"
mov es:[00a0h*0018h+8*4],ax
mov ah,10110010b
mov al,"O"
mov es:[00a0h*0018h+8*5],ax
屏幕打印和ES没关系,和cpu显存制造初始任务定义的物理地址才有关
也就是
mov ax,0
mov es,ax
mov ax,4c00h
int 21h
;输出结果如下图
注释
;kjlsahjsfhashjfkhj -- 分号直到换行进行单行注释
comment*多行注释可换行*comment --comment是关键字不能随便改
ASCII
已知ASCII码
大写字母ASCII + 20H = 小写字母ASCII
A-Z xx0xxxxx
a-z xx1xxxxx
20H 00100000 即空格ASCII,与或 00100000B 运算是自己
小写字母转大写即
减 00100000,结果和与运算即 and 11011111B没区别,节省性能开销
大写转大写也可以用 and 11011111B
同理
字母转小写
or 00100000B
总结
and 11011111B -- 转大写
or 00100000B -- 转小写
-- -------------------------------数组转大小写输出-----------------------------------------------------
assume cs:codesg,ds:data
data segment
str db 'heLLo World','$'
data ends
codesg segment
start:
mov ax,data -- data 是段地址
mov ds,ax
mov bx,0
mov cx,11 -- 循环次数,即数组长度
s:
mov al,[bx] -- ds:[bx]的值
or al,0100000B -- or转小写,and转大写
mov [bx],al -- 写入ds:[bx]的值
inc bx
loop s
;mov dx,offset str -- 单行注释,str代表str[0]的字节偏移地址
lea dx,str -- 等价上面写法
mov ah,09h
int 21H
mov ah,4ch
int 21H
codesg ends
end start
-- -------------------------------数组求最大ASCII值字符并输出------------------------------------------
assume cs:codesg,ds:data
data segment
str db 'hello world','$$'
data ends
codesg segment
start:
mov ax,data
mov ds,ax
mov bx,offset str -- str代表str[0]的字节偏移地址
mov cx,11
mov ah,0
s:
mov al,[bx]
cmp ah,al
jnb s1 -- 不小于则跳转
mov ah,al
s1:
inc bx
loop s
mov [bx],ah -- 或者 mov al,24h 然后 mov [bx],ah mov [bx+1],al
mov dx,bx
mov ah,9
int 21H
mov ah,4cH
int 21H
codesg ends
end start
-- -------------------------------数组求和并输出------------------------------------------
assume cs:codesg,ds:data,ss:stack
stack segment
db 0fff1h dup(0)
stack ends
data segment
str db 1,2,3,4,10,20,30,40,'$'
db 0fff1h-9 dup(0) ;-- 因为上面写了9个字节所以减9
data ends
codesg segment
start:
mov ax,stack
mov ss,ax
mov sp,8000H
mov ax,data
mov ds,ax
;###################### 上面是初始化模板 #######################
mov bx,offset str ;-- str代表第一个的字节偏移地址,即str[0]也是[str+0],ds:str的值机器码为01
mov cx,8
mov al,0
s:
add al,[bx]
inc bx
loop s
mov [bx],al -- 低八位一个字节
mov byte ptr [bx+1],24h ;-- 24h即'$',只有一位字节
mov dx,bx
mov ah,9
int 21H
mov ah,4cH
int 21H
codesg ends
end start
数组的拷贝 bx用作循环因子
mov al,[arr1+bx]
mov [arr2+bx],al
inc bx
反转拷贝
mov cx,xxx -- xxxx是arr1数组的长度
mov bx,0
mov di,xxx -- xxx是arr1数组的长度减1
s:
mov al,[arr1+bx]
mov [arr2+di],al
inc bx
mov di,xxx -- xxx是arr1数组的长度减1
sub di,bx
loop s
#########或者
mov si,0
mov di,xx -- xx就是要拷贝的数组arr1的长度减1
s:
mov al,[arr1+si]
mov [arr2+di],al
inc si
dec di
loop s
#########或者用栈,栈只适合作用word型,栈十分适合反转word数组,因为先push的最后pop
arr1 dw 0001,0002,0003,0004,0005
arr2 dw 0
mov cx,5 ;-- 是arr1数组的字符长度
mov si,offset arr1
mov di,9 ; -- 9 是arr1的字节长度减1
s:
push word ptr [arr1+si] -- push和pop只能作用word不能作用byte
pop word ptr [arr2+di]
add si,2
sub di,2
loop s
###想作用byte型
mov si,xx
mov ds:[si],al
双循环
外5内5
mov ax,0
mov dx,0
mov cx,5
for:
mov bx,cx
mov cx,5
#-------------------内循环
for1:
inc dx
loop for1
#-------------------内循环
mov cx,bx
inc ax
loop for
结果ax 5 dx 5*5
二维数组
怎么表示
xxx=[ [1,2,3],
[a,b,c],
[9,f,a,s,d,f]
]
str db '123'
db 'abc'
db '9fasdf'
mov al,str[0][2] # 即al为3对应xxx[0][2]
mov al,str[0][4] # 即al为'b'对应xxx[1][1]
mov al,str[0][9] # 即al为'a'对应xxx[2][3]
mov al,ds:str[bx+5] str[bx][5] [str][bx][5] [str+bx+5] 都一样
-- -----------------------------------二重循环把指定矩形转大写---------------------------------------
assume cs:codesg,ds:data,ss:stack
data segmeNT
str db 'aaaaabbbbbccccc '
db 'aaaaabbbbbccccc '
db 'aaaaabbbbbccccc '
db 'aaaaabbbbbccccc ','$'
data ends
stack segment
db 10 dup (0)
stack ends
codesg SEgment
start:
mov ax,data
mov ds,ax
mov bx,0
mov cx,4
for:
mov dx,cx
mov si,0
mov cx,5
for1:
mov al,ds:str[bx+si]
and al,11011111B
mov ds:str[bx+si],al
inc si
loop for1
mov cx,dx
add bx,16
loop for
lea dx,str ;即 mov dx,offset str 优相比mov的优点 可以 lea dx,[str-0c5h]
mov ah,9
int 21H
mov ah,4cH
int 21H
codesg ends
end start
冒泡排序
assume cs:codesg,ds:data,ss:stack
data segmeNT
arr db 0A2H,24H,07H,3AH,1BH,0F1H,3BH,25H,81H
data ends
stack segment
db 10 dup (0)
stack ends
codesg SEgment
start:
mov ax,data
mov ds,ax
mov bx,0
mov cx,8
for:
mov dx,cx
mov si,8
mov cx,8
sub cx,bx
for1:
mov ah,ds:arr[si]
mov al,ds:arr[si-1]
cmp ah,al
jnb all
xchg ah,al
mov ds:arr[si],ah
mov ds:arr[si-1],al
all:
dec si
loop for1
mov cx,dx
add bx,1
loop for
mov ah,4cH
int 21H
codesg ends
end start
-- C++写法
comment*
bx
for(int i=0;循环4次;i+=16){
si
for(int j=0;循环五次;j++){
arr[j+i]转大写
}
}
for(int i=0;i<;i+=16)
arr[i+5]转大写
for(int i=0;i<arr.size();i++){
for(int j=0;j<arr[0].size();j++){
cout <<arr[2][5]<<" ";
}cout<<endl;
}
*comment
comment*
c++
数组当中的最大值
int res=0
for(int i=0;i<str.size();i++)if(res<s[i])res=s[i];
return res
求最小值
int res=FF
for(int i=0;i<str.size();i++)if(res>s[i])res=s[i];
return res
for(int i=0;i<str.size();i++)str[i]转大写
*comment
斐波那契数列求和
assume cs:code,ds:data,ss:stack
data segment
arr dw 1H,1H,100 dup (0)
res db 800 dup (0)
data ends
stack segment
db 100 dup (0)
stack ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov bx,4
mov cx,30
for :
mov dx,0
add dx,ds:arr[bx-2]
add dx,ds:arr[bx-4]
mov ds:arr[bx],dx
add bx,2
loop for
mov ax,4c00H
int 21h
code ends
end start
十进制转十六进制并输出
assume cs:codesg,ds:data,ss:stack
data segmeNT
str db '00002333','$'
res db '0000','$'
data ends
stack segment
db 100 dup (0)
stack ends
codesg SEgment
start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,10
mov si,4
mov bx,0
mov cx,8
mov ax,0
s:
mov dx,ax
shl ax,1
shl ax,1
shl ax,1
shl dx,1
add ax,dx
;乘10
add al,str[bx]
adc ah,0
sub ax,30H
inc bx
loop s
;ax=3039
mov cx,4
l:
mov dx,ax
and dx,0FH
add dx,30H
cmp dx,3AH
jb s1
add dx,7H
;求出3039的ASCII码,push
s1:
dec si
mov ds:res[si],dl
shr ax,1
shr ax,1
shr ax,1
shr ax,1
loop l
;mov dx,offset res
lea dx,res
mov ah,9
int 21H
mov ax,4c00H -- al是返回码
int 21H
codesg ends
end start
端口读写
端口为word长度 00000000 00000000
对`0~255`以内的端口进行读写:
in al,0020h ;从20h端口读入一个字节
out 0020h,al ;往20h端口写入一个字节
对`256~65535`的端口进行读写时,端口号放在dX中
mov dx,03f8h
in al,dx;从03f8h端口读入一个字节给AL
out dx,al;向3f8h端口写入一个AL字节
必须用AX和端口交互
猜测
in ax,00ffh 是在ffh端口读两个字节
端口70h是时钟CMOS RAM接口
00 00 00 00 00 00 00
年 月 日 时 分 秒
用BCD码判断
端口60h是键盘输入接收端口
BCD码
00010100B表示14(十四)
10010001B表示91(九十一)
BCD码值 + 30h = 对应数字的ASCII
64
位操作系统的寄存器
eax,ebx,ecx,edx,每个可以存8个字节,6A3C 12E9
中断向量表
8086
cpu中断类型码8位 int00H -- int FFH
中断向量表(空间) 0000:0000-0000:03FF
0000:0200
之间是空的,可以用于存放用户的中断代码,注意是代码,不是向量,因此应把对应中断的向量表改为此处的CSIP
0000:02FF
2FF-200长度是256个字节,因此机器码长度最后不用超过256
规律如下
int 00H
即cs:ip 改为 00A7:1068
中断时的 标志寄存器的值 和 cs ip保存在栈中了
int 01H
即cs:ip 改为 0070:018B
中断时的 标志寄存器的值 和 cs ip保存在栈中了
int 02H
即cs:ip 改为 03A5:0016
中断时的 标志寄存器的值 和 cs ip保存在栈中了
中断本质(挂起/退出)
int N -- 十六进制N 挂起(如果有人iret)、退出(没人iret他的话就是编写退出了,是否回收内存取决于int 几)
(1)取得中断类型码N
(2)pushf -- 标志寄存器入栈
(3)TF=0,IF=0 -- IF interrupt flag 中断标志IF=l时,允许CPU响应可屏蔽中断,否则关闭中断
-- TF trap flag 用于调试单步操作
(4)push CS
(5)push IP
(6)IP=0000:[N*4],CS=0000:[N*4+2]
对应中断类型码查找中断向量表(位置ES:BX)赋值CSIP
# int 的反操作 iret 激活原来挂起的程序
恢复中断前的操作,此指令应放在中断指令紧接着的后方,而不是中断前的源位置
本质
(1) pop ip
(2) pop cs
(3) popf 也就是说int和iret都会改变sp的值,之所以简化写是因为iret的指令机器码只有一个Byte(CF)
int 0H 的机器码为 CD 00
########################################对比call和retf##########################################
-- call和ret的本质(在本段跳转)
call 实质是 push ip (call的下一条指令对应的的ip)
ret 实质是 pop ip (当前ip接收值)
如果call和ret需要修改段地址CS,
-- call far ptr s (跨段跳转)
先
push cs
push ip(call的下一条指令对应的的ip)
再把
cs:ip指向s:的段地址:偏移地址,debug实例 CALL 076A:000F
-- retf 此指令应放在跳转后的位置,放在跳转前的源位置是错误的做法
pop ip
pop cs
更改int 0H
中断的操作本质是修改中断向量表段空间
mov ax, 0 #设置中断向量表
mov es, ax
mov word ptr es:[0*4], xx xx是自定义操作代码对应的IP
mov word ptr es:[0*4+2], yy yy是自定义操作代码对应的CS
手工写一个int 7CH的中断,实现AX转大写
assume cs:code
code segment
mov ax,cs
mov ds,ax
mov si,offset coverA
mov ax,0
mov es,ax
mov di,200h
mov cx,offset len - offset coverA
cld
rep movsb
mov es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0h
mov ax,'al'
int 7ch
mov bx,cs
mov ds,bx
mov byte ptr ds:[0],ah
mov byte ptr ds:[1],al
mov byte ptr ds:[2],'$'
mov dx,0h
mov ah,9h
int 21H
mov ax,4c00h
int 21h
coverA:
-- 这个位置最好这样写,来保护局部变量
-- start: push cx
-- posh si
and ax,1101111111011111b ;执行转换从大写,此操作并没有更改cx,si,所以就注释了保护局部表里的操作
-- 这个位置最好这样写,来恢复之前的局部变量
-- ok: pop si
-- pop cx
iret
len:
nop
code ends
end
单步中断`int 01H`
标志寄存器的TF位标志单步中断 # 即debug界面的 -t指令
TF trap flag 用于调试单步操作
CPU执行完指令后检测TF,TF=1则产生单步中断
产生单步中断后的要做动作
push 标志寄存器
TF IF 重新设为0
push CS
push IP
(ip)=(1*4),(cs)=(1*4+2) # 根据中断向量表int 01H 记录的信息,将当前CSIP指针指向int 01H机器码指令的位置
^
|
|
|
即1号中断处理程序
1号中断处理程序的功能,显示所有寄存器中的内容后,等待输入命令
#特殊情况
当cpu修改SS栈段时,即便检测到 TF=1,也不会触发中断
原因是假如触发中断,
由于sp并没有指向合理的位置,所以会引起错误
因此,修改SP的指令要紧随修改SS的指令,连续存放
中断类型码功能或触发条件
中断的作用十分像调用函数
不同的函数/中断程序封装不同的功能
;编程:在屏幕中间依次显示“a”~“z”,并可以让人看清。在显示的过程中,按下'Esc'键后,改变显示的颜色。
;完整功能代码:
assume cs:code
stack segment
db 128 dup (0)
stack ends
data segment
dw 0,0
data ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,128
mov ax,data
mov ds,ax
mov ax,0
mov es,ax
push es:[9*4]
pop ds:[0]
push es:[9*4+2]
pop ds:[2] ;将原来的int 9中断例程的入口地址保存在ds:0、ds:2单元中
mov word ptr es:[9*4],offset int9
mov es:[9*4+2],cs ;在中断向量表中设置新的int 9中断例程的入口地址
mov ax,0b800h
mov es,ax
mov ah,'a'
s:
mov es:[160*12+40*2],ah
call delay
inc ah
cmp ah,'z'
jna s
mov ax,0
mov es,ax
push ds:[0]
pop es:[9*4]
push ds;[2]
pop es;[9*4+2] ;将中断向量表中int 9中断例程的入口恢复为原来的地址
mov ax,4c00h
int 21h
delay:
push ax
push dx
mov dx,2000h
mov ax,0
s1:
sub ax,1
sbb dx,0
cmp ax,0
jne s1
cmp dx,0
jne s1
pop dx
pop ax
ret
;------以下为新的int 9中断例程--------------------
int9:
push ax
push bx
push es
in al,60h
pushf
pushf
pop bx
and bh,11111100b
push bx
popf
call dword ptr ds:[0] ;对int指令进行模拟,调用原来的int 9中断例程
cmp al,1
jne int9ret
mov ax,0b800h
mov es,ax
inc byte ptr es:[160*12+40*2+1] ;属性增加1,改变颜色
int9ret:
pop es
pop bx
pop ax
iret
code ends
end start
80x86
I/O ADDR | INT TYPE(16进制) | FUNCTION |
---|---|---|
00 ~ 03 | 0 | 除法溢出中断 |
04 ~ 07 | 1 | 单步(用于DEBUG) |
08 ~ 0B | 2 | 非屏蔽中断(NMI) |
0C ~ 0F | 3 | 断点中断(用于DEBUG) |
10 ~ 13 | 4 | 溢出中断 |
14 ~ 17 | 5 | 打印屏幕 |
18 ~ 1F | 6/7 | 保留 |
8259
主片
I/O ADDR | INT TYPE(16进制) | FUNCTION |
---|---|---|
20 ~ 23 | 8 | 定时器(IRQ0) |
24 ~ 27 | 9 | 键盘(IRQ1) |
28 ~ 2B | A | 彩色/图形(IRQ2) |
2C ~ 2F | B | 串行通信COM2(IRQ3) |
30 ~ 33 | C | 串行通信COM1(IRQ4) |
34 ~ 37 | D | LPT2控制器中断(IRQ5) |
38 ~ 3B | E | 磁盘控制器中断(IRQ6) |
3C ~ 3F | F | LPT1控制器中断(IRQ7) |
BIOS
I/O ADDR | INT TYPE(16进制) | FUNCTION |
---|---|---|
40 ~43 | 10 | 视频显示 I/O根据ax,bx,dx等确定干啥 |
44 ~ 47 | 11 | 设备检验 |
48 ~ 4B | 12 | 测定存储器容量 |
4C ~ 4F | 13 | 磁盘 I/O |
50 ~ 53 | 14 | RS-232 串行口 I/O |
54 ~ 57 | 15 | 系统描述表指针 |
58 ~ 5B | 16 | 键盘 I/O |
5C ~ 5F | 17 | 打印机 I/O |
60 ~ 63 | 18 | ROM BASIC 入口代码 |
64 ~ 67 | 19 | 引导装入程序 |
68 ~ 6B | 1A | 日时钟 |
提供用户中断
I/O ADDR | INT TYPE(16进制) | FUNCTION |
---|---|---|
6C ~ 6F | 1B | Ctrl - Break 控制的软中断 |
70 ~ 73 | 1C | 定时器控制的软中断 |
74 ~ 77 | 1D | 视频参数块 |
78 ~ 7B | 1E | 软盘参数块 |
7C ~ 7F | 1F | 图形字符扩展码 |
在DOS系统(实模式)下,从0x20开始,用于操作系统本身。
DOS
I/O ADDR | INT TYPE(16进制) | FUNCTION |
---|---|---|
80 ~ 83 | 20 | DOS 中断返回 |
84 ~ 87 | 21 | DOS 系统功能调用 |
88 ~ 8B | 22 | 程序中止时 DOS 返回地址(用户不能直接调用) |
8C ~ 8F | 23 | Ctrl - Break 处理地址(用户不能直接调用) |
90 ~ 93 | 24 | 严重错误处理(用户不能直接调用) |
94 ~ 97 | 25 | 绝对磁盘读功能 |
98 ~ 9B | 26 | 绝对磁盘写功能 |
9C ~ 9F | 27 | 终止并驻留程序 |
A0 ~ A3 | 28 | DOS安全使用 |
A4 ~ A7 | 29 | 快速写字符 |
---|---|---|
A8 ~ AB | 2A | Microsoft 网络接口 |
B8 ~ BB | 2E | 基本 SHELL 程序装入 |
BC ~ BF | 2F | 多路服务中断 |
CC ~ CF | 33 | 鼠标中断 |
104 ~ 107 | 41 | 硬盘参数块 |
118 ~ 11B | 46 | 第二硬盘参数块 |
11C ~ 3FF | 47 ~ FF | BASIC 中断 |
保护模式
在Linux下(保护模式),没有使用BIOS设置的中断向量表,0x00 ~ 0x1F是CPU保护模式下的默认中断向量,而0x20开始,都是被Linux系统重新设置的。
X86占用的中断向量表如下:
向量号 | 助记符 | 说明 | 类型 | 错误号 | 产生源 |
---|---|---|---|---|---|
0 | #DE | 除出错 | 故障 | 无 | DIV或IDIV指令。 |
1 | #DB | 调试 | 故障/陷阱 | 无 | 任何代码或数据引用,或是INT 1指令。 |
2 | – | NMI中断 | 中断 | 无 | 非屏蔽外部中断。 |
3 | #BP | 断点 | 陷阱 | 无 | INT 3指令。 |
4 | #OF | 溢出 | 陷阱 | 无 | INTO指令。 |
5 | #BR | 边界范围超出 | 故障 | 无 | BOUND指令。 |
6 | #UD | 无效操作码 | 故障 | 无 | UD2指令或保留的操作码。 |
7 | #NM | 设备不存在 | 故障 | 无 | 浮点或WAIT/FWAIT指令。 |
8 | #DF | 双重错误 | 异常终止 | 有(0) | 任何可产生异常、NMI或INTR的指令。 |
9 | – | 协处理器段超越(保留) | 故障 | 无 | 浮点指令 |
10 | #TS | 无效的任务状态段TSS | 故障 | 有 | 任务交换或访问TSS |
---|---|---|---|---|---|
11 | #NP | 段不存在 | 故障 | 有 | 加载段寄存器或访问系统段 |
12 | #SS | 堆栈段错误 | 故障 | 有 | 堆栈操作或SS寄存器加载 |
13 | #GP | 一般保护错误 | 故障 | 有 | 任何内存引用和其他保护检查 |
14 | #PF | 页面错误 | 故障 | 有 | 任何内存引用 |
15 | – | (intel保留) | 无 | ||
16 | #MF | x87 FPU浮点错误 | 故障 | 无 | |
17 | #AC | 对齐检查 | 故障 | 有(0) | 对内存中任何数据的引用。 |
18 | #MC | 机器检查 | 异常终止 | 无 | 错误码(若有)和产生源与CPU类型有关。 |
19 | #XF | SIMD浮点异常 | 故障 | 无 |
INT 21H的功能
AH | 功能 | 调用参数 | 返回参数 |
00 | 程序终止(同INT 20H) | CS=程序段前缀 | |
01 | 键盘输入并回显 | AL=输入字符 | |
02 | 显示输出 | DL=输出字符 | |
03 | 异步通迅输入 | AL=输入数据 | |
04 | 异步通迅输出 | DL=输出数据 | |
05 | 打印机输出 | DL=输出字符 | |
06 | 直接控制台I/O | DL=FF(输入) DL=字符(输出) | AL=输入字符 |
07 | 键盘输入(无回显) | AL=输入字符 | |
08 | 键盘输入(无回显) 检测Ctrl-Break | AL=输入字符 | |
09 | 显示字符串 | DS:DX=字符串地址起点偏移地址 '$'结束字符串 | |
0A | 键盘输入到缓冲区 | DS:DX=缓冲区首地址 (DS:DX)=缓冲区最大字符数 | (DS:DX+1)=实际输入的字符数 |
0B | 检验键盘状态 | AL=00 有输入 AL=FF 无输入 | |
0C | 清除输入缓冲区并 请求指定的输入功能 | AL=输入功能号 (1,6,7,8,A) | |
0D | 磁盘复位 | 清除文件缓冲区 | |
0E | 指定当前缺省的磁盘驱动器 | DL=驱动器号 0=A,1=B,... | AL=驱动器数 |
0F | 打开文件 | DS:DX=FCB首地址 | AL=00 文件找到 AL=FF 文件未找到 |
10 | 关闭文件 | DS:DX=FCB首地址 | AL=00 目录修改成功 AL=FF 目录中未找到文件 |
11 | 查找第一个目录项 | DS:DX=FCB首地址 | AL=00 找到 AL=FF 未找到 |
12 | 查找下一个目录项 | DS:DX=FCB首地址 (文件中带有*或?) | AL=00 找到 AL=FF 未找到 |
13 | 删除文件 | DS:DX=FCB首地址 | AL=00 删除成功 AL=FF 未找到 |
14 | 顺序读 | DS:DX=FCB首地址 | AL=00 读成功 =01 文件结束,记录中无数据 =02 DTA空间不够 =03 文件结束,记录不完整 |
15 | 顺序写 | DS:DX=FCB首地址 | AL=00 写成功 =01 盘满 =02 DTA空间不够 |
16 | 建文件 | DS:DX=FCB首地址 | AL=00 建立成功 =FF 无磁盘空间 |
17 | 文件改名 | DS:DX=FCB首地址 (DS:DX+1)=旧文件名 (DS:DX+17)=新文件名 | AL=00 成功 AL=FF 未成功 |
19 | 取当前缺省磁盘驱动器 | AL=缺省的驱动器号 0=A,1=B,2=C,... | |
1A | 置DTA地址 | DS:DX=DTA地址 | |
1B | 取缺省驱动器FAT信息 | AL=每簇的扇区数 DS:BX=FAT标识字节 CX=物理扇区大小 DX=缺省驱动器的簇数 | |
1C | 取任一驱动器FAT信息 | DL=驱动器号 | 同上 |
21 | 随机读 | DS:DX=FCB首地址 | AL=00 读成功 =01 文件结束 =02 缓冲区溢出 =03 缓冲区不满 |
22 | 随机写 | DS:DX=FCB首地址 | AL=00 写成功 =01 盘满 =02 缓冲区溢出 |
23 | 测定文件大小 | DS:DX=FCB首地址 | AL=00 成功(文件长度填入FCB) AL=FF 未找到 |
24 | 设置随机记录号 | DS:DX=FCB首地址 | |
25 | 设置中断向量 | DS:DX=中断向量 AL=中断类型号 | |
26 | 建立程序段前缀 | DX=新的程序段前缀 | |
27 | 随机分块读 | DS:DX=FCB首地址 CX=记录数 | AL=00 读成功 =01 文件结束 =02 缓冲区太小,传输结束 =03 缓冲区不满 |
28 | 随机分块写 | DS:DX=FCB首地址 CX=记录数 | AL=00 写成功 =01 盘满 =02 缓冲区溢出 |
29 | 分析文件名 | ES:DI=FCB首地址 DS:SI=ASCIIZ串 AL=控制分析标志 | AL=00 标准文件 =01 多义文件 =02 非法盘符 |
2A | 取日期 | CX=年 DH:DL=月:日(二进制) | |
2B | 设置日期 | CX:DH:DL=年:月:日 | AL=00 成功 =FF 无效 |
2C | 取时间 | CH:CL=时:分 DH:DL=秒:1/100秒 | |
2D | 设置时间 | CH:CL=时:分 DH:DL=秒:1/100秒 | AL=00 成功 =FF 无效 |
2E | 置磁盘自动读写标志 | AL=00 关闭标志 AL=01 打开标志 | |
2F | 取磁盘缓冲区的首址 | ES:BX=缓冲区首址 | |
30 | 取DOS版本号 | AH=发行号,AL=版本 | |
31 | 结束并驻留 | AL=返回码 DX=驻留区大小 | |
33 | Ctrl-Break检测 | AL=00 取状态 =01 置状态(DL) DL=00 关闭检测 =01 打开检测 | DL=00 关闭Ctrl-Break检测 =01 打开Ctrl-Break检测 |
35 | 取中断向量 | AL=中断类型 | ES:BX=中断向量 |
36 | 取空闲磁盘空间 | DL=驱动器号 0=缺省,1=A,2=B,... | 成功:AX=每簇扇区数 BX=有效簇数 CX=每扇区字节数 DX=总簇数 失败:AX=FFFF |
38 | 置/取国家信息 | DS:DX=信息区首地址 | BX=国家码(国际电话前缀码) AX=错误码 |
39 | 建立子目录(MKDIR) | DS:DX=ASCIIZ串地址 | AX=错误码 |
3A | 删除子目录(RMDIR) | DS:DX=ASCIIZ串地址 | AX=错误码 |
3B | 改变当前目录(CHDIR) | DS:DX=ASCIIZ串地址 | AX=错误码 |
3C | 建立文件 | DS:DX=ASCIIZ串地址 CX=文件属性 | 成功:AX=文件代号 错误:AX=错误码 |
3D | 打开文件 | DS:DX=ASCIIZ串地址 AL=0 读 =1 写 =3 读/写 | 成功:AX=文件代号 错误:AX=错误码 |
3E | 关闭文件 | BX=文件代号 | 失败:AX=错误码 |
3F | 读文件或设备 | DS:DX=数据缓冲区地址 BX=文件代号 CX=读取的字节数 | 读成功: AX=实际读入的字节数 AX=0 已到文件尾 读出错:AX=错误码 |
40 | 写文件或设备 | DS:DX=数据缓冲区地址 BX=文件代号 CX=写入的字节数 | 写成功: AX=实际写入的字节数 写出错:AX=错误码 |
41 | 删除文件 | DS:DX=ASCIIZ串地址 | 成功:AX=00 出错:AX=错误码(2,5) |
42 | 移动文件指针 | BX=文件代号 CX:DX=位移量 AL=移动方式(0:从文件头绝对位移,1:从当前位置相对移动,2:从文件尾绝对位移) | 成功:DX:AX=新文件指针位置 出错:AX=错误码 |
43 | 置/取文件属性 | DS:DX=ASCIIZ串地址 AL=0 取文件属性 AL=1 置文件属性 CX=文件属性 | 成功:CX=文件属性 失败:CX=错误码 |
44 | 设备文件I/O控制 | BX=文件代号 AL=0 取状态 =1 置状态DX =2 读数据 =3 写数据 =6 取输入状态 =7 取输出状态 | DX=设备信息 |
45 | 复制文件代号 | BX=文件代号1 | 成功:AX=文件代号2 失败:AX=错误码 |
46 | 人工复制文件代号 | BX=文件代号1 CX=文件代号2 | 失败:AX=错误码 |
47 | 取当前目录路径名 | DL=驱动器号 DS:SI=ASCIIZ串地址 | (DS:SI)=ASCIIZ串 失败:AX=出错码 |
48 | 分配内存空间 | BX=申请内存容量 | 成功:AX=分配内存首地 失败:BX=最大可用内存 |
49 | 释放内容空间 | ES=内存起始段地址 | 失败:AX=错误码 |
4A | 调整已分配的存储块 | ES=原内存起始地址 BX=再申请的容量 | 失败:BX=最大可用空间 AX=错误码 |
4B | 装配/执行程序 | DS:DX=ASCIIZ串地址 ES:BX=参数区首地址 AL=0 装入执行 AL=3 装入不执行 | 失败:AX=错误码 |
4C | 带返回码结束 | AL=返回码 | 回显B800:00A0-B800:103F的显存空间信息 |
4D | 取返回代码 | AX=返回代码 | |
4E | 查找第一个匹配文件 | DS:DX=ASCIIZ串地址 CX=属性 | AX=出错代码(02,18) |
4F | 查找下一个匹配文件 | DS:DX=ASCIIZ串地址 (文件名中带有?或*) | AX=出错代码(18) |
54 | 取盘自动读写标志 | AL=当前标志值 | |
56 | 文件改名 | DS:DX=ASCIIZ串(旧) ES:DI=ASCIIZ串(新) | AX=出错码(03,05,17) |
57 | 置/取文件日期和时间 | BX=文件代号 AL=0 读取 AL=1 设置(DX:CX) | DX:CX=日期和时间 失败:AX=错误码 |
58 | 取/置分配策略码 | AL=0 取码 AL=1 置码(BX) | 成功:AX=策略码 失败:AX=错误码 |
59 | 取扩充错误码 | AX=扩充错误码 BH=错误类型 BL=建议的操作 CH=错误场所 | |
5A | 建立临时文件 | CX=文件属性 DS:DX=ASCIIZ串地址 | 成功:AX=文件代号 失败:AX=错误码 |
5B | 建立新文件 | CX=文件属性 DS:DX=ASCIIZ串地址 | 成功:AX=文件代号 失败:AX=错误码 |
5C | 控制文件存取 | AL=00封锁 =01开启 BX=文件代号 CX:DX=文件位移 SI:DI=文件长度 | 失败:AX=错误码 |
62 | 取程序段前缀 | BX=PSP地址 |