汇编学习
文章目录
寄存器
被除数 / 除数 = 商
- ax:
- 16位乘法计算(mul)存放低位结果
- 被除数16(除数8位)位除法计算(div)、结果 AL 存放商,AH 存放余数
- 被除数32(除数16位)位除法计算(div)、结果 AX 存放商,DX 存放余数
- bx
- cx:jcxz 有条件跳转指令,判断标志,为0时,跳转
- dx:
- 16位乘法计算(mul)存放高位结果
- 被除数32(除数16位)位除法计算(div)、结果 AX 存放商,DX 存放余数
- ss:栈中段寄存器
- sp:栈中偏移地址
- ds:数据中段寄存器
- cs:代码段中段寄存器
- ip:代码段中偏移地址
- si: 可做普通寄存器使用,类似 ax
- di: 可做普通寄存器使用,类似 ax
- bp
- es: 和 ds 类似,可以左段寄存器,例:es:[si]
定义数据
- db:data byte 定义字节数据
- dw:data word 定义字型数据
- dd:dword(double word)定义双字数据
- dup:配合 db、dw、dd使用,用来定义重复数据
例:
bd 3 dup (0) == db 0,0,0
显示区内存地址
B8000H ~ BFFFFH 共 32KB 空间,为80 * 25 彩色字符模式的显示缓冲区
在一行中,一个字符占两个字节的存储空间(一个字),低位字节存储字符的 ASCII 码,高位字节存储字符的属性。
对应行
- 偏移 000~09F 对应显示器上的第一行(80 个字符占 160 字节)
- 偏移 0A0~13F 对应显示器上的第二行
- 偏移 140~1DF 对应显示器上的第三行
对应列
- 00~01 单元对应显示器上的第一列
- 02~03 单元对应显示器上的第二列
- 04~05 单元对应显示器上的第三列
属性字节的格式
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
BL | R | G | B | I | R | G | B |
闪烁 | 背景 | 背景 | 背景 | 高亮 | 前景 | 前景 | 前景 |
例:
- 红底闪烁绿字:11000010B
- 红底高亮绿字:01001010B
- 黑底白字:00000111B
类型 ptr
dword = 2 word = 4 byte
类型包括:
- byte:字节型
- word:字型
- dword:双字型
offset
- 由编译器处理
- 功能:取得标号的偏移地址
assume cs:codesg
codesg segment
start: mov ax,offset start ; 相当于 mov ax,0
s: mov ax,offset s ; 相当于 mov ax,3
codesg ends
end start
add 和 adc
- add 指令执行后会将进位的结果舍去
- adc 是带进位加法指令,它利用了 cf 位商记录的进位值
- 指令格式:adc 操作对象1,操作对象2
- 功能:操作对象 1 = 操作对象 1 + 操作对象 2 + CF
例:
mov ax,2; mov bx,1; sub bx,ax; adc ax,1
,执行后 (ax) = 4,相当于计算 (ax) + 1 + CF = 2 + 1 + 1 = 4
sub 和 sbb
- sub 减法指令,借位将会被舍去
- sbb 是带借位减法指令,它利用了 CF 位商记录的借位值
- 指令格式:sbb 操作对象 1,操作对象 2
- 功能:操作对象 1 = 操作对象 1 - 操作对象 2 - CF
cmp
cmp 是比较指令,cmp 的功能相当于减法指令,只是不保存结果。cmp 指令执行后将对标志寄存器产生影响
- 指令格式:cmp 操作对象 1 ,操作对象 2
- 功能:计算操作对象 1 - 操作对象 2 单并不保存结果,仅仅根据计算结果对标志寄存器进行设置
例:
cmp ax,ax
指令执行后相当于 (ax) - (ax)
的运算,结果为 0,单并不保存在 ax 中。指令执行后:zf = 1, pf = 1, sf = 0, cf = 0, of = 0
cmp ax,bx:
- zf = 1,说明 (ax) = (bx)
- zf = 0,说明 (ax) != (bx)
- cf = 1,说明 (ax) < (bx)
- cf = 0,说明 (ax) >= (bx)
- cf = 1 并且 zf = 0,说明 (ax) > (bx)
- cf = 1 或 zf = 1,说明 (ax) <= (bx)
- sf = 1 且 of = 0 => (ax) < (bx)
- sf = 1 且 of = 1 => (ax) > (bx)
- sf = 0 且 of = 1 => (ax) < (bx)
- sf = 0 且 of = 0 => (ax) >= (bx)
- 有符号数进行 cmp ax,bx 运算时,可以通过 sf = 1 判断 ax < bx,sf = 0 判断 ax > bx
- 无符号数不可只通过 sf 进行判断大小关系
jmp
- 指令格式:jmp 类型 标号(跳转到标号位置)
- 类型包括:
- short:依据位移进行跳转,实现段内短转移。修改IP,8位位移,范围 -128~127(指令中包含转移相差位移);地址在编译时给出
- near ptr:于short类似,为16位位移,范围 -32786~32767;地址在编译时给出
- far ptr:依据转移目的地址进行跳转,实现段间跳转。修改CS、IP
- jmp 16位reg:转移地址在reg中
- word ptr 内存单元地址(段内转移):从内存单元地址处开始存放着一个字,是转移的目的便宜地址
- 执行后 ip = (内存单元地址)
- dword ptr 内存单元地址(段间转移):从内存单元地址处开始存放两个字,高地址是转移的目标段,低地址是转移的目的偏移地址
- 执行后 cs = (内存单元地址 + 2)
- 执行后 ip = (内存单元地址)
- 类型包括:
jcxz
有条件跳转指令,所有有条件跳转均为短指令。在机器码中包含转移的位移 ip 范围:-128~127
- 指令格式:jcxz 标号(如果(cx) = 0,转移到标号处执行)
- 当 (cx) != 0,什么也不做,程序向下执行
- 当 (cx) == 0,跳转到标号处执行
loop
loop 指令我i欸循环指令,所有循环指令都是短转移,在对应的机器码中包含转移的位移。对 ip 修改范围 -128~127
- (cx) = (cx) - 1
- if ((cx) != 0) jmp short 标号
ret 和 retf
- ret 用栈中的数据,修改 IP 的内容,从而实现近转移
- 相当于: pop ip
- retf 用栈中的数据,修改 CS 和 IP 的内容,实现远转移
- 相当于:pop ip, pop cs
call
指令格式:call [转移类型] 标号(将当前的 IP 压入栈后,转移到标号处指令)/ 16 位寄存器
- 将当前的 IP 或 CS 和 IP 压入栈中
- 转移
转移类型包括:
- far ptr:段间跳转;相当于:
push cs; push ip; jmp far ptr 内存单元地址 / reg
- word ptr:相当于进行:
push ip; jmp word ptr 内存单元地址 / reg
- dword ptr:相当于进行:
push cs; push ip; jmp dword ptr 内存单元地址
mul 和 div
mul
两数的相乘要么都是 8 位要么都是 16 位
- 8 位:一个默认存放在 AL 中,另一个存放在 8 位 reg 或内存字节单元中
- 16 位:一个默认存放在 AX 中,另一个存放在 reg 或内存字单元中
结果:
- 8 位:结果默认存放在 AX 中
- 16 位:结果高位默认存放在 DX 中,低位存放在 AX 中
格式: mul reg / mul (byte ptr/word ptr) 内存单元
div
- 被除数16(除数8位)、结果 AL 存放商,AH 存放余数
- 被除数32(除数16位)、结果 AX 存放商,DX 存放余数
shl 和 shr 指令
shl:左移指令,相当于 X = X * 2
- 将一个寄存器或内存单元中的数据向左移位
- 将最后移出的一位写入 CF 中
- 最低位用 0 补充
- 如果移动位数大于 1 时,必须将移动位数放在 cl 中
例:mov al,01001000b; shl al,1
执行后 (al)=10010000b,cf=0
例:mov al,01001000b; mov cl,3; shl al,cl
执行后 (al)=10010000b,cf=0
shr 逻辑右移指令,它与 shl 所进行的操作刚好相反;相当于 X = X / 2
- 将一个寄存器或内存单元中的数据向右移位
- 将最后移出的一位写入 CF 中
- 最高位用 0 补充
- 如果移动位数大于 1 时,必须将移动位数放在 cl 中
push 和 pop、pushf 和 popf
在函数调用时,即使用 cal 命令时,将会使 IP 寄存器的值压入堆栈;在使用 ret 命令后,将会使压入堆栈的地址传送给 IP
- push 入栈指令,以字为单位
- push ax 将会使 ax 存放到 ss:[sp] 位置处
- 入栈后 sp = sp - 2(sp 一直指向栈顶元素的位置)
- pop 出栈指令,以字为单位
- pop ax 将会使 ss:[sp] 位置处的值存放到 ax 寄存器中
- 出栈后 sp = sp + 2
- pushf 将标志寄存器的值压入栈
- popf 从栈中弹出数据,送入标志寄存器中
模块化设计
参数传递问题
单参问题
- 参数可以放入 bx 中
- 结果可以放入 dx 和 ax 中
批量参数
- 可将字符串的长度放入 cx 中
寄存器冲突
在子程序中使用了父程序所使用的寄存器,将导致寄存器中的内容重写,为了解决此冲突,在子程序中采用栈的形式保存之程序被调用时时所使用的寄存器,当子程序执行完成,则进行弹栈操作
标志寄存器
作用:
- 用来存储相关指令的某些执行结果
- 用来为 CPU 执行相关指令提供行为依据
- 用来控制 CPU 的相关工作方式
这种寄存器被称为 标志寄存器 ,其中存储信息通常被称为 程序状态字(PSW) 。简称 flag
8086CPU 中 flag 寄存器结构,(1、3、5、12、13、14、15 位在 8086CPU 中没有使用,不具有任何含义,而其他几位都具有特殊含义)
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF | DF | IF | TF | SF | ZF | AF | PF | CF |
在 Debug 中的表示
标志 | of | sf | zf | pf | cf | df |
---|---|---|---|---|---|---|
值为 1 的标记 | OV | NG | ZR | PE | CY | DN |
值为 0 的标记 | NV | PL | NZ | PO | NC | UP |
zf 标志
flag 的第六位 zf 为零标志。记录相关指令执行后,其结果是否为 0 .如果结果为 0 ,那么 zf 为 1;否则 zf 为 0(经实践,当计算结果溢出之后为 0 时,不能使 zf 为 0)
例:
mov ax,1; sub ax,1
,执行后,结果为 0 ,则 zf 为 1;
mov ax,2; sub ax,1
,执行后,结果为 1 ,则 zf 为 0
- 影响 zf 的指令:
add、sub、mul、div、inc、or、and
- 不影响 zf 的指令:
mov、push、pop
pf 标志
flag 的第 2 为是 pf,奇偶标志位。它记录相关指令执行后,其结果的所有 bit 位中 1 的个数是否位偶数,如果 1 的位数为偶数,则 pf = 1,否则 pf = 0(0 也为偶数)
例:
mov al,1; add al,10
,执行后结果为 00001011B,其中有 3 个 1 为奇数,则 pf = 0;
mov al,1; add al,2
,执行后结果为 00000011B,其中有 2 个 1 为偶数,则 pf = 1
sf 标志
flag 的第 7 位是 sf,符号标志位,它记录相关指令执行后,其结果是否为负,如果为负,sf = 1,否则 sf = 0;
cf 标志
flag 的第 0 位是 cf 进位标志位。在 无符号数 运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值
of 标志
flag 的第 11 位是 of 溢出标志位,记录了 有符号数 运算的结果是否发生了溢出。如果发生溢出,则 of = 1,否则 of = 0
例:
mov al,98; add al, 99
,指令执行后,cf = 0,of = 1
if 标志
当 CPU 检测到可屏蔽中断信息时,如果 IF = 1,则 CPU 在执行完当前指令后响应中断,引发中断过程;如果 IF = 0,则不响应可屏蔽中断
df 标志和串传送指令
flag 的第 10 位是 df 方向标志位。在串处理指令中,控制每次操作后 si、di 的增减
- df = 0 每次操作后 si、di 递增
- df = 1 每次操作后 si、di 递减
格式:movsb,传送字节
功能:执行 movsb 指令相当于进行下面的操作:
- ((es) * 16 + (di)) = ((ds) * 16 + (si))
- 如果 df = 0 则:(si) = (si) + 1; (di) = (di) + 1
- 如果 df = 1 则:(si) = (si) - 1; (di) = (di) - 1
格式:movsw,传送字
功能:执行 movsb 指令相当于进行下面的操作:
- ((es) * 16 + (di)) = ((ds) * 16 + (si))
- 如果 df = 0 则:(si) = (si) + 2; (di) = (di) + 2
- 如果 df = 1 则:(si) = (si) - 2; (di) = (di) - 2
rep movsb,循环传送字节
对应汇编格式:
s:movsb; loop s
rep movsw,循环传送字(cx 为其传送次数)
对应汇编格式:
s:movsw; loop s
8086 CPU 提供以下两条指令对 df 位进行设置
- cld 指令:将标志寄存器的 df 位置设置 0
- std 指令:将标志寄存器的 df 位置设置 1
检测比较结果的条件转移指令
根据 cmp 指令的比较结果进行转移的指令分为:
- 根据 无符号数 的比较结果进行转移的条件转移指令,检测 zf 和 cf 的值
- 根据 有符号数 的比较结果进行转移的条件转移指令,检测 sf、of 和 zf 的值
常用根据无符号数的比较结果进行转移的条件跳转指令
指令 | 含义 | 检测的相关标志位 |
---|---|---|
je | 等于则转移 | zf = 1 |
jne | 不等于则转移 | zf = 0 |
jb | 低于则转移 | cf = 1 |
jnb | 不低于则转移 | cf = 0 |
ja | 高于则转移 | cf = 0 且 zf = 0 |
jna | 不高于则转移 | cf = 1 且 zf = 1 |
e:equla、ne:not equal、b:below、nb:not below、a:above、na:not above
内中断
当 CPU 内有如下情况发生,将产生中断信息
- 除法错误,比如执行 div 指令产生的除法溢出;中断类型码:0
- 单步执行;中断类型码:1
- 执行 into 指令;中断类型码:4
- 执行 int 指令;该指令的格式:int n,指令中的 n 为字节型立即数,是提供给 CPU 的中断类型码
内存地址:0000:0000 ~ 0000:03FF 的 1024 个单元中存放着中断向量表;中断向量表中的一个表项栈两个字的大小,高位地址存放段地址,低地址存放偏移地址
中断过程
- (从中断信息中)取得中断类型码
- 标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中) ==> pushf
- 设置标志寄存器的第 8 位 TF 和第 9 位 IF 的值为 0 ==> TF = 0, IF = 0(当 TF = 1 时则又会去执行中断程序,将会产生嵌套)
- CS 的内容入栈 ==> push CS
- IP 的内容入栈 ==> push IP
- 从内存地址为中断类型码 * 4 和中断类型码 * 4 + 2 的两个字单元中读取中断处理程序的入口地址设置 IP 和 CS ==> (IP) = (N * 4 + 2)
iret 指令
指令功能用汇编语法描述为:
pop IP; pop CS; popf
算数运算被编译
计算某段代码的长度可以使用算数表达式进行,编译器将会计算其算数表达式的值,并编译为汇编语句。例如计算 s 段代码的长度
offset send-offset s
代码:
s: mov ax,10
mov ds,ax
; ……
send:nop
指令 mov ax,8-4
将会被编译器处理为指令:mov ax,4
指令 mov ax,(5+3)*5/10
将会被编译器处理为指令:mov ax,4
单步中断
CPU 执行完一条指令后,检测到标志寄存器的 TF 位为 1,则产生单步中断,引发中断过程,单步中断的中断类型码为 1。所引发的中断过程:
- 取得中断类型码 1
- 标志寄存器入栈,TF、IF 设置为 0
- CS、IP 入栈
- (IP) = (14), (CS) = (14+2)
在中断程序的入口处,应首先设置 TF = 0,以避免一直中断下去
响应中断的特殊情况
当执行 mov ss,ax; mov sp,0
类似命令时,两条指令会连续执行,中间不会产生中断,这是因为在中断时,将会进行 CS:IP 的入栈操作,导致,ss:ip 指向可能会发生异常。
在编写汇编代码时,应保持连续编写设置 ss 和 sp 的操作 mov ss,ax; mov sp,0
外中断
几乎所有由外设引发的外中断,都是可屏蔽中断
可屏蔽中断
- F = 1 则 CPU 在执行完当前指令后响应中断
- F = 0 则 CPU 不可响应可屏蔽中断
如果在中断处理程序中需要处理可屏蔽中断,则可以用指令将 IF 置 1;8086 CPU 提供的设置 IF 的指令:
- sti,设置 IF = 1
- cli,设置 IF = 0
不可屏蔽中断
当 CPU 检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程
对于 8086 CPU,不可屏蔽中断的中断类型码固定为 2,所以中断过程中,不需要取中断类型码。不可屏蔽中断过程:
- 标志寄存器入栈,IF = 0,TF = 0
- CS、IP 入栈
- (IP) = (8), (CS) = (0AH)
PC 机键盘的处理过程
键盘输入
- 键盘按下产生的扫描码成为 通码 。扫描码将被送到主板上相关接口芯片的寄存器中,该寄存器的端口地址为 60h
- 键盘松开产生的扫描码成为 断码 。扫描码将被送到主板上相关接口芯片的寄存器中,该寄存器的端口地址为 60h
- 通码的第 7 位为 0
- 断码的第 7 位为 1
断码 = 通码 + 80h
例:g 键的通码为 22h,断码为 a2h
键盘的输入到达 60h 端口时,相关的芯片就会向 CPU 发出中断类型码为 9 的可屏蔽中断信息,CPU 检测到该中断信息后,如果 IF = 1,则响应中断,引发终端过程,转去执行 int 9 中断例程
0040:17 单元存储键盘状态字节,该字节记录了控制键和切换键的状态。
值 | 记录状态 | 置1的状态 |
---|---|---|
0 | 右 SHIFT | 按下右 SHIFT |
1 | 左 SHIFT | 按下左 SHIFT |
2 | CTRL | 按下 CTRL |
3 | ALT | 按下 ALT |
4 | ScrollLock | 按下 ScrollLock |
5 | NumLock | 按下 NumLock |
6 | CapsLock | 按下 CapLock |
7 | Insert | 按下 Insert |
键盘输入处理过程:
- 键盘产生扫描码
- 扫描码送入 60h 端口
- 引发 9 号中断
- CPU 执行 int 9 中断例程处理键盘输入
端口操作
- CPU 最多可以定位 64KB 个不同的端口,则端口地址的范围为 0 ~ 65535
- 对端口的读写操作只有两条指令:in、out
例: in al,60h
从 60h 号端口读入一个字节
in 和 out 指令中,只能通过 ax 或 al 来存放从端口中读入的数据或要发送到端口的数据, 访问 8 位端口时用 al,访问 16 位端口时用 ax
对 0~255 以内的端口进行读写时:
- in al,20h ; 从 20h 端口读入一个字节
- out 20h,al ; 往 20h 端口写入一个字节
对 256~65535 的端口进行读写时,端口号放在 dx 中:
- mov dx,3f8h ; 将端口号 3f8h 送入 dx
- in al,dx ; 从 3f8h 端口读入一个字节
- out dx,al ; 向 3f8h 端口写入一个字节
附录
输出到屏幕中
assume cs:code,ds:data
stack segment
dw 16 dup (0)
stack ends
code segment
; 名称:show_str
; 功能:在指定的位置,用指定的颜色,显示一个用 0 结束的字符串
; 参数:(dh)=行号(取值范围 0~24),(dl)=列号(取值范围 0~79),(cl)=颜色,ds:si 指向字符串的首地址
; 返回:无
show_str: ; 保存数据
push es
push bx
push di
push cx
push dx
mov ax,0B800H
mov es,ax
; 计算行
mov al,160
mov ah,0
mul dh
mov bx,ax ; bx 存储行
; 计算列
mov al,2
mov ah,0
mul dl
mov di,ax ; di 存储列
mov dl,cl ; dl 存储颜色
; 输出到屏幕
lop: mov ch,0
mov cl,ds:[si]
jcxz ok
mov byte ptr es:[bx+di],cl
inc si
inc di
mov byte ptr es:[bx+di],dl
inc di
jmp short lop
ok: pop dx
pop cx
pop di
pop bx
pop es
ret
start: mov dh,8
mov dl,3
mov cl,2
mov ax,data
mov ds,ax
mov si,0
; 设置栈
mov ax,stack
mov ss,ax
mov sp,20H
call show_str
mov ax,4c00h
int 21h
code ends
end start
除法溢出的改进
assume cs:code,ds:data
data segment
db 'Welcome to masm!',0
data ends
stack segment
dw 16 dup (0)
stack ends
code segment
; 名称:divdw
; 功能:进行不会溢出的除法运算,被除数为 dword 型,除数为 word 型,结果为 dword 型
; 参数:
; - (ax)=dword 型数据的低 16 位
; - (dx)=dword 型数据的高 16 位
; - (cx)=除数
; 返回
; - (dx)=结果的高 16 位,(ax)=结果的低 16 位
; - (cx)=余数
divdw: push si
push di
mov si,ax ; 存放数据低位
mov ax,dx
mov dx,0
div cx ; 此时 ax 中保存商,dx 中保存余数
mov di,dx ; 存放余数
mov dx,ax
mov ax,0
; 将运算结果存入栈中,高位在下面,低位在上面
push dx
push ax
; mov ax,rem(di,cx)
mov dx,di
mov ax,0
; mul dx ; ax 中存方数据底位,dx 中存放数据高位
add ax,si
div cx ; ax 存放商,dx 中存放余数
mov cx,dx
pop si
pop di
add ax,si
add dx,di
pop di
pop si
ret
start: ; 设置栈
mov ax,stack
mov ss,ax
mov sp,20H
mov ax,4240H
mov dx,000FH
mov cx,0AH
call divdw
mov ax,4c00h
int 21h
code ends
end start
数值变为字符
assume cs:code,ds:data,ss:stack
data segment
db 10 dup (0)
data ends
stack segment
dw 16 dup (0)
stack ends
code segment
; 名称:dtoc
; 功能:将 word 型数据转变为十进制数的字符串,字符串以 0 结尾符
; 参数:(ax)=word型数据,dl:si=指向字符串的首地址
; 返回:无
dtoc: push si
push ax
push bx
push cx
push dx
; 保存数据
mov bx,10
mov di,0
divid: div bx
mov cx,dx ; cx 记录是否跳转
mov dx,0 ; 将 dx 设置为 0
jcxz hand
push cx ; 将余数 push 到栈中
inc di ; 记录入栈次数
jmp divid
hand: mov cx,di
write: pop ax
add ax,30H
mov byte ptr ds:[si],al
add si,1
loop write
pop dx
pop cx
pop bx
pop ax
pop si
ret
start: mov ax,stack
mov ss,ax
mov sp,20h
mov ax,12666
mov bx,data
mov ds,bx
mov si,0
call dtoc
mov ax,4c00h
int 21h
code ends
end start
将字符串中包含 a~z 的字符转化为大写
assume cs:code,ds:data
data segment
db "Beginner's All-purpose Symbolic Instruction Code.",0
data ends
code segment
; 名称 letterc
; 功能:将以 0 借位的字符串中的小写字母转变称大写字母
; 参数:ds:si 指向字符串首地址
letterc: mov ch,0
mov cl,ds:[si]
jcxz next ; 当 cx 为 0 时,即 ds:[si] 处的值为 9,跳转到next
mov bx,61H ; bx 记录小写字母最小
mov di,7AH ; di 记录小写字母最大
cmp cl,bl
inc si
jb letterc
cmp cx,di
ja letterc
dec si
and cl,11011111B
mov ds:[si],cl
inc si
jmp letterc
next: ret
begin: mov ax,data
mov ds,ax
mov si,0
call letterc
mov ax,4c00h
int 21h
code ends
end begin
重写 0 号中断的处理程序
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset d0
mov ax,0
mov es,ax
mov di,200h
mov cx,offset d0end-offset d0
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
d0: jmp short d0start
db 'divide error!'
d0start: mov ax,cs
; 设置字符串位置
mov ds,ax
mov si,202h
; 设置显示区域
mov ax,0B800H
mov es,ax
mov di,160*12+2*33
; 设置循环次数
mov cx,13
s: mov al,ds:[si]
mov es:[di],al
add di,2
inc si
loop s
mov ax,4c00h
int 21h
d0end: nop
code ends
end start
自制中断程序,显示字符
assume cs:code,ds:data
data segment
db 'Welcome to masm!',0
data ends
code segment
install: push ax
push cx
push es
push di
push ds
push si
mov ax,cs
mov ds,ax
mov si,offset display
mov ax,0
mov es,ax
mov di,200h
mov cx,offset displaye-offset display
cld
rep movsb
; 设置中断向量表
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
pop si
pop ds
pop di
pop es
pop cx
pop ax
ret
start: mov dh,10
mov dl,10
mov cl,2
mov ax,data
mov ds,ax
mov si,0
; 安装程序
call install
int 7ch
mov ax,4c00h
int 21h
; 功能显示一个用 0 结束的字符串
; 参数 (dh) = 行号,(dl) = 列号,(cl) = 颜色,ds:si 指向字符串首地址
; 返回:无
display: push es
push ax
push cx
push dx
push bp
push si
mov ch,0
mov ax,0B800H
mov es,ax
mov al,160
mul dh
mov di,ax ; di 中存储了行字节数
mov al,2
mul dl
mov bp,ax ; bp 中存储了列字节数
mov bl,cl ; bl 中存储颜色
s: mov ch,0
mov cl,ds:[si]
jcxz se
mov al,ds:[si]
mov byte ptr es:[bp+di],al
inc bp
mov byte ptr es:[bp+di],bl
inc bp
inc si
loop s
se: pop si
pop bp
pop dx
pop cx
pop ax
pop es
mov ax,4c00h
int 21h
displaye: nop
code ends
end start
自定义中断实现 loop 指令
assume cs:code,ds:data
data segment
db 'Welcome to masm!',0
data ends
code segment
install: push ax
push cx
push es
push di
push ds
push si
mov ax,cs
mov ds,ax
mov si,offset lop
mov ax,0
mov es,ax
mov di,200h
mov cx,offset lope-offset lop
cld
rep movsb
; 设置中断向量表
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
pop si
pop ds
pop di
pop es
pop cx
pop ax
ret
start: mov ax,0b800h
mov es,ax
mov di,160*12
mov bx,offset se-offset s
mov cx,80
; 安装程序
call install
s: mov byte ptr es:[di],'!'
add di,2
int 7ch
se: nop
mov ax,4c00h
int 21h
; 功能:完成 loop 指令的功能
; 参数 (cx) = 循环次数,(bx) = 位移
; 返回:无
lop: dec cx
jcxz ok
pop si ; 保存偏移地址
sub si,bx
push si
iret
ok: iret
lope: nop
code ends
end start