1、 理论知识参考"《80X86汇编语言程序设计教程》十九 操作系统类指令与输入输出保护",演示内容:显示80386关键寄存器的内容。逻辑功能:显示系统中GDTR、IDTR、LDTR和DR等关键寄存器的当前内容。
2、 源代码
“386scd.asm”不再贴上来。参考"《80X86汇编语言程序设计教程》十三 任务内无特权级变换转移实例",演示代码如下:
1 ;DosTest.Asm
2 ;演示内容:显示80386关键寄存器的内容
3 ;逻辑功能:显示系统中GDTR、IDTR、LDTR和DR等关键寄存器的当前内容
4
5
6 include 386scd.asm ;文件386.scd含有有关结构、宏指令和符号常量定义
7 .386p
8
9 ;字符显示宏指令的定义ECHOCH
10 ECHOCH macro ascii
11 mov ah,2
12 mov dl,ascii
13 int 21h
14 endm
15
16 ;------------------------------------------
17 ;全局描述符表GDT
18 GDTSeg segment para use16
19 GDT label byte
20 ;空描述符
21 dummy DESCRIPTOR<>
22
23 ;规范数据段描述符,存在的可读写数据段
24 Normal DESCRIPTOR<0ffffh,0,0,ATDW,0>
25 Normal_Sel = Normal - GDT
26 EFFGDT label byte
27
28 ;临时任务代码段描述符(16位段,DPL = 0,RPL = 0)
29 TempCode DESCRIPTOR<0ffffh,TempCodeSeg,,ATCE,>
30 TempCode_Sel = TempCode - GDT
31
32 ;缓冲区数据段描述符(16位段,DPL = 0,RPL = 0)
33 Buffer DESCRIPTOR<BufferLen -1,BufferSeg,,ATDW,>
34 Buffer_Sel = Buffer - GDT
35
36 ;测试描述符1
37 TestDesc1 DESCRIPTOR<01111h,0,,8792h,>
38 TestDesc1_Sel = TestDesc1 - GDT
39 TestDescR_Sel = TestDesc1 - GDT + RPL3
40
41 ;测试描述符2
42 TestDesc2 DESCRIPTOR<02222h,0,,1782h,>
43 TestDesc2_Sel = TestDesc2 - GDT
44
45 ;GDT中需要初始化基地址的描述符个数
46 GDTNum = ($ - EFFGDT)/(size DESCRIPTOR)
47
48 ;GDT段长度
49 GDTLen = $ - GDT
50 GDTSeg ends
51
52 ;------------------------------------------
53 ;缓冲区段描述符
54 BufferSeg segment para use16
55 GDTR_V PDESC<> ;存放GDTR
56 IDTR_V PDESC<> ;存放IDTR
57 MSW_V label word ;存放机器状态字
58 db 6 dup(0)
59 LDTR_V label word ;存放LDTR选择子
60 db 6 dup(0)
61 TR_V label word ;存放TR选择子
62 db 6 dup(0)
63 CR0_V label dword ;存放控制寄存器CR0
64 db 6 dup(0)
65 CR3_V label dword ;存放控制寄存器CR3
66 db 6 dup(0)
67 DR7_V label dword ;存放调试寄存器DR7
68 db 6 dup(0)
69 Test_RPL label word ;暂存RPL
70 db 6 dup(0)
71 ;
72 Test1_SLD equ this dword ;TestDesc1演示用变量
73 db 6 dup(0)
74 Test1_ARD equ this dword
75 db 6 dup(0)
76 Test1_SLW equ this word
77 db 6 dup(0)
78 Test1_ARW equ this word
79 db 6 dup(0)
80 Test1_RF equ this word
81 db 6 dup(0)
82 Test1_WF equ this word
83 db 6 dup(0)
84 ;
85 Test2_SLD label dword ;TestDesc2演示用变量
86 db 6 dup(0)
87 Test2_ARD label dword
88 db 6 dup(0)
89 Test2_SLW label word
90 db 6 dup(0)
91 Test2_ARW label word
92 db 6 dup(0)
93 Test2_RF label word
94 db 6 dup(0)
95 Test2_WF label word
96 db 6 dup(0)
97 ;
98 BufferLen = $ - BufferSeg
99 BufferSeg ends
100
101 ;------------------------------------------
102 ;临时代码段
103 TempCodeSeg segment para use16
104 assume cs:TempCodeSeg,ds:BufferSeg
105 Virtual:
106 mov ax,Buffer_Sel
107 mov ds,ax
108 mov eax,cr0 ;存储CR0
109 mov CR0_V,eax
110 mov eax,cr3 ;存储CR3
111 mov CR3_V,eax
112 mov eax,dr7 ;存储DR7
113 mov DR7_V,eax
114 str TR_V ;存储TR
115 sldt LDTR_V ;存储LDTR
116 ;
117 ;如果OPRD1.RPL < OPRD2.RPL
118 ;那么ZF = 1,OPRD1.RPL = OPRD2.RPL
119 ;以下Test_RPL.RPL = 0,ax.RPL = 3
120 ;执行后ZF = 1,Test_RPL.RPL = 3
121 mov Test_RPL,TestDesc1_Sel
122 mov ax,TestDescR_Sel;RPL = 3
123 arpl Test_RPL,ax ;说明调整申请特权级指令
124 ;
125 mov bx,0
126 mov ax,TestDesc1_Sel
127 Lab1:
128 mov edx,0
129 mov cx,0
130 lsl edx,eax ;说明装载段界限指令
131 lsl cx,ax
132 mov dword ptr Test1_SLD[bx],edx
133 mov word ptr Test1_SLW[bx],cx
134 mov edx,0
135 mov cx,0
136 lar edx,eax ;说明装载存取权指令
137 lar cx,ax
138 mov dword ptr Test1_ARD[bx],edx
139 mov word ptr Test1_ARW[bx],cx
140 mov word ptr Test1_RF[bx],0
141 verr ax ;说明读检验指令
142 jnz Lab2
143 mov word ptr Test1_RF[bx],1
144 Lab2:
145 mov word ptr Test1_WF[bx],0
146 verw ax ;说明写检验指令
147 jnz Lab3
148 mov word ptr Test1_WF[bx],1
149 Lab3:
150 add bx,36 ;偏移36个字节,指向TestDesc2
151 mov ax,TestDesc2_Sel
152 cmp bx,72
153 jb Lab1
154 Over:
155 ;准备返回实模式
156 mov ax,Normal_Sel
157 mov ds,ax
158 mov eax,cr0
159 and eax,0fffffffeh
160 mov cr0,eax
161 JUMP16 <seg Real>,<offset Real>
162 TempCodeSeg ends
163
164 ;------------------------------------------
165 ;实模式下的数据段和代码段
166 RCodeSeg segment para use16
167 VGDTR PDESC<GDTLen - 1,>
168 assume cs:RCodeSeg,ds:BufferSeg
169 start:
170 mov ax,RCodeSeg
171 mov ds,ax
172 cld
173 ;初始化GDT
174 call INIT_GDT
175 ;装载GDTR
176 mov bx,offset VGDTR
177 lgdt fword ptr [bx]
178 ;存储相关寄存器
179 mov ax,BufferSeg
180 mov ds,ax
181 sgdt GDTR_V ;存储GDTR
182 sidt IDTR_V ;存储IDTR
183 smsw MSW_V ;存储机器状态字
184 ;切换到保护模式
185 cli
186 mov eax,cr0
187 or eax,1
188 mov cr0,eax
189 JUMP16 <TempCode_Sel>,<offset Virtual>
190 Real:
191 ;又回到实模式
192 sti
193 ;显示缓冲区内容
194 mov ax,BufferSeg
195 mov ds,ax
196 mov si,0 ;使ds:si指向缓冲区首地址
197 mov bp,BufferLen/6
198 NEXTLINE:
199 mov cx,6
200 NEXTCH:
201 lodsb ;读入DS:SI的内容到AL中
202 push ax ;实际上是为了压入AL
203 shr al,4 ;取高半字节
204 call TOASCII
205 ECHOCH al
206 pop ax
207 call TOASCII
208 ECHOCH al
209 ECHOCH ' '
210 loop NEXTCH
211 ECHOCH 0dH
212 ECHOCH 0ah
213 dec bp
214 jnz NEXTLINE
215 ;等待按键结束程序
216 mov ax,0700h
217 int 21h
218 mov ax,4c00h
219 int 21h
220 ;------------------------------------------
221 ;初始化全局描述符表的子程序
222 ;(1)把定义时预置的段值转换成32位段基地址并置入描述符内相应字段
223 ;(2)初始化为GDTR准备的伪描述符
224 INIT_GDT proc near
225 push ds
226 mov ax,GDTSeg
227 mov ds,ax
228 mov cx,GDTNum ;初始化描述符的个数
229 mov si,offset EFFGDT ;开始偏移
230 assume si:ptr DESCRIPTOR
231 INITG:
232 mov ax,[si].BaseL ;取出预置的段值
233 movzx eax,ax ;扩展到32位
234 shl eax,4
235 shld edx,eax,16 ;分解到2个16位寄存器
236 mov [si].BaseL,ax ;置入描述符相应字段
237 mov [si].BaseM,dl
238 mov [si].BaseH,dh
239 add si,size DESCRIPTOR ;调整到下一个描述符
240 loop INITG
241 assume si:nothing
242 pop ds
243 ;
244 mov bx,16 ;初始化为GDTR准备的伪描述符
245 mov ax,GDTSeg
246 mul bx
247 mov word ptr VGDTR.Base,ax
248 mov word ptr VGDTR.Base + 2,dx
249 ret
250 INIT_GDT endp
251
252 ;把AL低4位的十六进制数转换成对应的ASCII码,保存在AL中
253 TOASCII proc
254 and al,0fh
255 add al,90h
256 daa
257 adc al,40h
258 daa
259 ret
260 TOASCII endp
261
262 RCodeSeg ends
263 end start
3、 源代码的一些说明
1) 老毛病依旧
2) 原书没有写显示部分,我为了简单的实现,改写了Buffer区的存储结构,以边界6字节对齐,然后每个变量作为1行输出,输出的是内存十六进制数对应ASCII码的形式
3) ECHOCH宏与TOASCII子过程为自行添加,为了输出数据
4、 测试输出
5、 测试说明
1) 作者的这个实例有点烂,我也懒,开始想构造TSS、IDT、LDT等全面测试一下环境切换的情况,一想又被打消了。接异常处理那例,代码就700行左右,汇编太繁琐真是。以下各系统指令参考“《80X86汇编语言程序设计教程》十九 操作系统类指令与输入输出保护”。
2) GDTR
使用指令sgdt存储,可以在实模式和保护模式任何权限等级执行。其格式参考“《80X86汇编语言程序设计教程》八 80386程序设计基础”。需要说明两点:段基址是系统分配的,现在暂时不知道怎么干预;段界限Limit = 47,这个就是在构造GDT时它的长度减去1(GDTLen – 1),容纳(47 + 1)/8 = 6个描述符,其中包括了0号空描述符。
3) IDTR、LDTR、TR、CR3与DR7
本测试用例均没用到它们。所以基址为0,界限为0FFFFH、选择子为0。这里注意:LDTR可以为0(空选择子),而且在LLDT指令时不会出错,但是TR为空,使用指令LTR装载必定触发通用保护故障。此外CR3用于分页机制,这里没有开启分页机制,测试它没有任何意义,而DR7比较复杂,这里不详解。
4) MSW、CR0
MSW为CR0的低16位。MSW在存储的是实模式下的值,而CR0存储的是保护模式下的值。可以对比看到,从实模式切换到保护模式,PE位由0切换到了1。
5) Test_RPL
指令ARPL的测试结果,其中OPRD1为“TestDesc1_Sel”,它的RPL = 0,而OPRD2为“TestDescR_Sel”,它的RPL = 3。指令后,Test_RPL = 3且ZF = 1。须注意:Test_RPL只是RPL字段被修改,其它的维持原样。23H = 0010 0011B,按照其格式可知,RPL = 3、TI = 0(位于GDT中)、索引 = 100B = 4(是GDT中第5个描述符),这些信息与代码编写时的设计一样。
6) Test1_SLD、Test1_SLW与Test2_SLD、Test2_SLW
指令LSL的测试结果,它们复制的是Limit字段(参考“《80X86汇编语言程序设计教程》九 分段管理机制及纯DOS环境搭建”)。由于测试描述符1的G = 1,所以段粒度为4KB,则执行结果将左移12位,空出位全部用1填充,得到的就是图片上的结果;而测试描述符2的G = 0,所以不进行转换,与代码的界限值“2222H”保持一致。此外,使用字存储结果时,只有低字部分的偏移,从图中可以得到验证。
7) Test1_ARD、Test1_ARW与Test2_ARD、Test2_ARW
指令LAR的测试结果,它们复制的是Attribute字段(参考“《80X86汇编语言程序设计教程》九 分段管理机制及纯DOS环境搭建”)。代码中测试描述符1属性字段为“8792h”,而这里显示的是“8793h”,原因是访问位A位从0被置1。而测试描述符2代码中的属性字段设置值与测试时显示值保持一致。此外,如果使用字存储结果时,将只有属性字段的低字节(描述符偏移为5的字节),从图中可以得到验证。
8) Test1_RF、Test1_WF和Test2_RF、Test2_WF
测试指令VERR与VERW。从代码设置测试描述符的结构分析,测试描述符1为“<01111h,0,,8792h,>”,可得:
1 ;粒度位G = 1:4KB
2 ;段界限Limit = 71111H
3 ;DPL = 0
4 ;存在位P = 1,存储段描述符DT = 1
5 ;类型TYPE = 2:未被访问的可写数据段
而测试描述符2为“<02222h,0,,1782h,>”,可得:
1 ;粒度位G = 0:1B
2 ;段界限Limit = 72222H
3 ;DPL = 0
4 ;存在位P = 1,存储段描述符DT = 0
5 ;类型TYPE = 2:LDT段描述符
可见,测试时使用的选择子全部为RPL = 0,而当前代码的CPL = 0。测试描述符1位可读写数据段,满足了RPL <= DPL,CPL <= DPL,故可读可写,所以图中的Test1_RF、Test1_WF均为1。而测试描述符2位LDT描述符,是不可读写的,所以Test2_RF、Test2_WF都为0。这里需要注意一个地方,LDT表段并不是不能读写,但是不能通过LDT描述符来读写,可以另外构造一个可读写数据段描述符,指向LDT,然后引用它的段选择子对LDT进行改写。这种所谓的别名技术之前有用过。