《汇编语言》王爽 实验代码
-
实验10
- 编写3个子程序
- 子程序①:在指定位置,用指定颜色,显示一个用0结束的字符串
- 子程序②:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型
- 子程序③:将data段中的数据以十进制的形式显示出来
assume cs:code data segment db 'Welcome to MASM!', 0 data ends code segment start: mov ax, 4240h mov dx, 000Fh mov cx, 0AH call far ptr divdw ;测试子程序②-DEBUG下查看(dx)=0001H (ax)=86A0H (cx)=0 mov ax, 12666 mov bx, data mov ds, bx mov si, 0 call dtoc ;测试子程序③-将数值转换成对应的十进制字符串,用子程序①的测试是否转换成功 mov dh, 8 mov dl, 3 mov cl, 2 call show_str ;测试子程序①-查看第8行第3列是否显示绿色字符串 mov ax, 4c00h int 21h ;子程序①:显示一个用0结束的字符串 字符串首址在ds:si处 ;Params: dh=行号(0~24) dl=列号(0~79) cl=颜色 ;return: null show_str: push dx push cx push si Initialize: mov ax, 0B800h mov es, ax mov ax, 0A0h mul dh ;计算给出的第(dh)行所在的首址 (dh)*160 byte mov bp, ax ;送偏移地址寄存器bp mov ax, 2 mul dl ;计算给出的第(dl)列所在的偏移地址 (dl)*2 byte mov di, ax ;送偏移地址寄存器di mov ah, cl ;将颜色字符串送到ah mov si, 0 s1: xor cx, cx ;cx置零 mov cl, ds:[si] ;取字符串的字符(8bitASCII码) jcxz ret1 ;若为0则跳转至ret处 mov al, cl ;此时将cl中存储的字符ASCII码送入al mov es:[bp][di], ax ;将字符与其属性的组合送入显示缓冲区 inc si ;字符串指针+1 add di, 2 ;显示缓冲区指针+2 jmp short s1 ret1: pop si pop cx pop dx ret ;子程序②:进行不会产生溢出的除法运算,被除数为dword,除数为word,结果为dword ;描述: 使用公式X/N=int(H/N)*65536+[rem(H/N)*65536+L]/N ;Params: (ax)=被除数低16位 (dx)=被除数高16位 (cx)=除数 ;return: (ax)=结果低16位 (ax)=结果高16位 (cx)=余数 divdw: push ax ;被除数低16位L入栈 push dx ;被除数高16位H入栈 ;开始计算H/N calculate: pop ax ;高16位H出栈作32位被除数的低16位 xor dx, dx ;32位被除数高16位置零 div cx ;计算H/N ax中存放商即int(H/N)部分 dx中存放余数rem(H/N) pop bx ;被除数低16位L出栈 暂存于bx中 push ax ;int(H/N)入栈 ;开始计算[rem(H/N)*65536+L]/N 此时余数作为32位被除数的高16位(直接使用dx) 原被除数的低16位直接使用 mov ax, bx div cx ;计算[rem(H/N)*65536+L]/N ax中存放dword型结果的低16位 mov cx, dx ;余数送入cx中 pop dx ;int(H/N)出栈 送入dx(即结果dword型的高16位) ret ;子程序③:将word型数据转变为表示十进制数的字符串,字符串以0结尾 ;描述: 用除法 每次除10 拿到每位上的十进制值(余数) 注意需要逆序输出 使用栈存储每个余数(每个数位)对应的ASCII码 ;Params: (ax)=word型数据 ds:si指向字符串的首地址 ;return: null dtoc: push ax ;要处理的数 入栈 mov bx, 10 ;除数10 xor cx, cx ;cx置零 xor dx, dx ;dx置零 s3: div bx ;采用16位除法(若采用8位除法,单步结果不一定小于256) ax存储商 dx存储余数 add dx, 30H ;余数(0~9)的实际数值+30H即得到它们对应的ASCII码 push dx ;入栈 准备逆序输出 xor dx, dx ;dx置零 防止上一步中的余数作为下一步运算的高16位而产生错误 inc si mov cx, ax ;若商为0说明各位的值已全部求出 jcxz ret2 ;写在inc si后面让si顺便记录循环次数 jmp short s3 ret2: mov cx, si ;准备逆序输出栈中存储的各余数字符 mov si, 0 ;si复用为偏移地址寄存器 s4: pop dx ;由于栈只能使用16位,因此dl为实际ASCII码 mov ds:[si], dl inc si loop s4 pop ax ;复原ax ret code ends end start
-
实验11
- 编写一个子程序,将包含任意字符,以0结尾的字符串中的小写字母转换为大写字母
- 参数: ds:si指向字符串首地址
assume cs:codesg datasg segment db "Beginner's All-purpose Symbolic Instruction Code.", 0 datasg ends codesg segment start: mov ax, datasg mov ds, ax mov si, 0 call letterc mov dh, 8 mov dl, 3 mov cl, 2 call show_str ;调用子程序显示字符串检查是否将小写转换为大写 mov ax, 4c00h int 21h letterc:push si ;保存 s: mov al, [si] cmp al, 0 je over ;(al)=0,已读到字符串结束符 cmp al, 97 jb continue ;小于小写字母a对应的ASCII码97则不处理 cmp al, 122 ja continue ;大于小写字母z对应的ASCII码122则不处理 sub al, 20H ;(al)-=32(20H) 得到小写字母其对应大写的ASCII码 mov [si], al continue:inc si ;偏移地址++, 继续读取下一个字符 jmp s over: pop si ret ;子程序①:显示一个用0结束的字符串 字符串首址在ds:si处 ;Params: dh=行号(0~24) dl=列号(0~79) cl=颜色 ;return: null show_str: ...... ;见上一实验 codesg ends end start
-
实验12
-
编写0号中断的处理程序,使得在除法溢出发生之前,在屏幕中间显示字符串“Divide error!”,然后返回到DOS
assume cs:code code segment start: mov ax, cs mov ds, ax mov si, offset do0 ;指向待安装的中断处理程序所在的源地址 mov ax, 0 mov es, ax mov di, 0200H ;送入中断处理程序的目标内存地址 mov cx, offset do0end - offset do0 ;通过编译器计算中断处理程序do0所占的字节个数 cld ;设置串传送指令的传输方向为正 rep movsb mov ax, 0 mov es, ax mov word ptr es:[0*4], 0200H ;设置中断向量表中0号中断的偏移地址 mov word ptr es:[0*4+2], 0 ;设置中断向量表中0号中断的段地址 ;测试是否已安装新的除法溢出中断处理程序 mov ax, 1000H mov bh, 1 div bh mov ax, 4C00H int 21H do0: jmp short do0start db "Divide error!" do0start: mov ax, cs mov ds, ax mov si, 0202H ;待显示的字符串的地址(在内存中存放中断处理程序的地址) mov ax, 0B800H mov es, ax mov di, 12*160+36*2 ;显示在第12行(从第0行开始)第36列 mov cx, 13 ;待显示字符串的长度 s: mov al, [si] ;传送字符对应的ascii码 mov ah, 00001100B ;设置字符的属性 mov es:[di], ax ;送入显存空间 inc si add di, 2 loop s mov ax, 4C00H int 21H do0end: nop code ends end start
-
实验13
-
编写并安装int 7ch号中断例程,功能为显示一个用0结束的字符串,中断例程安装在0:200处
assume cs:code data segment db "Welcome to masm", 0 data ends code segment start: mov ax, cs mov ds, ax mov si, offset show_str ;指向待安装的中断处理程序所在的源地址 mov ax, 0 mov es, ax mov di, 0200H ;送入中断处理程序的目标内存地址 0000:0200 处 mov cx, offset show_str_end - offset show_str ;通过编译器计算中断处理程序do0所占的字节个数 cld ;设置串传送指令的传输方向为正 rep movsb mov ax, 0 mov es, ax mov word ptr es:[7ch*4], 0200H ;设置中断向量表中7ch号中断的偏移地址 mov word ptr es:[7ch*4+2], 0 ;设置中断向量表中7ch号中断的段地址 ;测试中断是否成功安装 mov dh, 10 mov dl, 10 mov cl, 00000010B ;绿色 mov ax, data mov ds, ax mov si, 0 int 7ch mov ax, 4C00H int 21H ;将实验10中使用的显示字符串的子程序作为中断例程安装 ;子程序①:显示一个用0结束的字符串 字符串首址在ds:si处 ;Params: dh=行号(0~24) dl=列号(0~79) cl=颜色 ;return: null show_str: push dx push cx push si Initialize: mov ax, 0B800h mov es, ax mov ax, 0A0h mul dh ;计算给出的第(dh)行所在的首址 (dh)*160 byte mov bp, ax ;送偏移地址寄存器bp mov ax, 2 mul dl ;计算给出的第(dl)列所在的偏移地址 (dl)*2 byte mov di, ax ;送偏移地址寄存器di mov ah, cl ;将颜色字符串送到ah mov si, 0 s1: xor cx, cx ;cx置零 mov cl, ds:[si] ;取字符串的字符(8bitASCII码) jcxz ret1 ;若为0则跳转至ret处 mov al, cl ;此时将cl中存储的字符ASCII码送入al mov es:[bp][di], ax ;将字符与其属性的组合送入显示缓冲区 inc si ;字符串指针+1 add di, 2 ;显示缓冲区指针+2 jmp short s1 ret1: pop si pop cx pop dx iret show_str_end: nop ;占位 以计算中断例程部分有用的机器码字节数 便于安装 code ends end start
-
编写并安装int 7ch的中断例程,功能为完成loop指令的功能
assume cs:code code segment start: mov ax, cs mov ds, ax mov si, offset lp ;指向待安装的中断处理程序所在的源地址 mov ax, 0 mov es, ax mov di, 0200H ;送入中断处理程序的目标内存地址 0000:0200 处 mov cx, offset lp_end - offset lp ;通过编译器计算中断处理程序do0所占的字节个数 cld ;设置串传送指令的传输方向为正 rep movsb mov ax, 0 mov es, ax mov word ptr es:[7ch*4], 0200H ;设置中断向量表中7ch号中断的偏移地址 mov word ptr es:[7ch*4+2], 0 ;设置中断向量表中7ch号中断的段地址 ;测试loop中断是否成功安装 mov ax, 0B800H mov es, ax mov di, 160*12 mov bx, offset s - offset se mov cx, 80 s: mov byte ptr es:[di], '!' add di, 2 int 7ch se: nop mov ax, 4c00h int 21h lp: push bp mov bp, sp dec cx jcxz lpret add [bp + 2], bx ;将堆中存放的此前调用中断时入栈的IP值加上转移位移 中断返回时达到修改IP的目的 lpret: pop bp iret lp_end: nop code ends end start
-
使用系统提供的10号中断和21号中断在屏幕的第2,4,6,8行显示四句英文诗
assume cs:code code segment s1: db 'Good,better,best,','$' s2: db 'Never let it rest,','$' s3: db 'Till good is better,','$' s4: db 'And better,best.','$' s: dw offset s1, offset s2, offset s3, offset s4 row: db 2, 4, 6, 8 start: mov ax, cs mov ds, ax mov bx, offset s mov si, offset row mov cx, 4 ok: mov bh, 0 mov dh, ds:[si] mov dl, 0 mov ah, 2 int 10h ;调用10号中断的2号功能 设置光标位置 (BH)为页码 (DH)为行坐标 (DL)为列坐标 mov dx, [bx] ;将bx中存放的各诗句所在的偏移地址 使用ds:[bx]取到这些诗句实际的偏移地址送入dx mov ah, 9 int 21h ;调用21号中断的9号功能 显示字符串 DS:DX为串地址 以'$'结尾 inc si add bx, 2 ;s中存放的是各诗句的偏移地址(word型) 故取下一个偏移地址执行bx+=2即可 loop ok mov ax, 4c00h int 21h code ends end start
-
实验14
-
从CMOS RAM中取出时间信息,以“年/月/日 时:分:秒”的格式显示当前日期、时间
assume cs:code data segment db '??/??/?? ??:??:??','$' unit db 9, 8, 7, 4, 2, 0 data ends code segment start: mov ax, data mov ds, ax mov bx, offset unit mov si, 0 mov cx, 6 s: mov al, [bx] out 70h, al in al, 71h ;CMOS RAM中这些存储时间的单元均以BCD码形式存储 mov ah, al push cx mov cl, 4 shr ah, cl ;高4位 十位数码值 and al, 00001111B ;低4位 个位数码值 ;拿到数值对应的ASCII码 add ah, 30h add al, 30h mov ds:[si], ah ;十位数码写入日期字符串中 mov ds:[si + 1], al ;个位数码写入日期字符串中 add si, 3 inc bx pop cx loop s ;调用21号中断的9号功能显示字符串 DS:DX为串地址 mov dx, 0 mov ah, 9 int 21h mov ax, 4c00h int 21h code ends end start
-
实验15
- 安装一个新的int 9中断例程
- 功能:在DOS下,按下“A”键后,除非不再松开,如果松开则满屏幕显示“A”,其他键照常处理
assume cs:code stack segment db 128 dup (0) stack ends code segment start: mov ax, stack ;使用自己定义的栈 mov ss, ax mov sp, 128 push cs pop ds ;设置(ds) = (cs) mov ax, 0 mov es, ax ;中断例程待安装的目的段地址 mov si, offset int9 mov di, 204h ;在0:200H存放原来的int9中断例程的入口地址 所以0:204H处安装新的int9中断例程 mov cx, offset int9end - offset int9 cld rep movsb ;将中断向量表中原来的int9中断的入口地址从0:[9*4]处(dword型)复制到0:[200H]处 push es:[9*4] pop es:[200h] push es:[9*4+2] pop es:[202h] ;把新的int9中断例程的入口地址送到中断向量表中存放9号中断入口地址的内存单元中 ;用cli与sti包裹 防止在执行这段指令时 键盘引发中断 导致中断例程入口错误 cli mov word ptr es:[9*4], 204h mov word ptr es:[9*4+2], 0 sti mov ax, 4c00h int 21h int9: push ax push bx push cx push es press: in al, 60h ;从60h端口读出键盘输入 ;调用0:200H处的原始int9中断例程 处理其他硬件细节 pushf call dword ptr cs:[200h] ;这条指令执行时 该中断例程已成功安装在0:204H处 因此(cs)=0 cmp al, 1Eh ;“A”的通码为1Eh jne int9ret ;确认"A"已按下 循环等待"A"松开 release:in al, 60h pushf call dword ptr cs:[200h] cmp al, 9Eh ;"A"的断码为9Eh 断码=通码+80h jne release ;DOS显示缓冲区第一页全部显示"A" mov ax, 0b800h mov es, ax mov bx, 0 mov cx, 2000 s: mov byte ptr es:[bx], 'A' add bx, 2 loop s int9ret:pop es pop cx pop bx pop ax iret int9end:nop code ends end start
-
实验16
- 安装一个新的int 7ch中断例程,为显示输出提供下列功能子程序
- 子程序0:清屏
- 子程序1:设置前景色
- 子程序2:设置背景色
- 子程序3:向上滚动一行:依次将第n+1行的内容复制到第n行处,最后一行留空
- 用ah传递中断中使用的子程序功能号:0清屏,1设置前景色,2设置背景色,3向上滚动一行
- 对于1,2号功能,使用al传递颜色值(RGB) (al)∈{0,1,2,3,4,5,6,7}
-
最重要指令 ORG 0204H(中断例程的安装地址) 表示以下指令从Origin所示的起始地址开始编译
assume cs:code code segment start: mov ax, cs mov ds, ax mov ax, 0 mov es, ax mov si, offset int7c ;ds:si源串地址 mov di, 0204h ;es:di目标地址 mov cx, offset int7cend - offset int7c cld rep movsb ;将原始中断向量表中的int 7ch中断例程的入口地址复制到0:200处保存 以期恢复 push es:[7ch*4] pop es:[0200h] push es:[7ch*4 + 2] pop es:[0202h] ;将新的int 7ch中断例程的入口地址放入中断向量表中 双字低16位为偏移地址 高16位为段地址 cli mov word ptr es:[7ch*4], 0204h mov word ptr es:[7ch*4 + 2], 0 sti ;测试新安装的int 7ch中断 mov ah, 1 mov al, 1 ;前景色设为蓝 int 7ch mov ah, 2 mov al, 6 ;背景色设为R+B即红+蓝->橙 int 7ch delay: push ax push dx mov dx,1000h mov ax,0 s1: sub ax,1 sbb dx,0 cmp ax,0 jne s1 cmp dx,0 jne s1 pop dx pop ax ret mov ax, 4c00h int 21h ;int 7ch中断 ;用ah传递中断中使用的子程序功能号:0清屏,1设置前景色,2设置背景色,3向上滚动一行 ;对于1,2号功能,使用al传递颜色值(RGB) (al)∈{0,1,2,3,4,5,6,7} ;注意由于安装时\程序段都被复制到0:0204H处,若是把这些子程序当前标号的地址直接放在sublist里 ;会导致调用时实际上是找原始的偏移地址,因此现在计算它们相对于实际安装位置的地址即可 org 0204h ;ORG伪指令 标识以下的指令都是从偏移地址0204h(即中断例程的安装位置)开始,防止标号地址相对于安装前有改变 int7c: jmp short begin sublist dw sub0, sub1, sub2, sub3 begin: push bx push ds cmp ah, 3 ja int7cret ;功能号大于3则中断返回 mov bl, ah mov bh, 0 add bx, bx ;功能号*2就是其子程序入口地址存放在sublist中的偏移 push cs pop ds call word ptr sublist[bx] ;调用对应功能子程序 2是jmp short begin所占的机器码字节数 int7cret: pop ds pop bx iret ;子程序0:清屏 sub0: push cx push es mov bx, 0b800h mov es, bx mov bx, 0 mov cx, 2000 ;一页显示80*25=2000个 sub0s: mov byte ptr es:[bx], ' ' add bx, 2 loop sub0s pop es pop cx ret ;子程序1:设置前景色 sub1: push cx push es mov bx, 0b800h mov es, bx mov bx, 1 mov cx, 2000 ;更改字符属性,以0B800H为段地址的显示缓冲区的奇字节为属性 sub1s: and byte ptr es:[bx], 11111000B ;分离代表前景色属性的0,1,2位 or es:[bx], al ;将al中存放的颜色数值同当前前景色相或 达成前5位不变 后3位改为目标RGB值 add bx, 2 loop sub1s pop es pop cx ret ;子程序2:设置背景色 sub2: push cx push es mov cl, 4 ;由于al范围在0~8即3bit二进制表示 shl al, cl ;使用逻辑左移将其移至第4,5,6位便于后面直接与字符属性相或处理 mov bx, 0b800h mov es, bx mov bx, 1 mov cx, 2000 ;更改字符属性,以0B800H为段地址的显示缓冲区的奇字节为属性 sub2s: and byte ptr es:[bx], 10001111B ;分离代表背景色属性的4,5,6位 or es:[bx], al ;将al中存放的颜色数值同当前字段属性相或,修改字符属性字节中的背景色bit位 add bx, 2 loop sub2s pop es pop cx ret ;子程序3:向上滚动一行:依次将第n+1行的内容复制到第n行处,最后一行留空 sub3: push cx push si push di push es push ds mov si, 0b800h ;ax存储了信息不动用 用si中转 mov es, si mov ds, si mov si, 160 mov di, 0 cld mov cx, 24 ;共计24行(屏幕一共25行,最后一行留空) sub3s: push cx mov cx, 160 ;行内共计80个字符一共160字节 rep movsb ;使用串传送不断复制 pop cx loop sub3s mov cx, 80 ;开始处理最后一行 mov si, 0 sub3s1: mov byte ptr [160*24 + si], ' ' ;清空最后一行 add si, 2 loop sub3s1 pop ds pop es pop di pop si pop cx ret int7cend: nop code ends end start
-
例题17.1
-
使用int 16h中断,编程,接收用户的键盘输入,输入’r’/’g’/’b’将当前显示缓冲区中的字符设置为对应的颜色
assume cs:code code segment start: mov ah, 0 int 16h mov ah, 1 ;设置的字符属性字节中前景色RGBbit位在低0,1,2位,这里设置为001表示默认蓝色 cmp al, 'r' je red cmp al, 'g' je green cmp al, 'b' je blue jmp short sret ;不是键盘的rgb通码则结束 red: shl ah, 1 ;逻辑左移一次 同下面执行的Green中的一次左移使字段变为100 green: shl ah, 1 blue: mov bx, 0b800h mov es, bx mov bx, 1 mov cx, 2000 s: and byte ptr es:[bx], 11111000B ;前景色RGB位置0 or es:[bx], ah ;使用前述字段对RGB进行设置 add bx, 2 loop s sret: mov ax, 4c00h int 21h code ends end start
-
实验17
- 安装一个新的int 7ch中断例程,实现通过逻辑扇区号对软盘进行读写
- ah传递功能号:0读1写
- dx传递逻辑扇区号(0~2879)
- es:bx指向存储读出数据或写入数据的内存区
assume cs:code, es:data data segment destination dw 256 dup(0) ;一个扇区512字节,即256个字,es:bx指向这段数据段即可 data ends code segment start: mov ax, cs mov ds, ax mov si, offset int7c mov ax, 0 mov es, ax mov di, 0200h mov cx, offset int7c_end - offset int7c cld rep movsb ;将中断向量表中int 7ch中断的入口地址写入 cli mov word ptr es:[7ch*4], 0200h mov word ptr es:[7ch*4 + 2], 0 sti ;由于没有软盘,只是写出测试代码 mov dx, 2879 mov ax, data mov es, ax mov bx, destination mov ah, 0 int 7ch mov ax, 4c00h int 21h ;org 0200h ;由于并未在新中断中定义标号,故不用使用该伪指令(指明以下程序中的标号,编译器处理时以0200H为偏移起始地址) int7c: push dx push cx push bx push ax cmp ah, 1 ;若功能号大于1则返回 ja int7c_ret ;调用int 13h时需要复原的寄存器 push bx push ax ;dx中存放逻辑扇区号 ;除以1440得到面号,余数继续处理 mov ax, dx mov bx, 1440 div bx ;16位除法,AX保存商,DX保存余数 push ax ;面号压栈,待全部计算完毕后再调用int 13h中断 mov ax, dx ;把前一步中逻辑扇区号/1440的余数送到存放被除数的ax中 mov bx, 18 div bx ;商为磁道号,余数为扇区号-1 push ax ;磁道号写入table中 inc dx ;余数加1,拿到扇区号 push dx ;扇区号写入table中 ;计算完毕后调用int 13h pop bx mov cl, bl ;(cl)=扇区号 pop bx mov ch, bl ;(ch)=磁道号 mov dl, 0 ;驱动器号,软驱从0开始,硬盘从80h开始 pop bx mov dh, bl ;(dh)=磁头号 对于软盘就是面号 pop ax ;复原ax add ah, 2 ;由于该int 7ch中断0表示读而1表示写;int 13h中2表示读而3表示写。加2即可 mov al, 1 ;默认读/写1个扇区 pop bx ;int 13h操作的内存地址也是放在es:bx中,因此复原bx int 13h int7c_ret: pop ax pop bx pop cx pop dx iret int7c_end: nop code ends end start