实验16 简单解法
实验16 另一种解法
代码参考 :使用offset计算偏移地址
实际运行
完整源码
- 安装程序
assume cs:code
code segment
start: ;------ 安装功能程序到 0000:0200H 开始 --------
mov ax,cs
mov ds,ax
mov si, offset setscreen
mov ax,0
mov es,ax
mov di,200H
mov cx,offset setscreenend - offset setscreen
cld
rep movsb
;------ 在中断向量表 N = 7CH 号表项处写入
; 中断处理程序的入口地址0000:0200h -----
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
;org 200h
;---------- 7CH 号中断处理程序本身 ---------
; 用ah传递功能号,0-清屏 1-设置前景色 2-设置背景色 3-向上滚动一行
setscreen: jmp short set
table dw offset sub1 - offset setscreen + 200H,offset sub2 - offset setscreen + 200H,offset sub3 - offset setscreen + 200H,offset sub4 - offset setscreen + 200H
set: push bx
push si
cmp ah,3
ja sret
mov bl,ah
mov bh,0
add bx,bx
mov si,offset start - offset setscreen
call word ptr table[si+bx+200H]
sret: pop si
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
; 设置前景色 用al传送颜色值
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],11111000B
or es:[bx],al
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret
; 设置背景色 用al传送颜色值
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
; 滚动一行:依次将第n+1行的内容复制到第n行;最后一行为空
sub4: push cx
push si
push di
push es
push ds
mov si,0B800H
mov es,si
mov ds,si
mov si,160 ; ds:si指向第n+1行
mov di,0 ; es:di指向第n行
cld
mov cx,24
sub4s: push cx
mov cx,160
rep movsb
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
setscreenend: nop
code ends
end start
- 测试程序
assume cs:code
code segment
begin:
mov ah,2
mov al,00001001B ; 闪烁 背景R G B 高亮 前景R G B
int 7CH
mov ax,4c00h
int 21H
code ends
end begin
代码说明
- 代码片段
setscreen: jmp short set
table dw offset sub1 - offset setscreen + 200H,
offset sub2 - offset setscreen + 200H,
offset sub3 - offset setscreen + 200H,
offset sub4 - offset setscreen + 200H
set: push bx
push si
cmp ah,3
ja sret
mov bl,ah
mov bh,0
add bx,bx
mov si,offset start - offset setscreen
call word ptr table[si+bx+200H]
sret: pop si
pop bx
iret
- 先说 table[X] 的含义
由于在安装程序里写了 assume cs:code
所以 table[X] 本质上是 cs:N[X]
首先明确,执行中断程序时我们期望 cs=0000H,这样才能指向中断向量表,
但是安装程序本身执行时 cs 是什么我们并不知道,不妨假设安装时 cs = 076AH;
N是什么?
N 等于 076A:setscreen 减去 076A: start 所得的偏移量 + 2
这个2 来自于 setscreen标号后那一条jmp指令,这条jmp指令的机器码长度是2
用代码计算就是 = offset setscreen - offset start + 2
这里的 setscreen 和 start 都是安装程序的标号
【注意:为了和测试程序区分开来,我在测试程序用的是 begin】
对N而言,无论安装时cs=076AH 还是 cs=6666H,N的值都不变的,就是一个固定的偏移值
也是说,如果你要用table,你要知道 【它背地里给你多加了个N】
-
再看 table 里存着什么? 答,子程序的入口地址。
table dw offset sub1 - offset setscreen + 200H,
offset sub2 - offset setscreen + 200H,
offset sub3 - offset setscreen + 200H,
offset sub4 - offset setscreen + 200H
本质上存的就是 0222H 023DH 025BH 027DH
这些都是后面子程序 sub1 sub2 sub3 sub4 的入口地址的偏移地址
mov si,offset start - offset setscreen
call word ptr table[si+bx+200H]
si 添什么乱? si 不是添乱,si 是用来中和背地里的N的
si = start - setscreen
N = setscreen - start + 2
si + N = 2
因为 table [X] = CS:N[X] = CS:[N+X]
所以 table[X+si] = CS:N[X+si] = CS:[X+2]
在我们的代码里面用的是 table[si+bx+200H]
bx = 功能号 x 2 = ah的值 x 2
取值范围是{0,2,4,6}
结合上面的图表,我们可以看见
假设现在 ah = 2 要调用 sub3 子程序,
计算出来 bx = ah x 2 = 4
table[si+bx+200H] = cs:N[si+bx+200H] = cs:[2+bx+200H] 中和掉N
= cs:[2+4+200H]
此时是执行中断处理程序,CS = 0000H,即 CS:[206H] = 0000:0206H
按照上面的图表,内存地址 0000:0206H 存着的一个字型单元是 025BH
接着CPU就转去执行CS:025BH了,这就实现了调用 sub3 子程序。
可见table[si+bx+200H] 里面的200H是为了寻址用的,
这个地址是子程序入口地址的内存单元地址
总结,table[si+bx+200H]
(1)si 是为了中和看不见的 N ,带来结果 si + N = 2 (2源自jmp指令的2个机器码长度)
(2)200H 是为了寻找子程序入口地址的内存单元地址 。
那么为什么要用setscreen 减去 start
用标号start 本质上是想找到 code segment 之后的第一句代码,
计算看不见的N需要的本质不是start,
而是code segment之后的第一个机器码的偏移地址,也就是code段的开始。
因为assume cs:code是与code段关联的,
我们要找的N是相对于code段的开始。
如果start 之前还有东西,就还要加上那些东西占用的内存单元。
验证猜想
- table[si+bx+200H] 被解读成 CS:N[si+bx+200H]
table[si+bx+200H]
= CS:N[si+bx+200H]
= CS:[N+si+bx+200H]
= CS:[bx+022FH]
本程序中
N = 002D H + 2 = 002FH
= setscreen 距离 start的偏移量 + jmp指令的2个机器码
感想
之前写int 7ch中断处理程序,都是把完成安装的复制部分、中断处理本身割裂开来的,这次的实现则有一点不同,必须看到code段的首句以start标号开头,这就是代码段的开始,前方没有db dw dd 之类占着内存的东西了,这个影响着后面计算N,这次的代码是揉在一起的,受苦。