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
表示LOOP
F2
是补码形式的位移量,转换为二进制为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中进行调试观察的反汇编截图
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
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
进行代码调试
运行到第一次call之后,可见确实执行了s1后的代码,ax的值也确实是s1所标记地址的偏移量
继续向下执行
执行到第二次call,并运行两次pop之后,可见第二次call之后确实执行了s2之后的代码,并且bx的值确实是s2标记地址的偏移量,cx确实是当前代码段地址也就是cs寄存器中的内容
针对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
运行效果如下:
针对8086CPU,已知逻辑段定义如下:
data segment str db 'try' len equ $ - str data ends
编写8086汇编源程序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
运行结果:
针对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命令行上显示字符的知识,能够在命令行的任意位置以任何颜色显示想要显示的字符。此外,我还学会了向显存中写入内容以格式化输出字符。