实验任务一
代码:
1 assume cs:code, ds:data 2 data segment 3 x db 1, 9, 3 4 len1 equ $ - x ; 符号常量, $指下一个数据项的偏移地址,这个示例中,是3 5 y dw 1, 9, 3 6 len2 equ $ - y ; 符号常量, $指下一个数据项的偏移地址,这个示例中,是9 7 data ends 8 code segment 9 start: 10 mov ax, data 11 mov ds, ax 12 mov si, offset x ; 取符号x对应的偏移地址0 -> si 13 mov cx, len1 ; 从符号x开始的连续字节数据项个数 -> cx 14 mov ah, 2 15 s1:mov dl, [si] 16 or dl, 30h 17 int 21h 18 mov dl, ' ' 19 int 21h ; 输出空格 20 inc si 21 loop s1 22 mov ah, 2 23 mov dl, 0ah 24 int 21h ; 换行 25 mov si, offset y ; 取符号y对应的偏移地址3 -> si 26 mov cx, len2/2 ; 从符号y开始的连续字数据项个数 -> cx 27 mov ah, 2 28 s2:mov dx, [si] 29 or dl, 30h 30 int 21h 31 mov dl, ' ' 32 int 21h ; 输出空格 33 add si, 2 34 loop s2 35 mov ah, 4ch 36 int 21h 37 code ends 38 end start
1. 理解运算符offset、伪指令equ、预定义符号$的灵活使用。
答:
运行结果如下:
offset的作用是取得标号的偏移地址。如本题定义一个数组x含有三个元素,则通过offset x可以取得x的起始地址,也就是0.
len1 equ相当于定义一个常量,常量名为equ,对应偏移地址为$-x相当于对应x最后一个元素的偏移量,也就是3.
2. 回答问题
① line27, 汇编指令 loop s1 跳转时,是根据位移量跳转的。通过debug反汇编,查看其机器码, 分析其跳转的位移量是多少?(位移量数值以十进制数值回答)从CPU的角度,说明是如何计算得 到跳转后标号s1其后指令的偏移地址的。
答:反汇编结果如下
跳转的位移量是1B-D=E=14
CPU首先取得目前正在执行的指令的地址加上本条指令所占用空间的地址,也就是001B,然后减去标号的地址000D得到结果E,然后向前移动E个单位。
② line44,汇编指令 loop s2 跳转时,是根据位移量跳转的。通过debug反汇编,查看其机器码, 分析其跳转的位移量是多少?(位移量数值以十进制数值回答)从CPU的角度,说明是如何计算得 到跳转后标号s2其后指令的偏移地址的。
答:反汇编结果如下。
跳转的位移量是39-29=10=16
CPU首先取得目前正在执行的指令的地址加上本条指令所占用空间的地址,也就是0039,然后减去标号的地址0029得到结果10h,然后向前移动10h个单位。
③ 附上上述分析时,在debug中进行调试观察的反汇编截图
实验任务二
代码:
1 assume cs:code, ds:data 2 data segment 3 dw 200h, 0h, 230h, 0h 4 data ends 5 stack segment 6 db 16 dup(0) 7 stack ends 8 code segment 9 start: 10 mov ax, data 11 mov ds, ax 12 mov word ptr ds:[0], offset s1 13 mov word ptr ds:[2], offset s2 14 mov ds:[4], cs 15 mov ax, stack 16 mov ss, ax 17 mov sp, 16 18 call word ptr ds:[0] 19 s1: pop ax 20 call dword ptr ds:[2] 21 s2: pop bx 22 pop cx 23 mov ah, 4ch 24 int 21h 25 code ends
① 根据call指令的跳转原理,先从理论上分析,程序执行到退出(line31)之前,寄存器(ax) = ? 寄存器 (bx) = ? 寄存器(cx) = ?
答:ax中值应该是s1的偏移地址,bx中值应该是s2的偏移地址,cx中值应该是cs存储的地址。
首先ds:[0]指向的地址应该是s1的偏移地址,则call word ptr ds:[0]就是跳转到ds:[0]指向的地址执行,并且将当前IP压入栈中,当前的IP正好也是s1的偏移地址,所以ax对应s1指令的偏移地址。
bx是call dword ptr,所以会把cs和IP都压入栈中,之后同理,因为下一条就是s2,执行pop bx会把s2的地址出栈,存储给bx。
对于cx,由于栈中执行的操作是三次进栈,两次出栈操作,所以栈中还存储有cs的地址,出栈后cx对应栈中的地址。
② 对源程序进行汇编、链接,得到可执行程序task2.exe。使用debug调试,观察、验证调试结果与理论 分析结果是否一致。
实验任务三
要求: 编写子程序printNumber
功能:以十进制形式输出一个两位数
入口参数:寄存器ax(待输出的数据 --> ax)
出口参数:无 编写子程序
printSpace 功能:打印一个空格
入口参数:无
出口参数:无
在主体代码中,综合应用寻址方式和循环,调用printNumber和printSpace, 实现题目要求。
代码:
1 assume ds:data, cs:code 2 3 data segment 4 x db 99, 72, 85, 63, 89, 97, 55 5 len equ $- x 6 data ends 7 8 code segment 9 start: 10 mov ax,data 11 mov ds,ax 12 mov si,0 13 mov cx,len;cx中存储定义的字的个数,这也是循环会执行的次数 14 s: call printNumber;打印一个两位数 15 call printSpace;打印一个空格 16 inc si 17 loop s 18 19 mov ax,4c00h 20 int 21h 21 22 printNumber: 23 mov ah, 0 24 mov al, [si] 25 mov bl, 10 26 div bl 27 mov bl,al 28 mov bh,ah 29 30 mov ah,2 31 mov dl,bl 32 add dl,30h 33 int 21h 34 35 mov ah,2 36 mov dl,bh 37 add dl,30h 38 int 21h 39 ret 40 41 42 printSpace: 43 mov ah,2 44 mov dl,' ' 45 int 21h 46 ret 47 code ends 48 end start
效果:
实验任务四
要求: 编写子程序printStr
功能:
在指定行、以指定颜色,在屏幕上显示字符串
入口参数
字符串首字符地址 --> ds:si(其中,字符串所在段的段地址—> ds, 字符串起始地址的偏 移地址—> si)
字符串长度 --> cx
字符串颜色 --> bl
指定行 --> bh (取值:0 ~24)
出口参数:无
代码:
1 assume ds:data, cs:code 2 3 data segment 4 str db 'try' 5 len equ $ - str 6 data ends 7 8 code segment 9 start: 10 mov ax,data 11 mov ds,ax 12 13 mov si,0 14 mov cx,len;字符串长度为len赋值给cx 15 mov bh,0 16 mov bl,00000010B;对应绿色 17 call printStr 18 19 mov si,0 20 mov cx,len;字符串长度为len赋值给cx 21 mov bh,24 22 mov bl,00000100B;对应红色 23 call printStr 24 25 mov ax,4c00h 26 int 21h 27 28 29 printStr: 30 mov ax,0b800h;设置ax为显存段地址 31 mov es,ax 32 33 mov al,bh 34 mov bh,0a0h 35 mul bh 36 mov bp,ax 37 s: 38 mov ah,ds:[si];每次取出一个字节给ah 39 mov es:[bp],ah;把字母存给显存地址低八位中 40 inc bp 41 mov es:[bp],bl;将颜色信息存入高八位 42 inc bp 43 inc si 44 loop s 45 ret 46 47 code ends 48 end start
效果:
实验任务五
代码:
1 assume ds:data, cs:code 2 3 data segment 4 stu_no db '20498329042' 5 len = $ - stu_no 6 data ends 7 8 code segment 9 start: 10 mov ax,0b800h 11 mov es,ax 12 mov bp,1 13 mov cx,8000h 14 15 first: 16 mov byte ptr es:[bp],00011111B 17 add bp,2 18 loop first 19 20 mov ax,data 21 mov ds,ax;ds作数据段 22 mov ax,0050h;一行的长度 23 sub ax,len;减去学号的长度 24 mov bh,02h;除以二得到横线的长度 25 div bh 26 mov bl,al;商赋给bx,后面要用 27 mov bh,0 28 29 mov ax,0b800h;显存地址存给es 30 mov es,ax 31 mov bp,0F00h;首字母偏移量赋给bp 32 mov cx,bx 33 34 s: 35 mov byte ptr es:[bp],'-' 36 inc bp 37 inc bp 38 loop s;打印横杠 39 40 mov cx,len 41 mov di,0 42 43 s1: 44 mov al,ds:[di] 45 mov es:[bp],al 46 inc bp 47 inc bp 48 inc di 49 loop s1;打印学号 50 51 mov cx,bx 52 53 s2: 54 mov byte ptr es:[bp],'-' 55 inc bp 56 inc bp 57 loop s2;打印横杠 58 59 mov ax,4c00h 60 int 21h 61 62 code ends 63 end start
效果:
实验总结:
本次实验主要考察了两部分的内容,一部分是对跳转指令的理解和应用,另一部分是对显存数据写入的理解和应用。
首先实验使用了equ指令定义常量len,这个常量因为是定义字符的下一个地址,字符的开始地址是从0开始的,所以就可以得到字符的长度。
CPU执行跳转操作的时候,不是直接根据所给的地址进行跳转,而是根据下一条指令的偏移量和给出的目标地址进行计算得到位移量进行回退的。执行call命令的时候需要利用栈来存储跳跃前的指令的地址,这样在执行ret的时候就可以快速跳转回去,所以在此期间也不可以对栈段和栈偏移地址寄存器进行修改。offset指令通过获得某个定义的代码块的偏移地址来达成和call类似的效果。
将字符显示在屏幕上有两种方法,第一种是通过int21h的二号指令,将想要打印的数传给al来实现,因为会打印ASCII码,所以对于打印数字来说需要进行一次or 30h或add 30h的运算,才可以将数字转换成对应的码值。
写入显存的时候需要知道显存一行所占的字节数,显存地址从0b800h开始,偏移000-09F对应第一行,总计24行以此类推。对于每一个字来说,低位存储了想要打印的数字或字符,高位决定了数字的前景色和背景色,前四位决定背景色和高亮,后四位决定前景色和高亮,因此在显存中打印字符的时候需要一次跳两位。如果想像本题最后一题那样把整个屏幕都变蓝,我采取的方法是在开始的时候就把蓝色背景色对应数字存入每一个单数的数字位中(高位),这样整个屏幕就变蓝了。