1、 理论知识参考"《80X86汇编语言程序设计教程》十二 任务状态段、控制门和控制转移",这里的演示任务完成的逻辑功能是:用十六进制数码的ASCII码显示代码段L的段界限值。这里对权限等级的保护检测并不是测试得很明朗,因为所有的代码都是R0级,只有对请求特权级RPL有过变换,对于DPL为3的时候使用RPL为0与RPL为3进行数据访问。此外,重点要关注的就是LGT表的构建了。
2、 完整源代码
1 ;DosTest.Asm
2 ;演示任务内无特权级变换转移
3 ;逻辑功能:用十六进制数码的ASCII码显示代码段L的段界限值
4
5
6 include 386scd.asm ;文件386.scd含有有关结构、宏指令和符号常量定义
7 .386p
8
9 ;------------------------------------------
10 ;全局描述符表GDT
11 GDTSeg segment para use16 'gdt'
12 GDT label byte
13 DUMMY DESCRIPTOR<>
14 ;规范数据段描述符,存在的可读写数据段
15 NORMAL DESCRIPTOR<0ffffh,0,0,ATDW,0>
16 NORMAL_SEL = NORMAL - GDT
17 ;代码段K的描述符,存在的只执行代码段
18 CODEK DESCRIPTOR<0ffffh,,,ATCE,>
19 CODEK_SEL = CODEK - GDT
20 ;局部描述符表段的描述符,指向LDT
21 LDTABLE DESCRIPTOR<LDTLEN - 1,,,ATLDT,>
22 LDT_SEL = LDTABLE - GDT
23 GDTLEN = $ - GDT
24 GDTSeg ends
25 ;------------------------------------------
26 ;演示任务局部描述符表LDT
27 LDTSeg segment para use16 'ldt'
28 LDT label byte
29 ;以下的TIL = 4,段选择子2位为TI位,都加上TIL表示都是在LDT表中
30 ;类似,加上RPL3表示请求特权级等级为3,不加则默认请求特权等级为0
31 ;在DESCRIPTOR.Attributes域加上DPL3也是同一个道理,设置DPL为等级3
32 ;代码段L描述符,存在的只执行代码段
33 CODEL DESCRIPTOR<CODELLEN - 1,CodeLSeg,,ATCE,>
34 CODEL_SEL = (CODEL - LDT) + TIL
35 ;代码段C描述符,存在的只执行代码段
36 CODEC DESCRIPTOR<CODECLEN - 1,CodeCSeg,,ATCE,>
37 CODEC_SEL = (CODEC - LDT) + TIL
38 ;显示缓冲区段描述符,存在的可读写数据段
39 VIDEOBUFF DESCRIPTOR<0ffffh,0,0,0f00h + ATDW,0>
40 VIDEO_SEL = (VIDEOBUFF - LDT) + TIL
41 ;演示任务LDT别名段描述符(DPL = 3),存在的只读数据段
42 ;对于数据段设置DPL为等级3,而段选择子中的RPL设置为0
43 ;由于CPL = 0 <= DPL && RPL = 0 <= DPL,故可以访问
44 TOLDT DESCRIPTOR<LDTLEN - 1,LDTSeg,,ATDR + DPL3,>
45 TOLDT_SEL = (TOLDT - LDT) + TIL
46 ;显示信息缓冲区段描述符(DPL = 3),存在的可读写数据段
47 MDATA DESCRIPTOR<MDATALEN - 1,MDataSeg,,ATDW + DPL3,>
48 MDATA_SEL = (MDATA - LDT) + TIL + RPL3
49 ;堆栈段描述符,存在的已访问可读写数据段
50 STACKS DESCRIPTOR<TOPOFS - 1,StackSeg,,ATDWA,>
51 STACK_SEL = (STACKS - LDT) + TIL
52 ;LDT含描述符个数
53 LDNUM = ($ - LDT)/(size DESCRIPTOR)
54 ;LDT字节长度
55 LDTLEN = $ - LDT
56 LDTSeg ends
57
58 ;------------------------------------------
59 ;显示信息缓冲区段
60 MDataSeg segment para use16 'mdata'
61 message db 'Value = ',0
62 buffer db 80 dup(0)
63 MDATALEN = $ - MDataSeg
64 MDataSeg ends
65
66 ;------------------------------------------
67 ;演示任务堆栈段
68 StackSeg segment para use16 'stack'
69 dw 512 dup(0)
70 TOPOFS = $ - StackSeg
71 StackSeg ends
72
73 ;------------------------------------------
74 ;演示任务代码段C(含子程序D和子程序H)
75 CodeCSeg segment para use16 'codec'
76 assume cs:CodeCSeg
77
78 ;显示信息子程序D
79 ;入口参数:FS:SI指向要显示字符串,以0结尾
80 ; ES:EDI指向显示缓冲区
81 DISPMESS proc far
82 mov ah,7
83 DISP1:
84 mov al,fs:[si]
85 inc si
86 or al,al
87 jz DISP2
88 mov es:[edi],ax
89 inc edi
90 inc edi
91 jmp DISP1
92 DISP2:
93 ret
94 DISPMESS endp
95
96 ;子程序H:把一位十六进制数转换为对应字符的ASCII码
97 HTOASC proc far
98 and al,0fh
99 add al,90h
100 daa
101 adc al,40h
102 daa
103 ret
104 HTOASC endp
105
106 CODECLEN = $ - CodeCSeg
107 CodeCSeg ends
108
109 ;------------------------------------------
110 ;演示任务代码段L
111 CodeLSeg segment para use16 'codel'
112 assume cs:CodeLSeg
113 VIRTUAL2:
114 mov ax,VIDEO_SEL
115 mov es,ax ;设置显示缓冲区指针
116 mov edi,0b80a0h ;直接写屏方式,3号模式下第2行打头位置输出
117 ;
118 mov ax,MDATA_SEL
119 mov fs,ax ;设置提示信息缓冲区指针
120 mov si,offset message
121 ;
122 CALL16 CODEC_SEL,DISPMESS ;显示提示信息
123 ;
124 mov ax,TOLDT_SEL ;把演示任务的LDT别名段描述符选择子装入GS
125 mov gs,ax
126 ;
127 mov dx,gs:CODEL.LimitL ;取代码段L的段界限值
128 mov si,offset buffer ;并转换成对应可显示字符
129 mov cx,4
130 VIR:
131 rol dx,4
132 mov al,dl
133 CALL16 CODEC_SEL,HTOASC ;转换出ASCII码
134 mov fs:[si],al
135 inc si
136 loop VIR
137 mov word ptr fs:[si],'H'
138 ;
139 mov si,offset buffer
140 CALL16 CODEC_SEL,DISPMESS ;显示转换出的字符串
141 ;
142 JUMP16 CODEK_SEL,VIRTUAL3 ;跳转到代码段K
143
144 CODELLEN = $ - CodeLSeg
145 CodeLSeg ends
146
147 ;------------------------------------------
148 ;演示任务代码段K
149 CodeKSeg segment para use16 'codek'
150 assume cs:CodeKSeg
151 VIRTUAL1: ;保护模式执行代码入口
152 mov ax,LDT_SEL
153 LLDT ax ;装载局部描述符表寄存器LDTR
154 ;
155 mov ax,STACK_SEL
156 mov ss,ax ;建立演示任务堆栈
157 mov sp,offset TOPOFS
158 ;
159 JUMP16 CODEL_SEL,VIRTUAL2 ;跳转到代码段L
160 VIRTUAL3:
161 mov ax,NORMAL_SEL ;准备退出保护模式
162 mov es,ax
163 mov fs,ax
164 mov gs,ax
165 mov ss,ax
166 ;
167 mov eax,cr0
168 and eax,0fffffffeh
169 mov cr0,eax ;返回实模式
170 JUMP16 <seg REAL>,<offset REAL>
171 ;jmp far ptr REAL ;这里完全可以取代
172
173 CODEKLEN = $ - CodeKSeg
174 CodeKSeg ends
175
176 ;------------------------------------------
177 ;实模式数据段
178 RDataSeg segment para use16
179 VGDTR PDESC<GDTLEN - 1,> ;GDT伪描述符
180 SPVAR dw ? ;保存实模式下堆栈指针
181 SSVAR dw ?
182 RDataSeg ends
183
184 ;------------------------------------------
185 ;实模式代码段
186 RCodeSeg segment para use16
187 assume cs:RCodeSeg
188 start:
189 assume ds:GDTSeg
190 mov ax,GDTSeg
191 mov ds,ax
192 ;初始化全局描述符表GDT
193 mov bx,16
194 mov ax,CodeKSeg
195 mul bx
196 mov CODEK.BaseL,ax ;设置代码段K基地址
197 mov CODEK.BaseM,dl
198 mov CODEK.BaseH,dh
199 mov ax,LDTSeg ;设置演示任务LDT表基地址
200 mul bx
201 mov LDTABLE.BaseL,ax
202 mov LDTABLE.BaseM,dl
203 mov LDTABLE.BaseH,dh
204 ;设置GDT伪描述符
205 assume ds:RDataSeg
206 mov ax,RDataSeg
207 mov ds,ax
208 mov ax,GDTSeg
209 mul bx
210 mov word ptr VGDTR.Base,ax
211 mov word ptr VGDTR.Base + 2,dx
212 ;初始化演示任务LDT
213 cld
214 call INIT_MLDT
215 ;保存实模式堆栈指针
216 mov SSVAR,ss
217 mov SPVAR,sp
218 ;装载GDTR
219 LGDT fword ptr VGDTR
220 cli
221 ;切换到保护模式
222 mov eax,cr0
223 or eax,1
224 mov cr0,eax
225 JUMP16 <CODEK_SEL>,<offset VIRTUAL1>
226 REAL:
227 ;又回到实模式下
228 lss sp,dword ptr SPVAR ;恢复实模式堆栈指针
229 sti
230 mov ax,0700h ;结束程序
231 int 21h
232 mov ax,4c00h
233 int 21h
234
235 ;------------------------------------------
236 ;初始化演示任务LDT的子程序
237 ;将各描述符中的16位逻辑转20位物理地址
238 INIT_MLDT proc
239 push ds
240 mov ax,LDTSeg
241 mov ds,ax
242 mov cx,LDNUM ;表中描述符个数
243 mov si,offset LDT ;描述符指针
244 INITL:
245 mov ax,[si].DESCRIPTOR.BaseL
246 movzx eax,ax
247 shl eax,4 ;实模式下16位段基址左移4位转物理地址
248 shld edx,eax,16 ;双精度移位指令:edx左移16位,空出的位用eax高16位填补
249 mov [si].DESCRIPTOR.BaseL,ax ;此时dx:ax为物理地址
250 mov [si].DESCRIPTOR.BaseM,dl
251 mov [si].DESCRIPTOR.BaseH,dh
252 add si,size DESCRIPTOR ;指向下一个描述符
253 loop INITL
254 pop ds
255 ret
256 INIT_MLDT endp
257
258 RCodeSeg ends
259 end start
其中,“386scd.asm”源代码:
1 ;386scd.asm
2 ;------------------------------------------
3 ;存储段描述符/系统段描述符结构类型的定义
4 ;
5 ; |m + 7 |m + 6 m + 5|m + 4 m + 3 m + 2|m + 1 m + 0 |
6 ; |Base | Attributes | Segment Base |Segment Limit |
7 ; |31…24 | | 23…0 | 15…0 |
8 ;
9 ;其中段属性Attributes具体内容如下:
10 ;
11 ; m + 6 m + 5
12 ;7 6 5 4 | 3 2 1 0 | 7 | 6 5 | 4 | 3 2 1 0
13 ;G D 0 AVL | Limit(19…16) | P | DPL | DT | TYPE
14 ; X
15 ;
16 ;说明:DT = 1为存储段描述符,DT = 0为系统段描述符(此时X位不用,TYPE = 2为LDT)
17 DESCRIPTOR struct
18 LimitL dw 0 ;段界限(0-15)
19 BaseL dw 0 ;段基地址(0-15)
20 BaseM db 0 ;段基地址(16-23)
21 Attributes dw 0 ;段属性(其中包含高4位段界限)
22 BaseH db 0 ;段基地址(24-31)
23 DESCRIPTOR ends
24 ;------------------------------------------
25 ;门描述符结构类型定义
26 ;
27 ; |m + 7 m + 6 | m + 5 m + 4|m + 3 m + 2|m + 1 m + 0|
28 ; | Offset | Attributes | selector | Offset |
29 ; | 31…16 | | | 15…0 |
30 ;
31 ;其中段属性Attributes具体内容如下:
32 ;
33 ; m + 5 m + 4
34 ;7 | 6 5 | 4 | 3 2 1 0 | 7 6 5 | 4 3 2 1 0
35 ;P | DPL | DT | TYPE | 0 0 0 | Dword Count
36 ;
37 ;说明:DT = 0 时可能为系统段描述符,也可能为各种门:
38 ; TYPE = 5H 为 任务门
39 ; TYPE = CH 为 386调用门
40 ; TYPE = EH 为 386中断门
41 ; TYPE = FH 为 386陷阱门
42 ;
43 GATE struct
44 OffsetL dw 0 ;偏移量(0~15)
45 Selector dw 0 ;选择子
46 Dcount db 0 ;双字计数字段
47 GType db 0 ;门类型
48 OffsetH dw 0 ;偏移量(16~31)
49 GATE ends
50 ;------------------------------------------
51 ;GDT伪描述符结构类型定义(GDT表头地址-用于转载GDTR)
52 PDESC struc
53 Limit dw 0 ;16 bit界限
54 Base dd 0 ;32 bit基地址
55 PDESC ends
56 ;------------------------------------------
57 ;任务状态段TSS结构类型定义
58 TASKSS struct
59 TRLink dw ?,0 ;链接字
60 TRESP0 dd ? ;0级堆栈指针
61 TRSS0 dw ?,0 ;
62 TRESP1 dd ? ;1级堆栈指针
63 TRSS1 dw ?,0 ;
64 TRESP2 dd ? ;2级堆栈指针
65 TRSS2 dw ?,0 ;
66 TRCR3 dd ? ;CR3
67 TEIP dd ? ;EIP
68 TREFLAGES dw ?,? ;EFLAGS
69 TREAX dd ? ;EAX
70 TRECX dd ? ;ECX
71 TREDX dd ? ;EDX
72 TREBX dd ? ;EBX
73 TRESP dd ? ;ESP
74 TREBP dd ? ;EBP
75 TRESI dd ? ;ESI
76 TREDI dd ? ;EDI
77 TRES dw ?,0 ;ES
78 TRCS dw ?,0 ;CS
79 TRSS dw ?,0 ;SS
80 TRDS dw ?,0 ;DS
81 TRFS dw ?,0 ;FS
82 TRGS dw ?,0 ;GS
83 TRLDT dw ?,0 ;LDT
84 TRFLAG dw 0 ;TSS的特别属性字
85 TRTRAP dw 0 ;调试陷阱标志(只用位0)
86 TRIOMAP dw $+2 ;指向I/O许可位图的指针
87 TASKSS ends
88 ;------------------------------------------
89 ;存储段描述符类型值说明
90 ATDR = 90H ;存在的只读数据段
91 ATDW = 92H ;存在的可读写数据段
92 ATDWA = 93H ;存在的已访问可读写数据段
93 ATCE = 98H ;存在的只执行代码段
94 ATCER = 9AH ;存在的可读可执行代码段
95 ATCCO = 9CH ;存在的只执行一致代码段
96 ATCCOR = 9EH ;存在的可执行可读一致代码段
97 ;------------------------------------------
98 ;系统段描述符和门描述符类型值说明
99 ATLDT = 82H ;局部描述符表(LDT)段类型值
100 ATTASKGAT = 85H ;任务门
101 AT386TSS = 89H ;386TSS
102 AT386CGAT = 8CH ;386调用门
103 AT386IGAT = 8EH ;386中断门
104 AT386TGAT = 8FH ;386陷阱门
105 ;------------------------------------------
106 ;DPL和RPL值说明
107 DPL1 = 20H ;DPL = 1
108 DPL2 = 40H ;DPL = 2
109 DPL3 = 60H ;DPL = 3
110 RPL1 = 01H ;RPL = 1
111 RPL2 = 02H ;RPL = 2
112 RPL3 = 03H ;RPL = 3
113 IOPL1 = 1000H ;IOPL = 1
114 IOPL2 = 2000H ;IOPL = 2
115 IOPL3 = 3000H ;IOPL = 3
116 ;------------------------------------------
117 ;DPL和RPL值说明
118 D32 = 4000H ;32位
119 TIL = 04H ;TI = 1(描述符表标志)
120 VMFL = 0002H ;VMF = 1
121 IFL = 0200H ;IF = 1
122 ;------------------------------------------
123 ;32位偏移的段间转移宏指令
124 JUMP32 macro selector,offsetv
125 db 0eah ;操作码
126 dw offsetv ;32位偏移
127 dw 0
128 dw selector ;选择子
129 endm
130 ;------------------------------------------
131 ;32位偏移的段间调用宏指令
132 CALL32 macro selector,offsetv
133 db 09ah ;操作码
134 dw offsetv ;32位偏移
135 dw 0
136 dw selector ;选择子
137 endm
138 ;------------------------------------------
139 ;16位偏移的段间转移宏指令
140 JUMP16 macro selector,offsetv
141 db 0eah ;操作码
142 dw offsetv ;16位偏移
143 dw selector ;段值或者选择子
144 endm
145 ;16位偏移的段间调用宏指令
146 CALL16 macro selector,offsetv
147 db 09ah ;操作码
148 dw offsetv ;16位偏移
149 dw selector ;段值或选择子
150 endm;386scd.asm
3、 关于源码中几处要说明的地方
1) 同样,在计算段界限时,作者与之前有同样的错误,直接采用了“$”来计算。
2) 在“INIT_MLDT”函数内,MASM不支持“[si]. BaseL”,须改为“[si]. DESCRIPTOR .BaseL”
3) 在“386scd.asm”中关于TASKSS的结构定义原书有错误
其它有些源代码理解上的注意点都在注释中已经说明,此外我将输出位置稍做了调整。
4、 输出效果
5、 测试说明
1) 初始化IDT与IDT表的构建须特别关注
2) 程序模式切换步骤与之前的实例大体相同,注意两点:LDTR与GDTR表装载时使用语句的区别;LDTR在装载时要引用GDTR的特性决定了它不能在实模式下被装载。
3) RPL是可以动态调整的,在程序中可修改,这里的JUMP到非一致代码段时进行保护检测时判断条件是CPL = DPL且RPL <= DPL。实际上CPL = DPL = 0总是成立,RPL在代码段跳转时也总是0。CALL类似,只不过多了返回地址压入堆栈操作。
4) RET检测RPL = CPL(及必须同特权级的跳转)以及DPL = CPL,RPL<=DPL。这里对它们的测试其实并不明朗。但是这几个值都没有变动过,在控制跳转时。