0%

汇编语言

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

废了九牛二虎之力终于用比较朴素的方法实现了。遇到了不少问题,其中最主要的两个是:

  1. error A2052: Improper operand type
  2. 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
对于 1234h这个十六进制数来说,其高字节是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==7
MIN==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的内容放在以00000200开头的那段空间中
设置中断向量表 将中断类型码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进行键盘输入和磁盘读写