《80X86汇编语言程序设计教程》十四 任务内特权级变换转移实例

时间:2022-09-01 07:54:30

1、  理论知识参考"《80X86汇编语言程序设计教程》十二 任务状态段、控制门和控制转移",演示:通过调用门进行无特权等级变换转移;通过调用门从外层特权等级变换到内存特权级;通过段间返回指令从内层特权级变换到外层特权级。实现的逻辑功能:从保护模式R0切换到R3,执行显示子程序显示演示程序代码段执行时的当前特权级CPL,然后切换回R0。

 

 

2、  源代码

  “386scd.asm”不再贴上来。参考"《80X86汇编语言程序设计教程》十三 任务内无特权级变换转移实例",演示代码如下:

  1 ;DosTest.Asm
2 ;演示任务内特权级变换转移
3 ;逻辑功能:显示演示程序代码段执行时的当前特权级CPL
4
5
6 include 386scd.asm ;文件386.scd含有有关结构、宏指令和符号常量定义
7 .386p
8
9 ;------------------------------------------
10 ;全局描述符表GDT
11 GDTSeg segment para use16
12 GDT label byte
13 dummy DESCRIPTOR<>
14
15 ;规范数据段描述符,存在的可读写数据段
16 Normal DESCRIPTOR<0ffffh,0,0,ATDW,0>
17 Normal_Sel = Normal - GDT
18 EFFGDT label byte
19
20 ;演示任务状态段TSS描述符
21 DemoTSS DESCRIPTOR<DemoTSSLen -1,DemoTSSSeg,,AT386TSS,>
22 DemoTSS_Sel = DemoTSS - GDT
23
24 ;演示任务LDT段描述符
25 DemoLDT DESCRIPTOR<LDTLen -1,LDTSeg,,ATLDT,>
26 DemoLDT_Sel = DemoLDT - GDT
27
28 ;临时代码段描述符
29 TempCode DESCRIPTOR<0ffffh,TempCodeSeg,,ATCE,>
30 TempCode_Sel = TempCode - GDT
31
32 ;视频缓冲区段描述符(DPL = 3),段界限0fffffh
33 VideoBuff DESCRIPTOR<0ffffh,0,0,0f00h + ATDW + DPL3,0>
34 Video_Sel = VideoBuff - GDT
35
36 GDTNum = ($ - EFFGDT)/(size DESCRIPTOR)
37 GDTLen = $ - GDT
38 GDTSeg ends
39 ;------------------------------------------
40 ;演示任务局部描述符表LDT
41 LDTSeg segment para use16
42 LDT label byte
43
44 ;0级堆栈段描述符(32位段,DPL = 0,RPL = 0)
45 DemoStack0 DESCRIPTOR<DemoStack0Len - 1,DemoStack0Seg,,ATDW + D32,>
46 DemoStack0_Sel = (DemoStack0 - LDT) + TIL
47
48 ;1级堆栈段描述符(32位段,DPL = 1,RPL = 1)
49 DemoStack1 DESCRIPTOR<DemoStack1Len - 1,DemoStack1Seg,,ATDW + D32 + DPL1,>
50 DemoStack1_Sel = (DemoStack1 - LDT) + TIL + RPL1
51
52
53 ;3级堆栈段描述符(16位段,DPL = 3,RPL = 3)
54 DemoStack3 DESCRIPTOR<DemoStack3Len - 1,DemoStack3Seg,,ATDW + DPL3,>
55 DemoStack3_Sel = (DemoStack3 - LDT) + TIL + RPL3
56
57 ;演示代码段描述符(32位段,DPL = 3,RPL = 3)
58 DemoCode DESCRIPTOR<DemoCodeLen - 1,DemoCodeSeg,,ATCE + D32 + DPL3,>
59 DemoCode_Sel = (DemoCode - LDT) + TIL + RPL3
60
61 ;过渡代码段描述符(32位段,DPL = 0,RPL = 0)
62 T32Code DESCRIPTOR<T32CodeLen - 1,T32CodeSeg,,ATCE + D32,>
63 T32Code_Sel = (T32Code - LDT) + TIL
64
65 ;显示子程序代码段描述符(32位段,DPL = 1,RPL = 1 以及 RPL = 3)
66 EchoSubR DESCRIPTOR<EchoSubRLen -1,EchoSubRSeg,,ATCER + D32 + DPL1,>
67 Echo_Sel1 = (EchoSubR - LDT) + TIL + RPL1
68 Echo_Sel3 = (EchoSubR - LDT) + TIL + RPL3
69
70 ;LDT含非门描述符个数
71 LDTNum = ($ - LDT)/(size DESCRIPTOR)
72
73 ;指向过渡代码段内T32Begin点的调用门(DPL = 0)
74 ToT32GateA GATE<low offset T32Begin,T32Code_Sel,0,AT386CGAT,0>
75 ToT32GateA_Sel = (ToT32GateA - LDT) + TIL
76
77 ;指向过渡代码段内T32End点的调用门(DPL = 3)
78 ToT32GateB GATE<low offset T32End,T32Code_Sel,0,AT386CGAT + DPL3,0>
79 ToT32GateB_Sel = (ToT32GateB - LDT) + TIL
80
81 ;指向显示子程序的调用门(DPL = 3)
82 ToEchoGate GATE<low offset EchoSub,Echo_Sel3,0,AT386CGAT + DPL3,0>
83 ToEchoGate_Sel = (ToEchoGate - LDT) + TIL
84
85 ;LDT字节长度
86 LDTLen = $ - LDT
87 LDTSeg ends
88
89 ;------------------------------------------
90 ;演示任务TSS段
91 DemoTSSSeg segment para use16
92 DTSS TASKSS<>
93 db 0ffh ;IO许可位结束标志
94 DemoTSSLen = $ - DemoTSSSeg
95 DemoTSSSeg ends
96
97
98 ;------------------------------------------
99 ;演示任务0级堆栈段(32位段)
100 DemoStack0Seg segment para use32
101 DemoStack0Len = 512
102 db DemoStack0Len dup(0)
103 DemoStack0Seg ends
104
105
106 ;------------------------------------------
107 ;演示任务1级堆栈段(32位段)
108 DemoStack1Seg segment para use32
109 DemoStack1Len = 512
110 db DemoStack1Len dup(0)
111 DemoStack1Seg ends
112
113
114 ;------------------------------------------
115 ;演示任务3级堆栈段(16位段)
116 DemoStack3Seg segment para use16
117 DemoStack3Len = 512
118 db DemoStack3Len dup(0)
119 DemoStack3Seg ends
120
121 ;------------------------------------------
122 ;演示任务显示子程序代码段(32位段,1级)
123 EchoSubRSeg segment para use32
124 message db 'CPL = ',0
125 assume cs:EchoSubRSeg
126 ;显示调用程序的执行特权级
127 EchoSub proc far
128 cld
129 push ebp
130 mov ebp,esp
131 mov ax,Echo_Sel1 ;子程序代码段是可读段,采用RPL = 1的选择子
132 ;mov ax,Echo_Sel3 ;采用RPL = 3的选择子则无法访问数据
133 mov ds,ax
134 mov ax,Video_Sel
135 mov es,ax ;视频缓冲区基地址0h
136 mov edi,0b8000h ;视频缓冲区段从0b8000h开始
137 mov esi,offset message
138 mov ah,17h ;设置显示属性
139 EchoSub1:
140 lodsb
141 or al,al
142 jz EchoSub2
143 stosw ;显示字符串
144 jmp EchoSub1
145 EchoSub2:
146 mov eax,[ebp + 8] ;从堆栈中取出调用程序的选择子
147 and al,3 ;调用程序的CPL在CS的RPL字段
148 add al,'0'
149 mov ah,17h
150 stosw ;显示之
151 pop ebp
152 ;从"1级的32位任务显示子程序代码段"切换到"3级的32位任务演示代码段"
153 retf ;返回
154 EchoSub endp
155
156 EchoSubRLen = $ - EchoSubRSeg
157
158 EchoSubRSeg ends
159
160 ;------------------------------------------
161 ;演示任务的演示代码段(32位段,3级)
162 DemoCodeSeg segment para use32
163 assume cs:DemoCodeSeg
164 DemoBegin:
165 ;显示当前特权级(变换到1级)
166 ;从"3级的32位任务演示代码段"切换到"1级的32位任务显示子程序代码段"
167 CALL32 ToEchoGate_Sel,0
168 ;转到过渡代码段(变换到0级)
169 ;从"3级的32位任务演示代码段"切换到"0级的32位过渡代码段"
170 CALL32 ToT32GateB_Sel,0
171 DemoCodeLen = $ - DemoCodeSeg
172 DemoCodeSeg ends
173
174 ;------------------------------------------
175 ;演示任务的过渡代码段(32位段,0级)
176 T32CodeSeg segment para use32
177 assume cs:T32CodeSeg
178 T32Begin:
179 ;建立0级堆栈
180 mov ax,DemoStack0_Sel
181 mov ss,ax
182 mov esp,DemoStack0Len
183 ;压入3级堆栈指针
184 push dword ptr DemoStack3_Sel
185 push dword ptr DemoStack3Len
186 ;压入入口点
187 push dword ptr DemoCode_Sel
188 push offset DemoBegin
189 ;利用RET实现转3级的演示代码段
190 ;从"0级的32位过渡代码段"切换到"3级的32位任务演示代码段"
191 retf
192 T32End:
193 ;转临时代码段
194 JUMP32 TempCode_Sel,<offset ToReal>
195 T32CodeLen = $ - T32CodeSeg
196 T32CodeSeg ends
197
198 ;------------------------------------------
199 ;临时代码段(16位段,0级)
200 TempCodeSeg segment para use16
201 assume cs:TempCodeSeg
202 Virtual:
203 ;装载TR
204 mov ax,DemoTSS_Sel
205 ltr ax
206 ;装载LDTR
207 mov bx,DemoLDT_Sel
208 lldt bx
209 ;通过调用门转到过渡段
210 ;从"0级的16位临时代码段"切换到"0级的32位过渡代码段"
211 JUMP16 ToT32GateA_Sel,0
212 ToReal:
213 ;准备切换回实模式
214 mov ax,Normal_Sel ;把规范段描述符装载入各数据段寄存器
215 mov ds,ax
216 mov es,ax
217 mov fs,ax
218 mov gs,ax
219 mov ss,ax
220 ;
221 mov eax,cr0
222 and ax,0fffeh
223 mov cr0,eax
224 JUMP16 <seg Real>,<offset Real>
225 TempCodeLen = $ - TempCodeSeg
226 TempCodeSeg ends
227
228 ;------------------------------------------
229 ;实模式下的数据段
230 RDataSeg segment para use16
231 VGDTR PDESC<GDTLen - 1,>
232 SPVAR dw ?
233 SSVAR dw ?
234 RDataSeg ends
235
236 ;------------------------------------------
237 ;实模式下的代码段
238 RCodeSeg segment para use16
239 assume cs:RCodeSeg,ds:RDataSeg
240 start:
241 mov ax,RDataSeg
242 mov ds,ax
243 cld
244 ;初始化GDT
245 call INIT_GDT
246 ;初始化LDT
247 mov ax,LDTSeg
248 mov fs,ax
249 mov si,offset LDT
250 mov cx,LDTNum
251 call INIT_LDT
252 ;初始化TSS
253 call INIT_TSS
254 ;实模式堆栈保护
255 mov SSVAR,ss
256 mov SPVAR,sp
257 ;装载GDTR和切换到保护模式
258 lgdt fword ptr VGDTR
259 cli
260 mov eax,cr0
261 or ax,1
262 mov cr0,eax
263 JUMP16 TempCode_Sel,<offset Virtual>
264 Real:
265 mov ax,RDataSeg
266 mov ds,ax
267 lss sp,dword ptr SPVAR
268 sti
269 mov ax,0700h
270 int 21h
271 mov ax,4c00h
272 int 21h
273 ;------------------------------------------
274 ;初始化全局描述符表的子程序
275 ;(1)把定义时预置的段值转换成32位段基地址并置入描述符内相应字段
276 ;(2)初始化为GDTR准备的伪描述符
277 INIT_GDT proc near
278 push ds
279 mov ax,GDTSeg
280 mov ds,ax
281 mov cx,GDTNum ;初始化描述符的个数
282 mov si,offset EFFGDT ;开始偏移
283 assume si:ptr DESCRIPTOR
284 INITG:
285 mov ax,[si].BaseL ;取出预置的段值
286 movzx eax,ax ;扩展到32位
287 shl eax,4
288 shld edx,eax,16 ;分解到2个16位寄存器
289 mov [si].BaseL,ax ;置入描述符相应字段
290 mov [si].BaseM,dl
291 mov [si].BaseH,dh
292 add si,size DESCRIPTOR ;调整到下一个描述符
293 loop INITG
294 assume si:nothing
295 pop ds
296 ;
297 mov bx,16 ;初始化为GDTR准备的伪描述符
298 mov ax,GDTSeg
299 mul bx
300 mov word ptr VGDTR.Base,ax
301 mov word ptr VGDTR.Base + 2,dx
302 ret
303 INIT_GDT endp
304 ;------------------------------------------
305 ;初始化演示任务局部描述符表的子程序
306 ;把定义时预置的段值转换成32位段基地址并置入描述符内的相应字段
307 ;入口参数:FS:SI = 第一个要初始化的描述符
308 ; CX = 要初始化的描述符个数
309 INIT_LDT proc
310 ILDT:
311 mov ax,fs:[si].DESCRIPTOR.BaseL
312 movzx eax,ax
313 shl eax,4
314 shld edx,eax,16
315 mov fs:[si].DESCRIPTOR.BaseL,ax
316 mov fs:[si].DESCRIPTOR.BaseM,dl
317 mov fs:[si].DESCRIPTOR.BaseH,dh
318 add si,size DESCRIPTOR
319 loop ILDT
320 ret
321 INIT_LDT endp
322 ;------------------------------------------
323 ;初始化TSS段子程序
324 INIT_TSS proc
325 mov ax,DemoTSSSeg
326 mov fs,ax
327 mov si,offset DTSS
328 mov fs:[si].TASKSS.TRLink,0
329 mov fs:[si].TASKSS.TRCR3,0
330 mov fs:[si].TASKSS.TRESP0,DemoStack0Len
331 mov fs:[si].TASKSS.TRSS0,DemoStack0_Sel
332 mov fs:[si].TASKSS.TRESP1,DemoStack1Len
333 mov fs:[si].TASKSS.TRSS1,DemoStack1_Sel
334 mov fs:[si].TASKSS.TRLDT,DemoLDT_Sel
335 ret
336 INIT_TSS endp
337 RCodeSeg ends
338 end start

 

 

3、  源代码说要说明的几个地方

  1)  关于“386scd.asm”在“任务内无特权级变换转移实例”中已经有,这里要注意的是TSS段的构建,其中有一个IO许可位结束标志设置为固定值“0ffh”,且一定要紧紧跟随在“DTSS TASKSS<>”之后。

  2)  对于TSS段的构建,原书并没有采用这里的写法,所以这里多了一个对TSS段初始化的代码,主要设置的有3个内容,R0与R1的堆栈段,CR3的初始值,以及链接字的初始化。

  3)  还是那个毛病,作者在计算界限值时错误(包括门描述符中)。以及其它的一些之前就一直有的小问题(如结构体引用上的问题)。

 

 

4、  输出结果

《80X86汇编语言程序设计教程》十四 任务内特权级变换转移实例 

 

 

5、  测试说明

  1)  新增门描述符与TSS的注意点

    首先主要注意门描述符的构造,也可以看到,同一个段可以使用不同的RPL来构造不同选择子。其次注意将TSS装载入TS的指令“LTR”,它从LDT中取出对应TSS描述符装入TR的告诉缓冲存储器中,由于之前已经说过LDT必须在保护模式先装载,而LTR要访问LDT,所以LTR也必须在保护模式下装载。

  2)  通过段间返回指令实现内层特权级到外层特权级的切换

    从实模式转入保护模式时,特权等级为0,为了测试门描述符,需要切换到外层。这里使用的方式是构造一个门调用时进入内存特权级的环境---构建内层堆栈,然后使用RET语句返回外层,实现了从“0级的32位过渡代码段”切换到“3级的32位任务演示代码段”。这里可以从代码中清晰的看到内层堆栈的结构(无参数)。另外一处是从“1级的32位任务显示子程序代码段”切换到“3级的32位任务演示代码段”,这个地方的堆栈段完全由处理器维护的。

  3)  通过调用门实现特权级转换

    一处是从“3级的32位任务演示代码段”切换到“1级的32位任务显示子程序代码段”,这里由于调用了内层的子程序,所以必须使用调用门来实现,由于使用的门“ToEchoGate”设置的DPL = 3,而选择子中的RPL = 0,满足source.CPL(=3)<=gate.DPL(=3)且source.RPL(=0)<=gate.DPL(=3),可以进入门。在进入门以后,RPL被强制置为0(当然,这里没有被改变),由于“ToEchoGate”门使用的目标段选择子为“Echo_Sel3”,由此目标代码段的DPL = 1,RPL = 3,满足source.CPL(=3) > destination.DPL(=1),因此发生向内层的特权等级切换的转移,同时进行堆栈的切换(从“DemoStack3”切换到“DemoStack1”,由于时间问题,我没有再写代码显示堆栈切换的情况)。

    另一处是从“3级的32位任务演示代码段”切换到“0级的32位过渡代码段”,使用门“ToT32GateB”的DPL = 3,而选择子“T32Code_Sel”的RPL = 0,满足source.CPL(=3)<=gate.DPL(=3)且source.RPL(=0)<=gate.DPL(=3),可以进入门。进入门以后满足source.CPL(=3) > destination.DPL(=0),堆栈切换从“DemoStack3”切换到“DemoStack0”。这是一个有去无回的调用,虽然转入实模式停机。

    这里2处都使用到了调用门进入内层的切换,权限等级发生变化,不能使用JMP语句替代。

  4)  通过调用门实现无特权级变换的转移

    从“0级的16位临时代码段”切换到“0级的32位过渡代码段”,此时source.CPL = gate.DPL = destination.DPL,所以并没有特权级的变换,也没有进行堆栈的切换,这里可以使用段间JMP指令。

  5)  显示子程序对可读代码段中数据的访问

    子程序中在访问输出串“message”时,使用了段RPL = 1的选择子“Echo_Sel1”,由于CPL(=1)<= DPL(=1)而RPL(=1)<=DPL(=1),且代码段是可访问代码段,所以数据可成功访问,如果使用的是RPL = 3的“Echo_Sel3”,由于RPL > DPL,则反问必定引起保护异常。