1、 理论知识参考"《80X86汇编语言程序设计教程》十二 任务状态段、控制门和控制转移",演示内容:直接通过TSS段的任务切换、通过任务门的任务切换、任务内特权级的变换及参数传递。实现的逻辑功能是:从Temp任务切换到Demo任务以后显示原任务(Temp)的挂起点EIP的值。
2、 源代码
“386scd.asm”不再贴上来。参考"《80X86汇编语言程序设计教程》十三 任务内无特权级变换转移实例",演示代码如下:
1 ;DosTest.Asm 2 ;任务切换实例 3 ;逻辑功能:从Temp任务切换到Demo任务以后显示原任务(Temp)的挂起点EIP的值 4 5 6 include 386scd.asm ;文件386.scd含有有关结构、宏指令和符号常量定义 7 .386p 8 9 ;------------------------------------------ 10 ;全局描述符表GDT 11 GDTSeg segment para use16 12 GDT label byte 13 ;空描述符 14 dummy DESCRIPTOR<> 15 16 ;规范数据段描述符,存在的可读写数据段 17 Normal DESCRIPTOR<0ffffh,0,0,ATDW,0> 18 Normal_Sel = Normal - GDT 19 EFFGDT label byte 20 21 ;演示任务状态段TSS描述符(16位段,DPL = 0,RPL = 0) 22 DemoTSS DESCRIPTOR<DemoTSSLen -1,DemoTSSSeg,,AT386TSS,> 23 DemoTSS_Sel = DemoTSS - GDT 24 25 ;演示任务LDT段描述符(16位段,DPL = 0,RPL = 0) 26 DemoLDTab DESCRIPTOR<DemoLDTLen-1,DemoLDTSeg,,ATLDT,> 27 DemoLDT_Sel = DemoLDTab - GDT 28 29 ;临时任务任务状态TSS描述符(16位段,DPL = 2,RPL = 0) 30 TempTSS DESCRIPTOR<TempTSSLen - 1,TempTSSSeg,,AT386TSS + DPL2,> 31 TempTSS_Sel = TempTSS - GDT 32 33 ;临时任务代码段描述符(16位段,DPL = 0,RPL = 0) 34 TempCode DESCRIPTOR<0ffffh,TempCodeSeg,,ATCE,> 35 TempCode_Sel = TempCode - GDT 36 37 ;子程序代码段描述符(32位段,DPL = 0,RPL = 3) 38 SubR DESCRIPTOR<SubRLen - 1,SubRSeg,,ATCE + D32,> 39 SubR_Sel = SubR - GDT + RPL3 40 41 ;视频缓冲区段描述符,段界限0fffffh(16位段,DPL = 3,RPL = 0) 42 VideoBuff DESCRIPTOR<0ffffh,0,0,0f00h + ATDW + DPL3,0> 43 Video_Sel = VideoBuff - GDT 44 45 GDTNum = ($ - EFFGDT)/(size DESCRIPTOR) 46 47 ;指向演示任务Demo的任务门(DPL = 3,RPL = 0) 48 ;ToDemoT GATE<0,DemoTSS_Sel,0,ATTASKGAT + DPL3,0> 49 ;ToDemoT_Sel = (ToDemoT - GDT) + TIL 50 51 GDTLen = $ - GDT 52 GDTSeg ends 53 54 ;------------------------------------------ 55 ;演示任务局部描述符表LDT 56 DemoLDTSeg segment para use16 57 DemoLDT label byte 58 59 ;0级堆栈段描述符(32位段,DPL = 0,RPL = 0) 60 DemoStack0 DESCRIPTOR<DemoStack0Len - 1,DemoStack0Seg,,ATDW + D32,> 61 DemoStack0_Sel = (DemoStack0 - DemoLDT) + TIL 62 63 ;2级堆栈段描述符(32位段,DPL = 2,RPL = 2) 64 DemoStack2 DESCRIPTOR<DemoStack2Len - 1,DemoStack2Seg,,ATDW + D32 + DPL2,> 65 DemoStack2_Sel = (DemoStack2 - DemoLDT) + TIL + RPL2 66 67 ;演示任务代码段描述符(32位段,DPL = 2,RPL = 2) 68 DemoCode DESCRIPTOR<DemoCodeLen - 1,DemoCodeSeg,,ATCE + D32 + DPL2,> 69 DemoCode_Sel = (DemoCode - DemoLDT) + TIL + RPL2 70 71 ;演示任务数据段描述符(32位段,DPL = 3,RPL = 0) 72 DemoData DESCRIPTOR<DemoDataLen - 1,DemoDataSeg,,ATDW + D32 + DPL3,> 73 DemoData_Sel = (DemoData - DemoLDT) + TIL 74 75 ;把演示任务LDT作为普通数据段描述的描述符(16位段,DPL = 2,RPL = 0) 76 ToDLDT DESCRIPTOR<DemoLDTLen - 1,DemoLDTSeg,,ATDW + DPL2,> 77 ToDLDT_Sel = (ToDLDT - DemoLDT) + TIL 78 79 ;把临时任务TSS作为普通数据段描述的描述符(16位段,DPL = 2,RPL = 0) 80 ToTTSS DESCRIPTOR<TempTSSLen - 1,TempTSSSeg,,ATDW + DPL2,> 81 ToTTSS_Sel = (ToTTSS - DemoLDT) + TIL 82 83 ;LDT含非门描述符个数 84 DemoLDTNum = ($ - DemoLDT)/(size DESCRIPTOR) 85 86 ;指向子程序SubRB的调用门(DPL = 3,RPL = 2) 87 ToSubR GATE<low offset SubRB,SubR_Sel,0,AT386CGAT + DPL3,0> 88 ToSubR_Sel = (ToSubR - DemoLDT) + TIL + RPL2 89 90 ;指向临时任务Temp的任务门(DPL = 3,RPL = 0) 91 ToTempT GATE<0,TempTSS_Sel,0,ATTASKGAT + DPL3,0> 92 ToTempT_Sel = (ToTempT - DemoLDT) + TIL 93 94 ;LDT字节长度 95 DemoLDTLen = $ - DemoLDT 96 DemoLDTSeg ends 97 98 99 ;------------------------------------------ 100 ;演示任务任务状态段(TSS) 101 DemoTSSSeg segment para use16 102 DTSS TASKSS<> 103 db 0ffh ;IO许可位结束标志 104 DemoTSSLen = $ - DemoTSSSeg 105 DemoTSSSeg ends 106 107 108 ;------------------------------------------ 109 ;临时任务的任务状态段(TSS) 110 TempTSSSeg segment para use16 111 TempTask TASKSS<> 112 db 0ffh 113 TempTSSLen = $ - TempTSSSeg 114 TempTSSSeg ends 115 116 ;------------------------------------------ 117 ;演示任务0级堆栈段(32位段) 118 DemoStack0Seg segment para use32 119 DemoStack0Len = 1024 120 db DemoStack0Len dup(0) 121 DemoStack0Seg ends 122 123 124 ;------------------------------------------ 125 ;演示任务2级堆栈段(32位段) 126 DemoStack2Seg segment para use32 127 DemoStack2Len = 512 128 db DemoStack2Len dup(0) 129 DemoStack2Seg ends 130 131 ;------------------------------------------ 132 ;演示任务数据段(32位段) 133 DemoDataSeg segment para use32 134 message db ‘Value = ‘,0 135 DemoDataLen = $ - DemoDataSeg 136 DemoDataSeg ends 137 138 ;------------------------------------------ 139 ;子程序代码段(32位段) 140 SubRSeg segment para use32 141 assume cs:SubRSeg 142 ;从堆栈中取出参数显示 143 SubRB proc far 144 push ebp 145 mov ebp,esp 146 pushad 147 ;从堆栈(0级)中取提示信息串偏移 148 mov eax,[ebp + 12] 149 mov esi,eax 150 mov ah,7 151 jmp short SubR2 152 SubR1: 153 stosw 154 SubR2: 155 lodsb 156 or al,al 157 jnz SubR1 158 ;从堆栈(0级)中取显示值 159 mov edx,[ebp + 16] 160 mov ecx,8 161 SubR3: 162 rol edx,4 163 mov al,dl 164 call HTOASC 165 stosw 166 loop SubR3 167 popad 168 pop ebp 169 ret 8 170 SubRB endp 171 172 HTOASC proc 173 and al,0fh 174 add al,90h 175 daa 176 adc al,40h 177 daa 178 ret 179 HTOASC endp 180 181 SubRLen = $ - SubRSeg 182 183 SubRSeg ends 184 185 ;------------------------------------------ 186 ;演示任务的代码段(32位段) 187 DemoCodeSeg segment para use32 188 assume cs:DemoCodeSeg 189 DemoBegin: 190 ;把要复制的参数个数置入调用门 191 mov fs:ToSubR.Dcount,2 192 ;向堆栈(2级)中压入参数 193 mov dword ptr gs:TempTask.TREIP,offset ToReal 194 push dword ptr gs:TempTask.TREIP 195 push offset message 196 ;通过调用门调用子程序SubRB 197 CALL32 ToSubR_Sel,0 198 ;把指向规范数据段描述符的选择子填入临时任务TSS 199 assume ds:TempTSSSeg 200 push gs 201 pop ds 202 mov ax,Normal_Sel 203 mov TempTask.TRDS,ax 204 mov TempTask.TRES,ax 205 mov TempTask.TRFS,ax 206 mov TempTask.TRGS,ax 207 mov TempTask.TRSS,ax 208 ;通过任务切换到临时任务 209 JUMP32 ToTempT_Sel,0 210 DemoCodeLen = $ - DemoCodeSeg 211 DemoCodeSeg ends 212 213 ;------------------------------------------ 214 ;临时代码段(16位段,0级) 215 TempCodeSeg segment para use16 216 assume cs:TempCodeSeg 217 Virtual: 218 ;装载TR 219 mov bx,TempTSS_Sel 220 ltr bx 221 ;直接切换到演示任务 222 ;JUMP16 ToDemoT_Sel,0 223 JUMP16 DemoTSS_Sel,0 224 ToReal: 225 ;准备切换回实模式 226 clts ;清任务切换标志 227 ; 228 mov eax,cr0 229 and ax,0fffeh 230 mov cr0,eax 231 JUMP16 <seg Real>,<offset Real> 232 TempCodeLen = $ - TempCodeSeg 233 TempCodeSeg ends 234 235 236 ;------------------------------------------ 237 ;实模式下的数据段 238 RDataSeg segment para use16 239 VGDTR PDESC<GDTLen - 1,> 240 SPVAR dw ? 241 SSVAR dw ? 242 RDataSeg ends 243 ;------------------------------------------ 244 ;实模式下的代码段 245 RCodeSeg segment para use16 246 assume cs:RCodeSeg,ds:RDataSeg,es:RDataSeg 247 start: 248 mov ax,RDataSeg 249 mov ds,ax 250 cld 251 ;初始化GDT 252 call INIT_GDT 253 ;初始化演示任务LDT 254 mov ax,DemoLDTSeg 255 mov fs,ax 256 mov si,offset DemoLDT 257 mov cx,DemoLDTNum 258 call INIT_LDT 259 ;初始化TSS 260 call INIT_TSS 261 ;实模式堆栈保护 262 mov SSVAR,ss 263 mov SPVAR,sp 264 ;装载GDTR和切换到保护模式 265 lgdt fword ptr VGDTR 266 cli 267 mov eax,cr0 268 or eax,1 269 mov cr0,eax 270 JUMP16 TempCode_Sel,<offset Virtual> 271 Real: 272 ;又回到实模式 273 mov ax,RDataSeg 274 mov ds,ax 275 lss sp,dword ptr SPVAR 276 sti 277 mov ax,0700h 278 int 21h 279 mov ax,4c00h 280 int 21h 281 ;------------------------------------------ 282 ;初始化全局描述符表的子程序 283 ;(1)把定义时预置的段值转换成32位段基地址并置入描述符内相应字段 284 ;(2)初始化为GDTR准备的伪描述符 285 INIT_GDT proc near 286 push ds 287 mov ax,GDTSeg 288 mov ds,ax 289 mov cx,GDTNum ;初始化描述符的个数 290 mov si,offset EFFGDT ;开始偏移 291 assume si:ptr DESCRIPTOR 292 INITG: 293 mov ax,[si].BaseL ;取出预置的段值 294 movzx eax,ax ;扩展到32位 295 shl eax,4 296 shld edx,eax,16 ;分解到2个16位寄存器 297 mov [si].BaseL,ax ;置入描述符相应字段 298 mov [si].BaseM,dl 299 mov [si].BaseH,dh 300 add si,size DESCRIPTOR ;调整到下一个描述符 301 loop INITG 302 assume si:nothing 303 pop ds 304 ; 305 mov bx,16 ;初始化为GDTR准备的伪描述符 306 mov ax,GDTSeg 307 mul bx 308 mov word ptr VGDTR.Base,ax 309 mov word ptr VGDTR.Base + 2,dx 310 ret 311 INIT_GDT endp 312 ;------------------------------------------ 313 ;初始化演示任务局部描述符表的子程序 314 ;把定义时预置的段值转换成32位段基地址并置入描述符内的相应字段 315 ;入口参数:FS:SI = 第一个要初始化的描述符 316 ; CX = 要初始化的描述符个数 317 INIT_LDT proc 318 assume si:ptr DESCRIPTOR 319 ILDT: 320 mov ax,fs:[si].BaseL 321 movzx eax,ax 322 shl eax,4 323 shld edx,eax,16 324 mov fs:[si].BaseL,ax 325 mov fs:[si].BaseM,dl 326 mov fs:[si].BaseH,dh 327 add si,size DESCRIPTOR 328 loop ILDT 329 assume si:nothing 330 ret 331 INIT_LDT endp 332 ;------------------------------------------ 333 ;初始化TSS段子程序 334 INIT_TSS proc 335 mov ax,DemoTSSSeg 336 mov fs,ax 337 mov si,offset DTSS 338 assume si:ptr TASKSS 339 mov fs:[si].TRLink,0 ;链接字 = 0 340 mov fs:[si].TRESP0,DemoStack0Len ;0级堆栈指针 341 mov fs:[si].TRSS0,DemoStack0_Sel 342 mov fs:[si].TRESP2,DemoStack2Len ;2级堆栈指针 343 mov fs:[si].TRSS2,DemoStack2_Sel 344 mov fs:[si].TRESP,DemoStack2Len ;当前使用2级堆栈 345 mov fs:[si].TRSS,DemoStack2_Sel 346 mov fs:[si].TRCR3,0 ;CR3 = 0 347 mov fs:[si].TREDI,0b8000h ;ES:EDI指向视频输出缓冲区 348 mov fs:[si].TRES,Video_Sel 349 mov fs:[si].TREIP,offset DemoBegin ;CS:EIP指向演示代码段起始位置 350 mov fs:[si].TRCS,DemoCode_Sel 351 mov fs:[si].TRDS,DemoData_Sel ;DS指向演示任务数据段 352 mov fs:[si].TRFS,ToDLDT_Sel ;FS指向演示任务LDT(被当成数据段) 353 mov fs:[si].TRGS,ToTTSS_Sel ;GS指向临时任务TSS(被当成数据段) 354 mov fs:[si].TRLDT,DemoLDT_Sel ;演示任务LDTR 355 assume si:nothing 356 ret 357 INIT_TSS endp 358 RCodeSeg ends 359 end start
3、 源代码说明
依然存在的一些类似问题,要么直接copy这里的代码,要么对比原书去找它们了,不重要。须注意的是在引用结构的时候这里采用了一种新的形式。
4、 输出
5、 测试说明
1) 从Temp任务直接通过TSS切换到Dome任务
LTR指令装载Temp任务的TSS并刷新高速缓冲区,以指示当前任务。Temp任务采用段间JMP指令进行任务切换:CPL = DPL = 0且目标TSS指示的是一个可用任务。这里有个很纠结的地方,切换任务以后,Temp任务的现场的确被保存了,只不过EIP寄存器都被初始化为0,导致系统奔溃:
而且输出如下:
从输出来看,EIP的确被初始化为0了,为什么是没有保存现场而是被初始化为0呢?因为我在JMP前强行给EIP赋值过,测试发现输出还是0。我测试自己电脑的CPU是686,不知道是不是CPU对JMP切换任务已经更换了策略,还是说有什么潜在的问题没有发现,暂时不得而知,总之,我尝试了各种方法,包括使用任务门、修改Temp的TSS任务为忙的TSS,这些尝试都是导致的系统直接奔溃。
2) 从Dome任务通过任务门切换到Temp任务
采用段间JMP,通过任务门切换回Temp任务:source.CPL(=2)<=gate.DPL(=3)且source.RPL(=0)<=gate.RPL(=0)所以成功进入任务门。进入门以后不再对权限等级进行保护检测(可以切换到目标任务的任何权限等级),由于目标任务可用,则直接实行了切换。由于上述毛病的存在,我这这里强势设置了一下EIP,使用的语句是“mov dword ptr gs:TempTask.TREIP,offset ToReal”。
3) 演示任务内特权级变换和堆栈传递参数
采用段间CALL,通过调用门“ToSunR”调用子程序SubR。由于有权限等级的切换,栈也进行了切换。首先压入参数,须注意,压入时须保证CPL<=DPL且RPL<=DPL,栈是一种存储描述符。同时,注意调用门中Dcount字段,这个字段是压入参数的双字数目,这里压入了2个双字,所以设置为2。进入门的保护检测不再赘述。执行子程序时注意堆栈平衡。其它的没什么好说的。
4) 别名技术
在为了把调用门“ToSunR”中的Dcount字段设置为2时,使用了数据段描述符“ToDLDT”描述门所在演示任务的LDT段;此外在修改Temp任务的TSS时,也将其视普通的数据段。对同一内存段使用不同的解释方式,就是别名技术,这个不见得多么高深。