1.实验任务1
任务1
task1.asm源码:
assume cs:code, ds:data
data segment
x db 1, 9, 3
len1 equ $ - x
y dw 1, 9, 3
len2 equ $ - y
data ends
code segment
start:
mov ax, data
mov ds, ax
mov si, offset x
mov cx, len1
mov ah, 2
s1:mov dl, [si]
or dl, 30h
int 21h
mov dl, ' '
int 21h
inc si
loop s1
mov ah, 2
mov dl, 0ah
int 21h
mov si, offset y
mov cx, len2/2
mov ah, 2
s2:mov dx, [si]
or dl, 30h
int 21h
mov dl, ' '
int 21h
add si, 2
loop s2
mov ah, 4ch
int 21h
code ends
end start
源程序运行后结果为:
回答问题
① line27, 汇编指令 loop s1 跳转时,是根据位移量跳转的。通过debug反汇编,查看其机器码,
分析其跳转的位移量是多少?(位移量数值以十进制数值回答)从CPU的角度,说明是如何计算得
到跳转后标号s1其后指令的偏移地址的。
1)反汇编查看机器码,可以看到其机器码为
E2F2
E2
表示LOOPF2
是补码形式的位移量,转换为二进制为11110010
将其转换为原码 为10001110
=−14
,所以其位移量为−14
。
2)CPU根据目标偏移地址减去当前偏移地址得到位移量,当前指令的ip为0019h
,即25
,25−14=11
还需加上上一条指令的长度,上一条指令的偏移地址为11,而上一条指令B402
的长度为2字节,所以计算出s1的偏移量为:11+2=13
② line44,汇编指令 loop s2 跳转时,是根据位移量跳转的。通过debug反汇编,查看其机器码,
分析其跳转的位移量是多少?(位移量数值以十进制数值回答)从CPU的角度,说明是如何计算得
到跳转后标号s2其后指令的偏移地址的。
我们先运行至第一个loop处再反汇编,和以上计算方法一样,ip指向
0037h
,为55
,补码F0转换为源码为00010000
即16
偏移地址为55-16=39
,而上一条指令B402
的长度为2字节,所以计算出s1的偏移量为:39+2=41
③ 附上上述分析时,在debug中进行调试观察的反汇编截图
2.实验任务2
taska.asm:
assume cs:code, ds:data
data segment
dw 200h,0h,230h,0h
data ends
stack segment
db 16 dup(0)
stack ends
code segment
start:
mov ax,data
mov ds,ax
mov word ptr ds:[0],offset s1
mov word ptr ds:[2],offset s2
mov ds:[4],cs
mov ax,stack
mov ss,ax
mov sp,16
call word ptr ds:[0]
;push ip
;jmp word ptr ds:[0]
s1: pop ax
;(ax) = stack.top() = offset line23 + size(line23) = offset s1
call dword ptr ds:[2]
;push cs
;push ip
;jmp dword ptr ds:[2]
s2: pop bx
;(bx) = stack.top() = offset line28 + size(line28) = offset s2
pop cx
;(bx) = stack.top() = cs = code
mov ah,4ch
int 21h
code ends
end start
问题1
call word ptr ds:[0] 短转移, 将下一条指令偏移地址(ip)压入栈, 并转移至 ds:[0]地址即 s1 处, 此后的 pop ax 将该内容出栈给ax;
call dword ptr ds:[2] 段间转移, 将下一条指令基址和偏移地址(cs 和 ip)压入栈, 并转移至 ds:[2] 起始的双字指向的地址即 s2 处, 此后的 pop bx 将ip出栈给ax, pop cx 将 cs 出栈给 cx.
即:(ax) = offset s1
(bx) = offset s2
(cx) = cs = code
问题2
进行代码调试
运行到第一次call之后,可见确实执行了s1后的代码,ax的值也确实是s1所标记地址的偏移量
继续向下执行
执行到第二次call,并运行两次pop之后,可见第二次call之后确实执行了s2之后的代码,并且bx的值确实是s2标记地址的偏移量,cx确实是当前代码段地址也就是cs寄存器中的内容
3.实验任务3
针对8086CPU,已知逻辑段定义如下:
data segment
x db 99, 72, 85, 63, 89, 97, 55
len equ $- x
data ends
编写8086汇编源程序task3.asm,在屏幕上以十进制形式输出data段中这一组连续的数据,数据和数据之间以空格间隔。
要求:
-
编写子程序printNumber
功能:以十进制形式输出一个两位数
入口参数:寄存器ax(待输出的数据 --> ax)
出口参数:无 -
编写子程序printSpace
功能:打印一个空格
入口参数:无
出口参数:无
在主体代码中,综合应用寻址方式和循环,调用printNumber和printSpace, 实现题目要求。
实现代码:
task3.asm:
assume cs:code, ds:data
data segment
x db 99, 72, 85, 63, 89, 97, 55
len equ $- x
data ends
code segment
main:
mov ax, data
mov ds, ax
mov cx, len
mov si, offset x
print:
mov al, [si]
mov ah, 0
call printNumber
call printSpace
inc si
loop print
mov ah, 4ch
int 21h
;功能:以十进制形式输出一个两位数
;入口参数:寄存器ax(待输出的数据 --> ax)
;出口参数:无
printNumber:
mov bl, 10
div bl
mov bx, ax
mov ah, 2
mov dl, bl ; 打印商(10位)
or dl, 30h
int 21h
mov dl, bh ; 打印余数(个位)
or dl, 30h
int 21h
ret
printSpace:
mov ah, 2
mov dl, ' '
int 21h
ret
code ends
end main
运行效果如下:
4.实验任务4
针对8086CPU,已知逻辑段定义如下:
data segment
str db 'try'
len equ $ - str
data ends
编写8086汇编源程序task4.asm,在屏幕上以指定颜色、指定行,在屏幕上输出字符串。
要求:
- 编写子程序printStr
- 功能:在指定行、以指定颜色,在屏幕上显示字符串
- 入口参数
- 字符串首字符地址 --> ds:si(其中,字符串所在段的段地址—> ds, 字符串起始地址的偏移地址—> si)
- 字符串长度 --> cx
- 字符串颜色 --> bl
- 指定行 --> bh (取值:0 ~24)
- 出口参数:无
- 在主体代码中,两次调用printStr,使得在屏幕最上方以黑底绿字显示字符串,在屏幕最下方以黑
底红色显示字符串
实现代码:
task4.asm:
assume cs:code
data segment
str db 'try'
len equ $ - str
data ends
code segment
main:
mov ax, 0b800h
mov es, ax
first_print:
mov ax, data
mov ds, ax
mov si, offset str
mov cx, len
mov bl, 00000010b ; 黑底绿字
mov bh, 0 ; 第0行
call printStr
second_print:
mov si, offset str
mov cx, len
mov bl, 00000100b ; 黑底红字
mov bh, 24 ; 第24行
call printStr
mov ah, 4ch
int 21h
; 入口参数:
; 字符串首字符地址 --> ds:si(其中,字符串所在段的段地址—> ds, 字符串起始地址的偏移地址—> si)
; 字符串长度 --> cx
; 字符串颜色 --> bl
; 指定行 --> bh (取值:0 ~24)
printStr:
push bp ; 因为要用到bp和di, 先保存现场
push di
mov ah, 0
mov al, 160
mul bh
mov bp, ax ; 计算行数偏移地址存储在bp
mov di, si ; di存储显存中每个字符偏移地址
printChar:
mov al, ds:[si]
mov es:[bp+di], al ; 字符
mov es:[bp+di+1], bl ; 颜色
inc si
inc di
inc di ; di要加两次
loop printChar
pop bp ; 还原现场
pop di
ret
code ends
end main
运行结果:
5.实验任务5
针对8086CPU,针对8086CPU,已知逻辑段定义如下:
data segment
stu_no db '20498329042'
len = $ - stu_no
data ends
在80×25彩色字符模式下,在屏幕最后一行正中间显示学号。要求输出窗口蓝底,学号和两侧折线,以
白色前景色显示。
实现代码:
assume cs:code, ds:data
data segment
stu_no db '201983300514'
len = $ - stu_no
data ends
code segment
main:
call print_blue_screen
call print_stu_no
mov ah, 4ch
int 21h
print_blue_screen:
push ax ; 保存现场
push es
push si
mov ax, 0b800h
mov es, ax
mov cx, 2000
mov si, 1
single_blue:
mov byte ptr es:[si], 00010000b
inc si
inc si
loop single_blue
pop si ; 还原现场
pop es
pop ax
ret
print_stu_no:
push ax
push es
push si
push ds
push di
prefix:
mov ax, 0b800h
mov es, ax
mov cx, 34
mov si, 3840 ; si存放每次显存输出的偏移地址
call print_dash
content:
mov ax, data
mov ds, ax
mov cx, len
mov di, 0 ; di存放data中每个字符的偏移地址
single_no:
mov al, ds:[di]
inc di
mov byte ptr es:[si], al
inc si
mov byte ptr es:[si], 00010111b
inc si
loop single_no
postfix:
mov cx, 34
call print_dash
pop di
pop ds
pop si
pop es
pop ax
ret
; 输入参数:
; 显示的基地址si
; 输出长度cx
; 输出:
; 迭代后的基地址si
print_dash:
single_dash:
mov byte ptr es:[si], '-'
inc si
mov byte ptr es:[si], 00010111b
inc si
loop single_dash
ret
code ends
end main
实验结果:
实验总结
这次的实验我了解了loop指令的原理以及call、ret的原理,熟悉了call和ret指令进行代码块封装和重用的能力,运用标记和ret可以封装一部分代码,并且可以通过call指令调用这段代码,这样就可以避免代码的重复冗余,节省了代码编写的时间。我还学习到了很多在dos命令行上显示字符的知识,能够在命令行的任意位置以任何颜色显示想要显示的字符。此外,我还学会了向显存中写入内容以格式化输出字符。