汇编实验4 8086标志寄存器及中断
实验任务1
源代码
功能:对128位的两个数字进行求和运算
点击查看代码
assume cs:code, ds:data
data segment
x dw 1020h, 2240h, 9522h, 5060h, 3359h, 6652h, 2530h, 7031h
y dw 3210h, 5510h, 6066h, 5121h, 8801h, 6210h, 7119h, 3912h
data ends
code segment
start:
mov ax, data
mov ds, ax
mov si, offset x ; x -> si
mov di, offset y ; y -> di
call add128
mov ah, 4ch
int 21h
add128:
push ax ; 依次保存寄存器的值
push cx
push si
push di
sub ax, ax ; 主要是把CF(进位)标志位置NC(0),否则第一次adc会出问题
; 由于8086是小端模式,根据逻辑来说,高高低低
; 左边是低地址,应该是低位
; 从左加到右, 正好是从低位加到高位
mov cx, 8 ; 8组数字
s: mov ax, [si] ; x中的数
adc ax, [di] ; 带进位的加法,加y中的数
mov [si], ax ; 结果送回x
inc si ; 如果换成add, 会影响CF的值,
inc si ; 本来应该有进位的, 如果用了add会导致CF = 0,
inc di ; 进位就失效了,
inc di ; 所以这里si和di只能用inc而不能用add.
loop s
pop di
pop si
pop cx
pop ax
ret
code ends
end start
实验问题解答
① line34
~line37
的4条inc指令,能否替换成如下代码?
add si,2
add di,2
答案:
这段代码data
段给的数据可以,但是如果换成其它数据就不一定可以。
原因:
这题中给的128位数据,每个16位相加都恰好都没有产生进位,所以使用add
即便修改了进位寄存器CF
的值,也没有影响。在这题中是可以替换的。
但如果换成其他数据,若相加过程中产生了进位,则使用add
会导致进位寄存器CF
的值发生变化。
如果本来应当是有进位的,CF
的值为CY(1)
,但是做了add
操作后CF
会变成NC(0)
,会对后面的位数加法产生影响。所以不能使用add
。
而inc
指令并不影标志寄存器CF
的值。
事实上,根据 intel 白皮书中对
inc
的描述,可以很清楚的知道这一点(Intel® 64 and IA-32 Architectures Software Developer’s Manual,Vol. 2A3-493):(翻译:给目标操作数加1,同时保留
CF
标志的状态。目标操作数可以是一个寄存器或一个内存位置。这条指令允许在不影响CF
标志的情况下更新一个循环计数器。(使用即时操作数为1的ADD
指令来执行更新CF
标志的增量操作)。)
② 在debug中调试,观察数据段中做128位加之前,和,加之后,数据段的值的变化。
可以观察到数据被正确求和了。

观察
① add指令对标志寄存器中的零标志位ZF(Zero Flag)、进位标志位CF(Carry Flag)是否有影响?
答案:有影响
根据 Intel 白皮书(Vol. 2A 3-31)对
ADD
指令的描述:(翻译:ADD指令执行整数加法。它对有符号和无符号整数操作的结果进行评估,并设置OF和CF标志,分别表示有符号或无符号结果中的进位(溢出)。SF标志表示有符号结果的符号。)
② inc指令对标志寄存器中的零标志位ZF(Zero Flag)、进位标志位CF(Carry Flag)是否有影响?
答案:对
ZF
有影响,而对CF
无影响根据 intel 白皮书中对
inc
的描述,可以很清楚的知道这一点(Intel® 64 and IA-32 Architectures Software Developer’s Manual,Vol. 2A3-493):
实验任务2
源代码
点击查看代码
assume cs:code, ds:data
data segment
str db 80 dup(?)
data ends
code segment
start:
mov ax, data
mov ds, ax
mov si, 0
s1:
mov ah, 1 ; 调用int 21h的1号子程序
int 21h ; 从键盘接收输入
mov [si], al ; 把输入的字符放入 ds:[si]
cmp al, '#' ; 判断接收到的字符是否是#
je next ; 如果是'#',执行next处操作
inc si ; 否则si + 1
jmp s1 ; 返回s1继续读入下一个字符
next:
mov ah, 2 ; 调用
mov dl, 0ah ; 0ah是换行符回车的ASCII码
int 21h ; 打印一个换行符
mov cx, si ; 由于si从0开始,si = 读入的字符个数-1,正好不会把'#'打印出来
mov si, 0 ; si回到字符串开始的位置
s2: mov ah, 2 ; 调用2号子程序
mov dl, [si] ; 打印字符
int 21h
inc si
loop s2 ; 打印字符串循环
mov ah, 4ch
int 21h
code ends
end start
实验结果

实验分析
该程序的作用是:从键盘接收输入一串以#
为结尾的字符串,然后将其打印在屏幕上
① 汇编指令代码line11-18,实现的功能是?
s1:
mov ah, 1 ; 调用int 21h的1号子程序
int 21h ; 从键盘接收输入
mov [si], al ; 把输入的字符放入 ds:[si]
cmp al, '#' ; 判断接收到的字符是否是#
je next ; 如果是'#',执行next处操作
inc si ; 否则si + 1
jmp s1 ; 返回s1继续读入下一个字符
总结:从键盘上读取输入的字符,并保存到ds:[si]
,每读入一个就判断是否为#
,如果是则不保存,转跳至标号next
处执行;如果不是则si + 1
并继续读入下一个字符。
② 汇编指令代码line20-22,实现的功能是?
mov ah, 2 ; 调用
mov dl, 0ah ; 0ah是换行符回车的ASCII码
int 21h ; 打印一个换行符
总结:打印一个回车(换行符)
③ 汇编指令代码line24-30,实现的功能是?
mov cx, si ; 由于si从0开始,si = 读入的字符个数-1,正好不会把'#'打印出来
mov si, 0 ; si回到字符串开始的位置
s2: mov ah, 2 ; 调用int 21h的2号子程序
mov dl, [si] ; 打印字符
int 21h
inc si
loop s2 ; 打印字符串循环
总结:打印字符串,并且不会把#
打出来
说明(实验指导里给的)
- DOS系统功能调用int 21h的1号子功能:
功能:从键盘上输入单个字符
入口参数:(ah) = 1
出口参数: (al)存放输入字符的ASCII码
即:
mov ah,1 int 21h ; (al) <-- 输入字符的ascII码
- DOS系统功能调用int 21h的2号子功能
功能:输出单个字符到屏幕上
入口参数:(ah) = 2, (dl) = 待输出的字符或其ascII码
出口参数:无
即:
mov ah, 2 mov dl, ×× ; ××是待输出的字符,或,其ascII码 int 21h
实验任务3
题目描述
注:该任务是对实验3的任务3进行改进。
针对8086CPU,已知逻辑段定义如下:
data segment
x dw 91, 792, 8536, 65521, 2021
len equ $ - x
data ends
编写8086汇编源程序task3.asm,在屏幕上以十进制形式输出data段中这一组连续的数据,数据和数据之间以空格间隔。
要求:
-
编写子程序printNumber
- 功能:以十进制形式输出一个任意位数的整数(整数范围0 ~ 65535)
- 入口参数:寄存器ax(待输出的数据 --> ax)
- 出口参数:无
-
编写子程序printSpace
- 功能:打印一个空格
- 入口参数:无
- 出口参数:无
-
在主体代码中,综合应用寻址方式和循环,调用printNumber和printSpace, 实现题目要求。
源代码
点击查看代码
; 可以打印0~65535不定位数的数字
assume ds:data, cs:code, ss:stack
data segment
x dw 91, 792, 8536, 65535, 2021, 0
len equ $ - x
data ends
stack segment
dw 8 dup(?)
stack ends
code segment
start:
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, 16
mov cx, len/2 ; 由于数据都是word型,所以len/2才是数据个数
; print循环: 依次打印所有数字
print:
mov ax, word ptr ds:[di] ; 把数据放入al
add di, 2 ; di指针后移2字节
push cx ; 把cx保存起来, 子程序中会修改cx值
call printNumber ; 打印数字
call printSpace ; 打印空格
pop cx ; 恢复cx
loop print
mov ah, 4ch
int 21h
; 子程序: printNumber
; 功能: 打印数字
; 入口参数:
; 寄存器ax (待输出的数据 --> ax)
; 局部变量说明:
; bx -> 存储数字字符个数
printNumber:
mov bx, 0 ; 获取之前位数为0
; 逐位获取数字
; getEach循环: 获取每一位,然后压入栈中
getEach:
; 除数放在16位寄存器bp中
mov bp, 10 ; 除10运算
mov dx, 0 ; 由于除数是16位寄存器,dx也是被除数一部分,需要置零
div bp ; 数据除10
push dx ; 将数字压入栈中(余数在dx里)
inc bx ; 位数+1
mov cx, ax ; 除法商赋给cx, 如果商为0则说明所有位数都获取完了
inc cx ; 由于loop时会-1,这里先+1,防止出现负数
loop getEach
; 打印数字
mov cx, bx ; 先把bx存的数字位数赋给cx
; printEach循环: 依次从栈中取出数字,逐位打印
printEach:
pop dx ; 取出一位数
add dl, 30h ; dl是刚才除法的余数,也就是需要得到的位数,+30h是转成对应字符
mov ah, 2 ; 调用int 21h的2号子程序打印
int 21h
loop printEach
ret
; 子程序: printSpace
; 功能: 打印空格
printSpace:
mov ah, 2
mov dl, 20h
int 21h
ret
code ends
end start
实验结果
该程序可以成功打印0
~65535
之间的任意数字。

实验说明
说明全部写在注释中。
实验任务4
题目描述
针对8086CPU,已知逻辑段定义如下:
data segment
str db "assembly language, it's not difficult but tedious"
len equ $ - str
data ends
编写8086汇编源程序task4.asm,将data段中字符串里的小写字符转换成大写。
要求:
- 编写子程序
strupr
- 功能:将包含任意字符的字符串中的小写字母变成大写
- 入口参数
- (
ds:si
) 字符串首地址的段地址和偏移地址分别送至ds和si - (
cx
) 字符串的长度
- (
- 出口参数:无
- 在主体代码中,设置入口参数,调用strupr, 实现题目要求。
源代码
点击查看代码
assume cs:code,ds:data
data segment
str db "assembly language, it's not difficult but tedious"
len equ $ - str
data ends
stack segment
db 8 dup(?)
stack ends
code segment
start:
mov ax, data
mov ds, ax
mov si, 0
mov cx, len ; 参数:字符串长度存在cx中
call strupr ; 调用转换子程序
call printStr ; 调用打印子程序
mov ax, 4c00h
int 21h
; 子程序 strupr
; 功能: 将包含任意字符的字符串中的小写字母变成大写
; 入口参数
; (ds:si) 字符串首地址的段地址和偏移地址分别送至ds和si
; (cx) 字符串的长度
strupr:
push cx
push si ; 先保存两个寄存器的值
transform:
mov al, ds:[si] ; 取出一个字符
cmp al, 97 ; 判断ASCII码是否 >= 97
jl continue ; 小于97直接进入下次循环
cmp al, 122 ; 判断ASCII码是否 <= 122
jg continue ; 大于122直接进入下次循环
and al, 0dfh ; 小写转成大写,原理详见实验2
mov ds:[si], al ; 把转换后的字符送回data段
continue:
inc si ; 指针后移
loop transform ; 循环
pop si
pop cx ; 恢复寄存器的值
ret ; 返回
; 子程序 printStr
; 功能: 打印字符串
; 入口参数
; (ds:si) 字符串首地址的段地址和偏移地址分别送至ds和si
; (cx) 字符串的长度
printStr:
push cx ; 保存寄存器的值
push si
print: ; 打印字符循环
mov ah, 2
mov dl, ds:[si]
int 21h
inc si
loop print
pop si ; 恢复寄存器的值
pop cx
ret ; 返回
code ends
end start
实验结果
可以看到,该程序成功将小写字母转成了大写。

实验任务5
源代码
点击查看代码
assume cs:code, ds:data
data segment
str1 db "yes", '$'
str2 db "no", '$'
data ends
code segment
start:
mov ax, data
mov ds, ax
mov ah, 1 ; 调用int 21h的1号子程序进行单字符输入
int 21h
mov ah, 2 ; 调用BIOS中断例程int 10h的2号子功能
mov bh, 0 ; 设置显示页为第0页
mov dh, 24 ; 设置光标位置在第24行
mov dl, 70 ; 设置光标位置在第70列
int 10h ; 设置光标位置
cmp al, '7' ; 把输入的字符和字符'7'进行比较
je s1 ; 如果输入的字符是'7',就跳转s1处
mov ah, 9 ; 调用int 21h的9号子程序显示str2
mov dx, offset str2 ; 把标号str2的偏移量放入dx
int 21h ; 显示标号str2处的字符串(no)
jmp over ; 无条件跳转到over处
s1: mov ah, 9 ; 调用int 21h的9号子程序显示str1
mov dx, offset str1
int 21h ; 显示标号str1处的字符串(yes)
over:
mov ah, 4ch ; 程序结束
int 21h
code ends
end start
运行结果
输入7
后,屏幕上倒数第2行右下角显示yes

输入其它字符,屏幕上倒数第2行右下角显示no

总结和理解
该程序的解释已经全部写在源代码的注释中。
总结一下,这个程序实现的功能是:
从键盘输入一个字符,判断输入的字符是否为
7
如果为
7
,则从屏幕的第24行第70列开始显示yes
如果不为
7
,则从屏幕的第24行第70列开始显示no
实验任务6
运行42号中断程序
通过编译连接运行task6_1.asm
和task6_2.asm
后可以看到,屏幕底部出现了绿色的"welcome to 2049!
",说明42号中断程序被成功调用。

自定义中断:36号中断程序
源代码
点击查看代码
assume cs:code
code segment
start:
; 36号中断
mov ax, cs
mov ds, ax
mov si, offset int36 ; set ds:si
mov ax, 0
mov es, ax
mov di, 200h ; set es:di
; 复制中断程序到 es:[di]
mov cx, offset int36_end - offset int36
cld
rep movsb
; 设置中断向量表
mov ax, 0
mov es, ax
mov word ptr es:[36*4], 200h
mov word ptr es:[36*4+2], 0
; 调用36号中断程序
int 36
; 程序结束
mov ah, 4ch
int 21h
; 36号中断程序
int36:
jmp short int36_start
stu_num db "201983290048 Li Qingyun"
len1 equ $ - stu_num
task db "Assembly Experiment 4 Interrupt No.36"
len2 equ $ - task
; 中断程序指令部分
int36_start:
mov ax, cs
mov ds, ax
mov di, 202h
call printString
iret
; 打印子程序:
; 参数说明:
; 学号字符串存储在 -> ds:[di]
printString:
mov ax, 0b800h
mov es, ax ; 控制显存区域段指针
mov si, 0 ; 显存区域列指针从0开始
; 先把屏幕前11行置位蓝色(第一行会被吃掉)
mov al, 11 ; 全25行
mov dl, 80 ; 每行160个字节文字和颜色都需要修改
mul dl ; 25*160, 获得需要修改的字节数
mov cx, ax
printBlue1:
mov byte ptr es:[si], 20h ; 用一个空格填充文字位置
inc si ; 后移指针
mov byte ptr es:[si], 17h ; 填充颜色: 蓝底+白字:0 001 0 111 -> 17h
inc si ; 后移指针
loop printBlue1
; 把屏幕中间5行置为白底蓝字
mov cx, 5*80
printWhite:
mov byte ptr es:[si], 20h ; 用一个空格填充文字位置
inc si ; 后移指针
mov byte ptr es:[si], 71h ; 填充颜色: 白底+蓝字:0 111 0 001 -> 71h
inc si ; 后移指针
loop printWhite
; 把屏幕最后10行置为蓝色
mov cx, 10*80
printBlue2:
mov byte ptr es:[si], 20h ; 用一个空格填充文字位置
inc si ; 后移指针
mov byte ptr es:[si], 17h ; 填充颜色: 蓝底+白字:0 001 0 111 -> 17h
inc si ; 后移指针
loop printBlue2
; 打印学号
mov si, 12*160 ; 从第12行开始打印
mov dx, (80-len1)/2 ; 计算左右两边空格数
; 调用打印空格的子程序, 打印学号左侧的空格
mov cx, dx
call printSpace
; 打印学号字符串
mov cx, len1
printNumber:
mov al, ds:[di] ; 低位是字符
mov ah, 71h ; 高位是颜色
mov word ptr es:[si], ax ; 按字放入
inc di
add si, 2
loop printNumber
; 再次调用打印空格的子程序, 打印学号右侧的空格
mov cx, dx
call printSpace
; 打印下面一行文字
mov si, 14*160 ; 从第14行开始打印
mov dx, (80-len2)/2 ; 计算左右两边空格数
; 调用打印空格的子程序, 打印文字左侧的空格
mov cx, dx
call printSpace
; 打印文字字符串
mov cx, len2
printTask:
mov al, ds:[di] ; 低位是字符
mov ah, 71h ; 高位是颜色
mov word ptr es:[si], ax ; 按字放入
inc di
add si, 2
loop printTask
; 再次调用打印空格的子程序, 打印文字右侧的空格
mov cx, dx
call printSpace
ret
; 子程序: 打印分隔符空格
; 参数: 长度 -> cx
; 位置 -> es:[si]
printSpace:
mov al, 20h ; 一个空格
mov ah, 71h
mov word ptr es:[si], ax
add si, 2
loop printSpace
ret
int36_end:
nop
code ends
end start
运行结果
36号中断的运行效果是:
将屏幕背景变为蓝色,中间5行背景变为白色;
并居中打印"
201983290048 Li Qingyun
"和"Assembly Experiment 4 Interrupt No.36
",表明这是36号中断程序。
所有说明都写在代码注释中。

总结与思考
- 标志寄存器会被多数指令修改,如
add
、sub
、jmp
等,而一些指令执行时也会使用标志寄存器的值来确定执行的方式,这可能会对操作结果造成影响。比如实验一中,inc
不会对CF
造成影响,但add
却会,如果使用add
再使用adc
会对结果造成影响。 movsb
和movsw
指令会使用方向寄存器的值,确定指针移动方向。cmp
指令会对两个操作数做减法,然后将结果存在标志寄存器中,有条件跳转指令则会根据标志寄存器的值进行判断并跳转。- 使用
pushf
和popf
可以间接实现对标志寄存器的修改,但通常不需要这样操作。 - 内中断程序可以使CPU在执行到特殊情况时调用,中断处理程序可以让CPU从错误中恢复,比如除法中除0的错误。
- 中断向量表存储在
0000:0000
开始空间中,可以存一个字的中断程序地址。