debug中的常用命令
g 加地址,直接跳转至此处,前面全部执行
u 将内存中的机器指令翻译成汇编指令
t 执行一条指令
r 查看寄存器中的值,同时显示出下一条要执行的指令,还可以改变寄存器中的内容,比如r ax,然后弹出冒号,输入即可
d 查看内存中的内容,d 段地址:偏移地址 (可以在此处加上想查看的范围,默认是128字节),之后再按d显示后续内容
p 可以跳过loop循环
e 向内存单元写入命令,e 段地址:偏移地址 B8 01 00,即向该内存写入mov ax,1命令
第7章 更灵活的定位内存地址的方法
话不多说,直接上图,完成了任务。只不过忘掉了如何一次性执行完循环,一直t加回车,头皮发麻。这一题是让我们补充codesg段的代码,我们要灵活的利用栈来存储和释放cx
s0处的push cx是将外层循环的cx值压入栈中,然后往下执行,mov cx,4设置内层循环次数,执行完4次s1后,此时的cx值为零,将之前cx的值弹出栈,恢复为3(即4-1),然后往复执行。
tips 快速结束循环
再补充一个快速的指令 g 偏移地址,例如g 0012执行后,ip=0012,从此处开始往下执行。
第8章 数据处理的两个基本问题
只有上述形式是是正确的,有个小要点,[bp]默认的段地址是ss。
指令要处理的数据有多长 这是我们必须指出的,可以显性地指出也可以隐形的指出,比如在有寄存器名称的情况下我们可以判断出访问的是字单元还是字节单元,在没有寄存器参与的情况下用操作符X ptr指明长度。X为byte或word。对于push [1000]这样的指令则无需指明,因为push指令只进行字操作。
实验 7
废了九牛二虎之力终于用比较朴素的方法实现了。遇到了不少问题,其中最主要的两个是:
error A2052: Improper operand type
g命令之后卡死
第一个在经过Google之后找到了解答
我记得这一点书上好像提到过,给忘掉了,真是一头雾水
第二个问题,算是摸索着解开了疑惑,网上说有三种情况,1.代码段没加mov ax,4c00h 2.重启解决 3.代码导致g命令出错
我的情况应该是属于第三种,因为我按自己的想法写的时候,寻址方式比较奇怪。比如说我成功之前的那一次
疑惑 1 2 3 4 div word ptr [bx].0Ah//g命令后卡死,虽然这两处的值不相同,但按道理来说结果会出错,不应该出现程序卡死,此处存有疑惑 div word ptr [bx+0ah]//也不可行 div word ptr es:[bx+0ah]//成功,上边的错误在于忘记了要标明es段,可是有一点存疑,就算用的是ds段地址,那同样也能读取数据,为什么会卡死呢????? div word ptr [168+si]//正确
寻址方式小结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 assume cs:codesg //写的比较朴实,可能有较多重复的步骤,也可以一次循环填充一行中的所有信息,然后循环 data segment //21次即可 db '1975','1976','1977','1978','1979','1980','1981','1982','1983' db '1984','1985','1986','1987','1988','1989','1990','1991','1992' db '1993','1994','1995' dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514 dd 345980,590827,803530,1183000,1843000,2759000,375000,4649000,5937000 dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226 dw 11542,14430,15257,17800 data ends table segment db 21 dup('year sumn ne ?? ') table ends codesg segment start:mov ax,data mov ds,ax mov ax,table mov es,ax mov bx,0 mov si,0 mov cx,21 s0: mov ax,[si] mov es:[bx],ax add si,2 mov ax,[si] mov es:[bx+2],ax add si,2 add bx,16 loop s0 mov bx,0 mov si,0 mov cx,21 s1: mov ax,[84+si] mov es:[bx+5],ax add si,2 mov ax,[84+si] mov es:[bx+5+2],ax add si,2 add bx,16 loop s1 mov bx,0 mov si,0 mov cx,21 s2: mov ax,[168+si] mov es:[bx+0Ah],ax add bx,16 add si,2 loop s2 mov bx,0 mov si,0 mov cx,21 s3: mov ax,es:[bx+5] mov dx,es:[bx+5+2] div word ptr [168+si] mov es:[bx+0Dh],ax add si,2 add bx,16 loop s3 mov ax,4c00h int 21h codesg ends end start
通过这次的实验知识发现了不少短板,hhh或者说全是短板哈哈哈哈,很多基础的东西打的不是很牢固,比如说高低字节,高低位
高低字节,高低位 1 2 对于 1234 h这个十六进制数来说,其高字节是12 ,低字节是34 我们平时接触小端序比较多,那么高字节12 存放在高地址单元中,低字节34 存放在低地址单元中
如果我们进行div word ptr ds:0操作,那么处理的也就是0、1这两个内存单元组成的数1234h。
第九章 转移指令的原理 操作符offeset offeset在汇编语言中是由编译器处理的符号,它的功能是取得标号处的偏移地址
转移指令 jmp指令 CPU在执行jmp指令的时候不需要转移的目的地址,需要的是位移量。
转移的目的地址在指令中的jmp指令
可以看到,这里是通过目的地址而非位移量进行转移的
转移地址在内存中的jmp指令 jmp word ptr 内存单元地址(段内转移)
jmp dword ptr 内存段地址(段间转移)
对于段间转移 (CS)=(内存单元地址+2),
(IP) =(内存单元地址)
jcxz指令 有条件的段间转移,有条件转移都是段间的,在对应的机器码中包含位移而不是地址。当cx==0的时候执行跳转,cx!=0时直接执行下一条语句
loop指令 循环指令都是段指令,对应的机器码中包含位移地址而不是目的地址
实验八 分析一个奇怪的程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 assume cs:codesg codesg segment mov ax,4c00h int 21h start: mov ax,0 s: nop nop mov di,offset s mov si,offset s2 mov ax,cs:[si] mov cs:[di],ax s0: jmp short s s1: mov ax,0 int 21h mov ax,0 s2: jmp short s1 nop codesg ends end start
虽然看着是挺奇怪的,s的操作就是使s处的命令变为s2处的命令,即跳转到s1,很显然mov ax,0这里不满足让程序正确返回,用debug的t命令进行调试,可以成功运行,我们知道jup命令是不带有目标位置的地址的,它含有一个偏移地址,s2处的jump short s1的机器码是EB F6,F6就是偏移地址1111 0110补码表示-10,-10含义是标号处的地址-jmp指令后的第一个字节的地址,也就是从mov di,offset s位置前移十个字节,正好到达mov ax,4c00h int 21h,程序得以成功返回
实验九 将’welcome to masm!’正好16个字符,填入第11、12、13行
绿色属性 0 000 0 010B 02h
绿底红字属性 0 010 0 100B 24h
白底蓝色属性 0 111 0 001B 71h
经过不懈的努力终于是搞好了,困住我的主要有两点,其一是对字,字节,寄存器不敏感,对于传输字和字节有点生疏。其二就是让我崩溃的东西,题目要求是打印在中间,我一想80个字符位,左边空出32,右边空出32中间正好留下16,然后左边的32x2=64,有因为是从零开始,所以这边我们第一个填充的位置就是64呀,当成了63操作,结果是真抽象,还好最后一试,将welcome全部改成了11111,打印出来发现全部是同一个颜色的符号,也就是说我的颜色就然和字符与关,果断想到填充错了位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 assume cs:codesg,ds:data data segment db 'welcome to masm!' data ends codesg segment start:mov ax,data mov ds,ax mov di,0 mov cx,16 mov bx,0 mov ax,0B800h mov es,ax mov si,06e0h mov ah,02h s0:mov al,ds:[di] mov es:[bx+si+64],al mov es:[bx+si+65],ah add bx,2 add di,1 loop s0 mov ax,0B800h mov es,ax mov di,0 mov bx,0 mov si,780h mov cx,16 s1:mov al,ds:[di] mov es:[bx+si+64],al mov al,24h mov es:[bx+si+65],al add bx,2 inc di loop s1 mov ax,0B800h mov es,ax mov bx,0 mov di,0 mov si,820h mov cx,16 s2:mov al,ds:[di] mov es:[bx+si+64],al mov al,71h mov es:[bx+si+65],al add bx,2 inc di loop s2 mov ax,4c00h int 21h codesg ends end start
第十章 CALL指令和RET指令 ret和retf ret指令只修改ip的内容,实现近迁移,retf应该就是ret far的意思,同时修改cs和ip中的内容,实现远迁移。
用汇编语言解释
ret:POP IP
retf:POP IP ,POP CS
CALL指令
依据位迁移进行转移的call指令
下面程序执行后,ax的值
读取过call s指令后,ip中的值自动增加(第二章 28页),变为6,call指令相当于push ip,s处是pop ax,所以ax的值为6。
转移的目的地址在指令中的call指令
转移地址在寄存器中的call指令
bp的默认段地址是ss
转移地址在内存中的call指令
回顾一下jump dword ptr 内存单元地址
CS=(内存单元地址+2)
IP=(内存单元地址)
mul指令
实验 10 编写子程序 1.显示字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 assume cs:code,ss:stack data segment db'welcome to masm!',0 data ends stack segment db 8 dup (0) stack ends code segment start:mov dh,8 ;传参 8行 3列 绿色 mov dl,3 mov cl,2 mov ax,data mov ds,ax mov si,0 call show_str mov ax,4c00h int 21h show_str: sub dh,1;这里注意000是第一行开始,160是第2行开始 mov al,160 mul dh;存入的数据是7*160,即第八行开始 mov bx,ax mov dh,0 add bx,dx add bx,dx;这里实现列数,注意两个字节表一个字符,所以我们加两次即加6 mov ax,0b800h mov es,ax mov al,cl s:mov cl,[si] mov ch,0 jcxz ok;如果是结束字符0,则跳转回去 mov es:[bx],cl mov es:[bx+1],al inc si add bx,2 jmp short s ok: ret code ends end start
由于粗心出现了一个警告 missing data:zero assume,提醒我们缺少操作数
把add bx,dx写成了add bx,dx
2.解决除法的溢出问题
给出了一个提示
65536是十六进制的1 0000也就是将得到的数左移四个16进制位呗,我们可以直接把int(H/N)的内容直接放入dx中,然后把后面的整体装入ax中,把后面整体产生的余数装入cx中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 assume cs:code,ss:stack stack segment dw 8 dup (0) stack ends code segment start:mov ax,stack mov ss,ax mov sp,10h mov ax,4240h mov dx,000fh mov cx,0ah call divdw mov ax,4c00h int 21h divdw:push ax push dx mov bx,ax mov dx,0 pop ax div cx push ax;商 push dx;余 mov ax,bx div cx mov cx,dx pop dx pop dx pop si;它存在的意义是把栈清理到只剩一个,以便使下面的ret成功返回 ret ;前面哪些部分其实可以不使用栈,此题占用的寄存器不多 code ends end start
3.数值显示将二进制转化为十进制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 assume cs:code data segment db 10 dup (10) data ends code segment start: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 mov ax,4c00h int 21h dtoc:mov dx,0 mov bx,10 div bx;余数在dx,商在ax mov cx,ax add dx,30h mov [si],dl;存放余数的ascii inc si;表示位数 jcxz short s1 jmp short dtoc s1:ret show_str: sub dh,1 mov al,160 mul dh mov bx,ax mov dh,0 add bx,dx add bx,dx mov ax,0b800h mov es,ax mov al,cl s:mov cl,[-1+si];66621 mov ch,0 ;jcxz ok 如果不注释掉的话,以零结尾的数将无法显示 mov es:[bx],cl mov es:[bx+1],al sub si,1 mov cx,si jcxz short ok add bx,2 jmp short s ok: ret code ends end start
感谢这位博主:https://blog.csdn.net/qq_60829702/article/details/123582250
第十一章 标志寄存器
ZF 标志 记录相关指令执行后结果是否为零,如果为零则ZF为1,如果非零则ZF为0。
PF 标志 记录执行相关指令后,结果的所有bit位1的个数是否为偶数,如果1的数量是偶数,则PF为1.
SF 标志 记录执行相关指令后,结果是否为负,如果结果为负,则SF为1.
CF 标志 在进行无符号运算时,它记录进位和借位情况
进位比较好理解,下面看一下借位
OF 标志 进行有符号运算是否发生溢出,如果溢出则OF为1.
adc 指令
这个指令乍一看很奇怪,很多余,为什么要加上一个cf?但是看了下面的解释之后,我直呼巧妙
adc也会对CF位进行设置,由于这样的功能,我们可以对任意大的数据进行加法运算。
sbb 指令
cmp 指令
可以通过标志寄存器中的值得出比较结果
这里有点疑惑的,为什么实际结果为负,且发生了溢出,就可以推出实际结果为正。溢出,有正溢出和负溢出。正溢出就是两个正数相加,超过了能表示的最大范围,变为负数,负溢出就是两个负数相加,超出了能表示的最小范围,变为正数。举个例子:
1 2 3 4 5 拿四位有符号数来举例 MAX ==0111==7MIN ==1000==-8正溢出0111+0111 ==1110(补码)==-2 负溢出1000+1000 ==10000截断最高位==0000两个负数相加结果为0
好的疑惑消失了。
检测比较结果的条件转移指令 这些指令往往和cmp搭配使用,因为cmp可以同时进行两种比较,无符号数比较和有符号数比较。
DF标志和串传送指令
串传送指令:movsb,实现传送一个字节,movsw实现传送一个字
movsb一般和rep搭配使用
pushf 和 popf
push 和 pop显然不能进行这些操作,他两个的操作对象是字而不是bit。
标志寄存器在Debug中的表示
实验 11 编写子程序
我们分析一下题目,以0结尾,我们可以通过cx实现自动循环。
难点在于我们如何识别出是小写字符az,应该是通过ASCII(97122)判断,如果ascii在这个区间,那么我们就通过and 1101 1111将第6位置零,如果不在此区间,那么直接下一个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 assume cs:codesg datasg segment db "Beginner's All-purpose Symbolic Instruction Code.",0 data ends codesg segment begin:mov ax,datasg mov ds,ax mov si,0 call letterc mov ax,4cooh int 21h letterc: s0 :mov cl,[si] mov ch,0 jcxz then cmp cl,97 jb over;如果小于97,进行下一个字符 cmp cl,122;如果大于122,进行下一个 ja over and cl,11011111b;如果在区间内则 mov [si],cl over :inc si jmp s0 then :ret codesg ends end begin
运行完我们使用d命令进行检测,全部都是大写,成功。
第十二章 内中断
内中断的产生
有四种中断源,前三个看不懂,但是最后这个int很熟悉,我们可以推测一下,int 21,21这个中断类型码代表的就是结束程序。CPU通过中断类型码来识别中断信息的来源。
中断过程
1.取得中断类型码
2.标志寄存器入栈
3.设置标志寄存器
4.CS的内容入栈
5.IP的内容入栈
6.读取入口地址
中断处理程序和iret指令 中断处理程序是存储在内存某段空间之中的,因为CPU随时都可能执行中断处理程序。中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表中。
iret指令相较于ret指令多了一步,让标志寄存器出栈
pop IP
pop CS
popf
向量表
实验 12 编写0号中断的处理程序
要点:1.中断处理程序一般存储在0000:0200~0000:02ff这256个字节中。
2.显示缓冲区 以B8000开始 25x80,25行80列,一列160个字节,两个字节显示一个字
3.我们想要现实的字符串,不能刚开始就存放在data段中,因为这个程序执行完之后,它所占用的内存空间被系统释放,在其中存放的字符串很可能会被别的信息覆盖,所以我们将字符串放置在do0程序中
1 2 3 do0 安装程序 将do0 的内容放在以0000 :0200 开头的那段空间中 设置中断向量表 将中断类型码0 对应的中断向量表改为do0 程序的起始地址 注意第一个字节是偏移地址,第二个字节是段地址 do0 程序 实现功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 assume cs:code code segment start:;do0安装程序 mov ax,cs mov ds,ax ;设置ds:si指向源地址(do0) mov si,offset do0;刚开始忘了设置si了,导致源地址指向了向量表 mov ax,0 mov es,ax mov di,200h ;设置es:di指向目的地址 mov cx,offset do0end-do0 cld ;设置传输方向为正 rep movsb ;逐字节传输 ;设置中断向量表 mov ax,0 mov es,ax mov word ptr es:[0],200h;通用的格式是[n*4] mov word ptr es:[2],0 ;检测程序 mov ax,1000h mov bh,1 div bh mov ax,4c00h int 21h do0:jmp short do0start db "divide erro!" do0start: mov ax,cx mov ds,ax mov si,202h ;设置es:di指向字符串,刚开始的jmp指令长度为两个字节 mov ax,0b800h mov es,ax mov di,12*160+36*2 ;第十三行,第36列 mov cx,12 ;cx为字符串长度 s: mov al,[si] mov es:[di],al mov es:[di+1],7ch inc si add di,2 loop s mov ax,4c00h int 21h do0end:nop code ends end start
第十三章 int 指令
BIOS和DOS所提供的中断例程
实验 13 编写、应用中断例程
这一题应该可以调用int 10的9号子程序吧,搭配2号子程序设置光标,但是9号子程序的结束标志是‘$’,看来是想让我们自己实现。
在网上看到了一种方法,先把安装程序执行,在运行测试程序,只要不重启DOSBox,就会保存我们设置的中断例程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ;编写安装程序 assume cs:code code segment start:mov ax,cs mov ds,ax mov si,offset ins ;设置ds:si指向源程序 mov ax,0 mov es,ax mov di,200h ;设置es:di指向目的地址 mov cx,offset insends-0ffset ins ;传输长度 cld ;正向传输 rep mobsb ;设置中断向量表 mov ax,0 mov es,ax mov word ptr es:[7ch*4],200h ;两个字节存一个地址 高地址存段 低地址存偏移 mov word ptr es:[ych*4+2],0 mov ax,4c00h int 21h ins:sub dh,1;这里注意000是第一行开始,160是第2行开始 mov al,160 mul dh;存入的数据是7*160,即第八行开始 mov bx,ax mov dh,0 add bx,dx add bx,dx;这里实现列数,注意两个字节表一个字符,所以我们加两次即加6 mov ax,0b800h mov es,ax mov al,cl s:mov cl,[si] mov ch,0 jcxz ok;如果是结束字符0,则跳转回去 mov es:[bx],cl mov es:[bx+1],al inc si add bx,2 jmp short s ok: iret insends:nop code ends end start
这里的ins程序和我们之前做的show_str十分吻合,我们直接copy过来,不要忘记将子程序使用的寄存器入栈然后反序出栈。
啊这个不就是书中的例子吗,自己尝试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ;编写安装程序 照着抄即可 assume cs:code code segment start:mov ax,cs mov ds,ax mov si,offset ins ;设置ds:si指向源程序 mov ax,0 mov es,ax mov di,200h ;设置es:di指向目的地址 mov cx,offset insends-offset ins ;传输长度 cld ;正向传输 rep movsb ;设置中断向量表 mov ax,0 mov es,ax mov word ptr es:[7ch*4],200h ;两个字节存一个地址 高地址存段 低地址存偏移 mov word ptr es:[7ch*4+2],0 mov ax,4c00h int 21h ins:push bp mov bp,sp dec cx jcxz lpret add [bp+2],bx ;给原来的ip加上位移 lpret:pop bp iret insends:nop code ends end start
本来ins那里
1 2 3 4 ins: dec cx jcxz lpret add [sp],bx ;给原来的ip加上位移
然后就出现了一个报错:must be index or base register 必须是索引寄存器或者base寄存器
也就是sp不能放在[]里单独使用,只有bx、si、di、bp (第八章)
看到后面的’$‘,不难看出下面将使用int 21的子程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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 strat: 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 mov dx,ds:[bx] mov ah,9 int 21h add bx,2 int si,2 loop ok mov ax,4c00h int 21h code ends end strat
第十四章 端口 端口的读写 端口的读写指令只有两条,in和out,分别用于从端口读取数据和往端口写入数据
举个例子 (这里的in和out是针对 out就是从寄存器中出去)
1 2 3 mov dx,3f8h ;将端口号送入dx in al,dx ;从3f8h端口读入一个字节,就是将dx中的数据送入al out dx,al ;向3f8h端口写入一个字节,将al中的数据写入dx
CMOS RAM 芯片
1 2 3 mov al,2 out 70h,al in al,71h
逻辑位移指令 shl和shr
左移相当于x*2,右移相当于x/2。
实验 14 访问 CMOS RAM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 assume cs:code code segment start: mov cx,3 mov si,30 s: push cx add cx,6 mov al,cl out 70h,al in al,71h mov ah,al mov cl,4 shr ah,cl and al,00001111b add ah,30h ;以ascii表示 add al,30h mov bx,0b800h mov es,bx mov bx,si mov byte ptr es:[160*12+si+bx],ah mov byte ptr es:[160*12+si+bx+2],al mov byte ptr es:[160*12+si+bx+4],2fh add si,3 pop cx loop s mov cx,3 s2: push cx add cx,cx sub cx,2 push cx mov al,cl out 70h,al in al,71h mov ah,al mov cl,4 shr ah,cl and al,00001111b add ah,30h ;以ascii表示 add al,30h mov bx,0b800h mov es,bx mov bx,si mov byte ptr es:[160*12+si+bx],ah mov byte ptr es:[160*12+si+bx+2],al pop cx jcxz ok ;如果没有这一步的化,最后秒后面会多出一个冒号 mov byte ptr es:[160*12+si+bx+4],3ah ;冒号的ascii add si,3 pop cx loop s2 ok:mov ax,4c00h int 21h code ends end start
第十五章 外中断 用两个16位寄存器来存放32位的循环次数,这里刚开始没看懂,在网上找到了一个恍然大悟
1 2 3 4 5 6 7 8 mov ds,10h mov ax,0 s: sub ax,1 ;第一次ax=fffffh,cf=1 sbb dx,0 ;第一次dx=fh cmp ax,0 jne s cmp dx,0 jne s
上述程序实现了循环100000次。sbb dx,0 相当于dx=dx-0-CF,每循环(ffffh+1)即10000h次,dx减1.注意这里dx减1,发生在每10000次循环的最开始哦。
sti和cli指令
下面的程序用于改变全屏的显示信息非常的炫酷
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 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 mov ax,0 mov es,ax mov si,offset int9 mov di,204h mov cx,offset int9end-offset int9 cld rep movsb push es:[9*4] pop es:[200h] push es:[9*4+2] pop es:[202h] 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 in al,60h ;接收键入的字符 pushf call dword ptr cs:[200h] cmp al,3bh jne int9ret mov ax,0b800h mov es,ax mov bx,1 mov cx,2000 s: inc byte ptr es:[bx] add bx,2 loop s int9ret:pop es pop cx pop bx pop ax iret int9end:nop code ends end start
实验 15 安装新的int9中断例程
这次实验我们只需要更改上面程序int9的部分即可,A的通码1E,断码9E
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 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 mov ax,0 mov es,ax mov si,offset int9 mov di,204h mov cx,offset int9end-offset int9 cld rep movsb push es:[9*4] pop es:[200h] push es:[9*4+2] pop es:[202h] 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 in al,60h ;接收键入的字符 pushf call dword ptr cs:[200h] cmp al,1eh ;A的通码 je int9ret cmp al,9eh jne int9ret mov ax,0b800h mov es,ax mov bx,0 mov cx,2000 s: mov byte ptr es:[bx],65 add bx,2 loop s int9ret:pop es pop cx pop bx pop ax iret int9end:nop code ends end start
第十六章 直接定址表 描述了单元长度的标号
1 2 3 4 code segment a db 1,2,3,4,5,6,7,8 b dw 0 ;这里的a其实可以理解为数组的首地址a[0]是1
1 2 3 a:db 1,2,3,4 b:dw 0 ;这种用法是代码段中特有的 即使用 :
将标号当作数据来使用 1 2 3 4 5 data segment a db 1,2,3,4,5 b dw 0 c dw a,b ;c dd a,b data ends
相当于
1 2 3 4 5 data segment a db 1,2,3,4,5 b dw 0 c dw offset a,offset b ;c dw offset a,seg a,offset b,seg b data ends
seg 取段地址
实验 16
具体的功能怎么实现,书中写的很清楚,我们的任务是确定整个程序的框架。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 assume cs:code stack segment db 64 dup(0) stack ends code segment start:;安装新的int7ch mov ax,stack mov ss,ax mov sp,64 mov ax,cs mov ds,ax mov ax,offset int7ch mov si,ax mov ax,0 mov es,ax mov di,200h mov cx,offset int7chend-offset int7ch cld rep movsb cli mov word ptr es:[7ch*4],200h mov word ptr es:[7ch*4+2],0 sti mov ax,4c00h int 21h int7ch: jmp short set table dw offset sub1-offset int7ch+200h dw offset sub2-offset int7ch+200h dw offset sub3-offset int7ch+200h dw offset sub4-offset int7ch+200h set:push bx cmp ah,3 ja sret mov bl,ah mov bh,0 add bx,bx call word ptr cs:[bx+202h] ;这里的cs是跳转到int7ch之后的cs,加202是因为前面的jmp short set占了两个字节 sret:pop bx iret sub1:push bx ;清屏 push cx push es mov bx,0b800h mov es,bx mov bx,0 mov cx,2000 sub1s:mov byte ptr es:[bx],' ' add bx,2 loop sub1s pop es pop cx pop bx ret sub2:push bx ;前景 push cx push es mov bx,0b800h mov es,bx mov bx,1 mov cx,2000 sub2s:and byte ptr es:[bx],11110000b or es:[bx],al add bx,2 loop sub2s pop es pop cx pop bx ret sub3:push bx ;背景 push cx push es mov cl,4 shl al,cl mov bx,0b800h mov es,bx mov bx,1 mov cx,2000 sub3s:and byte ptr es:[bx],10001111b or es:[bx],al add bx,2 loop sub3s pop es pop cx pop bx ret sub4:push cx ;滚动 push si push di push es push ds mov si,0b800h mov es,si mov ds,si mov si,160 mov di,0 cld mov cx,24 sub4s:push cx mov cx,160 rep movsb ;si和di的增加在此处进行 pop cx loop sub4s mov cx,80 mov si,0 sub4s1:mov byte ptr [160*24+si],' ' add si,2 loop sub4s1 pop ds pop es pop di pop si pop cx ret int7chend:nop code ends end start
其实我们这里使用table的方式有点麻烦
1 2 3 4 5 6 7 8 9 ; table dw offset sub1-offset int7ch+200h dw offset sub2-offset int7ch+200h dw offset sub3-offset int7ch+200h dw offset sub4-offset int7ch+200h ;call word ptr cs:[bx+202h] ;我们把上面这些替换成下面的 ;在int7ch前面加上 org 200h 表示偏移地址从200h处开始 table dw sub1,sub2,sub3,sub4 call word ptr table[bx]
没加那个org 200h是不行的
org 200H ;表示下一条地址从偏移地址200H开始,和安装后的偏移地址相同,若没有org 200H,中断例程安装后,标号代表的地址改变了,和之前编译器编译的有所区别
如果我们不加上org,那么这个table的偏移地址就是本程序中的偏移地址.可是为什么sub1,这种标号还是以200h为准的偏移?应该是因为,sub1作为一个标号能正确的复制过去,table作为一个标号复制不过去,是因为table是比较抽象的?隐式的?
下面这张图是org 200h的,【bx+202h】
这张图是没有org的,我们可以看到下面的【bx+36h】,加的table的偏移地址
疑惑 为什么同样是标号,table复制过去是安装程序中的地址,sub传过去的就是200h的偏移地址???我觉得合理的解释是这个sub1s跟着一起复制了过去,就是第二个sub1s其实是随着第一个sub1s的变化而变化的。在程序执行完之后,主程序所占的内存被其他数据覆盖,之前的sub1消失,这么理解的话,标号代表的不一定是个固定不变的地址。
第十七章 使用BIOS进行键盘输入和磁盘读写