1、 这是我这本书调得最失败的一个实例,而且问题都是超出了这本书能教会我的范畴。作者对调试环境几乎只字不提,这让我有点费解。原作者使用TASM的编译代码,而我使用的MASM加虚拟机进行测试,不知道这两样东西哪里有问题还是源代码要做哪些修正。理论知识参考"《80X86汇编语言程序设计教程》二十二 分页管理机制与虚拟8086模式"。
2、 进入和离开V86模式实例:各2种方式进入和离开V86模式、V86模式下8086程序调用实模式软中断处理程序。逻辑功能:以驻留方式结束程序。具体步骤,从Temp任务通过任务门切换进V86任务(为V86模式),在V86模式下显示进入V86的提示信息,随后V86任务退出并驻留。在驻留期间,可以运行其它程序,可能发生通用保护故障,如果有,那么驻留V86任务被激活,提示通用保护故障信息,并终止引发故障的程序。此外,如果驻留期间,有int 0ffh功能调用,那么驻留V86任务也将激活,进入INTFF任务(保护模式下),它利用任务门进入Temp任务,开始返回实模式,并退出演示程序,整个测试终止。
3、 源代码
“386scd.asm”不再贴上来。参考"《80X86汇编语言程序设计教程》十三 任务内无特权级变换转移实例",演示代码如下:
1 ;DosTest.Asm 2 ;进入和离开V86模式实例:各2种方式进入和离开V86模式、V86模式下爱8086程序调用实模式软中断处理程序 3 ;逻辑功能:以驻留方式结束程序 4 5 6 include 386scd.asm ;文件386.scd含有有关结构、宏指令和符号常量定义 7 .386p 8 9 ;测试输出宏,像V86Data_Sel的Des单元写入Sur的十六进制数码的ASCII码 10 WriteReg macro Des,Sur,DSeg 11 local WriteREG 12 push edi 13 push eax 14 push edx 15 push ecx 16 push es 17 mov di,offset Des 18 mov ax,DSeg 19 mov es,ax 20 mov dx,Sur 21 and edx,0ffffh 22 mov ecx,8 23 WriteREG: 24 rol edx,4 25 mov al,dl 26 and al,0fh 27 add al,90h 28 daa 29 adc al,40h 30 daa 31 stosb 32 loop WriteREG 33 pop es 34 pop ecx 35 pop edx 36 pop eax 37 pop edi 38 endm 39 ;显示寄存器内容的宏 40 ShowReg macro SSeg,DSeg 41 local Next 42 push ds 43 push si 44 push es 45 push di 46 push cx 47 mov ax,SSeg 48 mov ds,ax 49 mov si,offset TestMess 50 mov ax,DSeg 51 mov es,ax 52 mov di,0 53 mov ah,17h 54 mov cx,TestMessLen 55 cld 56 Next: 57 lodsb 58 stosw 59 loop Next 60 pop cx 61 pop di 62 pop es 63 pop si 64 pop ds 65 endm 66 67 ;------------------------------------------ 68 ;全局描述符表GDT 69 GDTSeg segment para use16 70 GDT label byte 71 ;空描述符 72 dummy DESCRIPTOR<> 73 74 ;规范数据段描述符,存在的可读写数据段 75 Normal DESCRIPTOR<0ffffh,0,0,ATDW,0> 76 Normal_Sel = Normal - GDT 77 78 ;以下描述符需要初始化 79 EFFGDT label byte 80 81 ;V86任务TSS段描述符 82 V86TSS DESCRIPTOR<V86TSSLen - 1,V86TSSSeg,,AT386TSS,> 83 V86TSS_Sel = V86TSS - GDT 84 85 ;V86任务LDT表描述符 86 V86LDT DESCRIPTOR<V86LDTLen - 1,V86LDTSeg,,ATLDT,> 87 V86LDT_Sel = V86LDT - GDT 88 89 ;INTFF任务TSS段描述符 90 INTFFTSS DESCRIPTOR<INTFFTSSLen - 1,INTFFTSSSeg,,AT386TSS,> 91 INTFFTSS_Sel = INTFFTSS - GDT 92 93 ;INTFF任务LDT表描述符 94 INTFFLDT DESCRIPTOR<INTFFLDTLen - 1,INTFFLDTSeg,,ATLDT,> 95 INTFFLDT_Sel = INTFFLDT - GDT 96 97 ;临时任务TSS段描述符 98 TempTSS DESCRIPTOR<TempTSSLen - 1,TempTSSSeg,,AT386TSS,> 99 TempTSS_Sel = TempTSS - GDT 100 101 ;临时代码段描述符 102 TempCode DESCRIPTOR<0ffffh,TempCodeSeg,,ATCE,> 103 TempCode_Sel = TempCode - GDT 104 105 ;显示缓存区描述符 106 VideoBuff DESCRIPTOR<80 * 25 * 2 - 1,0b800h,,ATDW,> 107 VideoBuff_Sel = VideoBuff - GDT 108 109 110 ;测试显示缓存区描述符 111 TestVideoBuff DESCRIPTOR<80 * 25 * 2 - 1,0b80ah,,ATDW,> 112 TestVideoBuff_Sel = TestVideoBuff - GDT 113 114 ;GDT中需要初始化基地址的描述符个数 115 GDTNum = ($ - EFFGDT)/(size DESCRIPTOR) 116 117 TempGate DESCRIPTOR<0,TempTSS_Sel,0,ATTASKGAT + DPL3,0> 118 TempGate_Sel = TempGate - GDT 119 120 ;GDT段长度 121 GDTLen = $ - GDT 122 GDTSeg ends 123 124 ;------------------------------------------ 125 ;V86任务使用的中断描述符表IDT 126 IDTSeg segment para use16 127 IDT label byte 128 129 ;从0~12号的13个中断/异常的中断门描述符 130 rept 13 131 GATE<0,TPCode_Sel,0,AT386IGAT + DPL3,0> 132 endm 133 134 ;对应13号通用保护异常的陷阱门描述符 135 GATE<offset GPBegin,GPCode_Sel,0,AT386TGAT + DPL3,0> 136 137 ;从14~254号的240个中断/异常的中断门描述符 138 rept 256 - 1 - 14 139 GATE<0,TPCode_Sel,0,AT386IGAT + DPL3,0> 140 endm 141 142 ;对应255(0ffh)号中断的任务门描述符 143 GATE<0,INTFFTSS_Sel,0,ATTASKGAT + DPL3,0> 144 145 ;中断描述符表长度 146 IDTLen = $ - IDT 147 IDTSeg ends 148 149 ;------------------------------------------ 150 ;INTFF任务状态段(TSS) 151 INTFFTSSSeg segment para use16 152 TASKSS<> 153 db 0ffh ;IO许可位结束标志 154 INTFFTSSLen = $ - INTFFTSSSeg 155 INTFFTSSSeg ends 156 157 ;------------------------------------------ 158 ;INTFF任务LDT表段 159 INTFFLDTSeg segment para use16 160 FLDT label byte 161 162 ;0级堆栈段描述符 163 INTFFStack0 DESCRIPTOR<INTFFStack0Len - 1,INTFFStack0Seg,,ATDWA,> 164 INTFFStack0_Sel = (INTFFStack0 - FLDT) + TIL 165 166 ;代码段描述符 167 INTFFCode DESCRIPTOR<INTFFCodeLen - 1,INTFFCodeSeg,,ATCER,> 168 INTFFCode_Sel = (INTFFCode - FLDT) + TIL 169 170 ;TempTSS别名技术 171 TempTSSD DESCRIPTOR<TempTSSLen -1 ,TempTSSSeg,,ATDW,> 172 TempTSSD_Sel = TempTSSD - FLDT 173 174 ;该LDT中需要初始化基地址描述符个数 175 INTFFLDTNum = ($ - FLDT)/(size DESCRIPTOR) 176 INTFFLDTLen = $ - FLDT 177 INTFFLDTSeg ends 178 179 ;------------------------------------------ 180 ;INTFF任务0级堆栈段 181 INTFFStack0Seg segment para use16 182 INTFFStack0Len = 512 183 db INTFFStack0Len dup(0) 184 INTFFStack0Seg ends 185 186 ;------------------------------------------ 187 ;INTFF任务代码段 188 INTFFCodeSeg segment para use16 189 INTFFMess db ‘Return to real mode.‘ 190 INTFFMessLen = $ - INTFFMess 191 assume cs:INTFFCodeSeg 192 INTFFBegin: 193 mov si,offset GPErrMess 194 mov ax,VideoBuff_Sel 195 mov es,ax ;置显示缓冲区选择子 196 mov di,0 ;从屏幕左上角开始显示 197 mov ah,17h ;置显示属性 198 mov cx,INTFFMessLen ;置提示信息长度 199 cld 200 INext: 201 mov al,cs:[si] ;从段码段取显示信息 202 inc si 203 stosw ;显示返回实模式的提示信息 204 loop INext 205 ; 206 assume si:ptr TASKSS 207 mov si,0 208 mov ax,TempTSSD_Sel 209 mov fs,ax 210 mov fs:[si].TRLink,0 ;链接字 = 0 211 mov fs:[si].TRCR3,0 ;CR3 = 0 212 mov fs:[si].TREFLAGS,0 ;EFLAGS 213 mov fs:[si].TREFLAGS + 2,0 214 mov fs:[si].TRCS,TempCode_Sel ;CS 215 mov fs:[si].TREIP,offset ToReal ;EIP 216 mov fs:[si].TRLDT,0 ;LDT(临时任务不使用LDT) 217 assume si:nothing 218 ;JUMP16 TempTSS_Sel,0 ;切换到临时任务 219 JUMP16 TempGate,0 220 INTFFCodeLen = $ - INTFFCodeSeg 221 INTFFCodeSeg ends 222 223 ;------------------------------------------ 224 ;V86任务状态段(TSS) 225 V86TSSSeg segment para use16 226 TASKSS<> 227 db 4000h/8 dup(0) ;IO许可位图 228 db 0ffh ;IO许可位结束标志 229 V86TSSLen = $ - V86TSSSeg 230 V86TSSSeg ends 231 232 ;------------------------------------------ 233 ;V86任务LDT表段 234 V86LDTSeg segment para use16 235 VLDT label byte 236 237 ;V86任务线性地址空间中最低端1M字节段的描述符 238 ;16位可读写数据段,粒度4K,界限0fffffh,基址00000h 239 VallMem DESCRIPTOR<0ffffh,0,,ATDWA + 8f00h,> 240 VallMem_Sel = (VallMem - VLDT) + TIL 241 242 ;V86任务0级堆栈段描述符 243 V86Stack0 DESCRIPTOR<V86Stack0Len - 1,V86Stack0Seg,,ATDWA,> 244 V86Stack0_Sel = (V86Stack0 - VLDT) + TIL 245 246 ;V86任务数据段描述符 247 V86Data DESCRIPTOR<V86DataLen - 1,V86DataSeg,,ATDW,> 248 V86Data_Sel = (V86Data - VLDT) + TIL 249 250 ;V86中断/异常处理程序代码段描述符 251 TPCode DESCRIPTOR<TPCodeLen - 1,TPCodeSeg,,ATCE,> 252 TPCode_Sel = (TPCode- VLDT) + TIL 253 254 ;V86通用保护异常处理程序代码段描述符 255 GPCode DESCRIPTOR<GPCodeLen - 1,GPCodeSeg,,ATCE,> 256 GPCode_Sel = (GPCode- VLDT) + TIL 257 258 ;该LDT中需要初始化基地址描述符个数 259 V86LDTNum = ($ - VLDT)/(size DESCRIPTOR) 260 V86LDTLen = $ - VLDT 261 V86LDTSeg ends 262 263 ;------------------------------------------ 264 ;V86任务0级堆栈段 265 V86Stack0Seg segment para use16 266 V86Stack0Len = 512 267 db V86Stack0Len dup(0) 268 V86Stack0Seg ends 269 270 ;------------------------------------------ 271 ;V86任务3级堆栈段 272 V86Stack3Seg segment para use16 273 V86Stack3Len = 1024 274 db V86Stack3Len dup(0) 275 V86Stack3Seg ends 276 277 ;------------------------------------------ 278 ;V86数据段 279 V86DataSeg segment para use16 280 GPErrMess db ‘---General Protection Error!(CS:EIP = ‘ 281 CSVar db 8 dup(30h) 282 db ‘:‘ 283 EIPVar db 8 dup(30h) 284 db ‘)---‘ 285 GPErrMessLen = $ - GPErrMess 286 TestMess db ‘Stack Values :‘ 287 db (80 - ($ - TestMess)) dup(‘ ‘) 288 IpL db ‘IpVar([bp + 0 + Pof]) = ‘ 289 IpVar db 8 dup(30h) 290 db (80 - ($ - IpL)) dup(‘ ‘) 291 CsL db ‘CsVar([bp + 4 + Pof]) = ‘ 292 CsVar db 8 dup(30h) 293 db (80 - ($ - CsL)) dup(‘ ‘) 294 FlagL db ‘FlagVar([bp + 8 + Pof]) = ‘ 295 FlagVar db 8 dup(30h) 296 db (80 - ($ - FlagL)) dup(‘ ‘) 297 SpL db ‘SpVar([bp + 12 + Pof]) = ‘ 298 SpVar db 8 dup(30h) 299 db (80 - ($ - SpL)) dup(‘ ‘) 300 SsL db ‘SsVar([bp + 16 + Pof]) = ‘ 301 SsVar db 8 dup(30h) 302 db (80 - ($ - SsL)) dup(‘ ‘) 303 EsL db ‘EsVar([bp + 20 + Pof]) = ‘ 304 EsVar db 8 dup(30h) 305 db (80 - ($ - EsL)) dup(‘ ‘) 306 DsL db ‘DsVar([bp + 24 + Pof]) = ‘ 307 DsVar db 8 dup(30h) 308 db (80 - ($ - DsL)) dup(‘ ‘) 309 FsL db ‘FsVar([bp + 28 + Pof]) = ‘ 310 FsVar db 8 dup(30h) 311 db (80 - ($ - FsL)) dup(‘ ‘) 312 GsL db ‘GsVar([bp + 32 + Pof]) = ‘ 313 GsVar db 8 dup(30h) 314 db (80 - ($ - GsL)) dup(‘ ‘) 315 TestMessLen = $ - TestMess 316 V86DataLen = $ - V86DataSeg 317 V86DataSeg ends 318 319 ;------------------------------------------ 320 ;定义部分代表堆栈单元的符号 321 Pof equ 4 ;偏移,保存BP前有两次字push 322 Pip equ <word ptr [bp + 0 + Pof]> 323 Pcs equ <word ptr [bp + 4 + Pof]> 324 Pflag equ <word ptr [bp + 8 + Pof]> 325 Psp equ <word ptr [bp + 12 + Pof]> 326 Pss equ <word ptr [bp + 16 + Pof]> 327 Pes equ <word ptr [bp + 20 + Pof]> 328 Pds equ <word ptr [bp + 24 + Pof]> 329 Pfs equ <word ptr [bp + 28 + Pof]> 330 Pgs equ <word ptr [bp + 32 + Pof]> 331 332 ;------------------------------------------ 333 ;V86任务下的中断/异常处理程序代码段 334 TPCodeSeg segment para use16 335 assume cs:TPCodeSeg 336 TPBegin: 337 Count = 0 338 rept 256 ;对应256个入口 339 if Count eq 21h 340 ENT21H label byte;在第21H项处定义标号ENT21H 341 endif 342 push bp ;esp = esp -2 343 mov bp,Count ;置中断向量号到BP 344 jmp PROCESS ;都转统一的处理程序 345 Count = Count + 1 346 endm 347 PROCESS: 348 push bp ;保存BP,esp = esp - 2 349 mov bp,sp ;堆栈指针送BP 350 push eax 351 push ebx ;保存EAX、EBX 352 push ecx 353 ;测试:显示堆栈内容 354 ;写入各值 355 WriteReg IpVar,Pip,V86Data_Sel 356 WriteReg CsVar,Pcs,V86Data_Sel 357 WriteReg FlagVar,Pflag,V86Data_Sel 358 WriteReg SpVar,Psp,V86Data_Sel 359 WriteReg SsVar,Pss,V86Data_Sel 360 WriteReg EsVar,Pes,V86Data_Sel 361 WriteReg DsVar,Pds,V86Data_Sel 362 WriteReg FsVar,Pfs,V86Data_Sel 363 WriteReg GsVar,Pgs,V86Data_Sel 364 ; 365 ;显示 366 ShowReg V86Data_Sel,TestVideoBuff_Sel 367 ;jmp $ 368 ; 369 ;(1)在V86堆栈顶形成返回点的现场 370 ;装载描述符最低1M字节线性地址空间的描述符选择子 371 mov ax,VallMem_Sel 372 mov ds,ax 373 ;修改在V86任务0级堆栈中保存的3级堆栈指针 374 ;减3个字,即在V86模式下的栈顶空出3个字 375 xor eax,eax 376 mov ax,Psp 377 sub ax,3 * 2 378 mov Psp,ax 379 xor ebx,ebx 380 ;使EBX指向V86堆栈顶 381 mov bx,Pss 382 shl ebx,4 383 add ebx,eax 384 ;把保存在0级堆栈中的返回地址的偏移部分送V86堆栈 385 ;V86下堆栈每次push时esp值减2 386 mov ax,Pip 387 mov [ebx],ax 388 ;段值部分送V86堆栈 389 mov ax,Pcs 390 mov [ebx + 2],ax 391 ;标志值送V86堆栈 392 mov ax,Pflag 393 mov [ebx + 4],ax 394 ;(2)用对应的中断向量值代替返回地址 395 mov bx,[bp] ;取中断号 396 shl bx,2 ;乘4 397 mov ax,[bx] ;取实模式下对应中断向量的偏移 398 mov Pip,ax ;代替0级堆栈中的EIP 399 mov ax,[bx + 2] ;取实模式下对应中断向量号的段值 400 mov Pcs,ax ;代替0级堆栈中的CS 401 ; 402 pop ecx 403 pop ebx ;恢复EBX、EAX等 404 pop eax 405 pop bp 406 pop bp 407 ;(3)从保护模式返回V86模式 408 ;先转入对应中断处理程序,再返回中断发生处 409 ;jmp $ 410 iretd 411 TPCodeLen = $ - TPCodeSeg 412 TPCodeSeg ends 413 414 ;------------------------------------------ 415 ;V86任务下的通用保护故障异常处理程序代码段 416 GPCodeSeg segment para use16 417 assume cs:GPCodeSeg 418 GPBegin: 419 ;废除堆栈中的错处代码 420 add esp,4 421 ;写入CS:EIP 422 mov di,offset EIPVar 423 mov ax,V86Data_Sel 424 mov es,ax 425 mov edx,[esp] 426 call WriteEDX 427 ; 428 mov di,offset CSVar 429 mov ax,V86Data_Sel 430 mov es,ax 431 mov edx,[esp + 4] 432 call WriteEDX 433 ;显示 434 mov ax,V86Data_Sel 435 mov ds,ax ;装载V86任务的数据段 436 mov si,offset GPErrMess 437 mov ax,VideoBuff_Sel 438 mov es,ax 439 mov di,0 440 mov ah,17h ;显示属性值 441 mov cx,GPErrMessLen 442 cld 443 GNext: 444 lodsb 445 stosw ;显示发生通用保护异常的提示信息 446 loop GNext 447 ;利用DOS的21H功能调用终止引起该异常的程序 448 ;jmp $ 449 mov ax,4c01h 450 JUMP16 TPCode_Sel,ENT21H ;转21H号中断处理程序 451 452 ;写入EDX内容 453 WriteEDX proc 454 mov ecx,8 455 WriteEDX1: 456 rol edx,4 457 mov al,dl 458 call HTOASC 459 stosb 460 loop WriteEDX1 461 ret 462 WriteEDX endp 463 464 ;把AL低4位的十六进制数转换成对应的ASCII码,保存在AL中 465 HTOASC proc 466 and al,0fh 467 add al,90h 468 daa 469 adc al,40h 470 daa 471 ret 472 HTOASC endp 473 474 GPCodeLen = $ - GPCodeSeg 475 GPCodeSeg ends 476 477 ;------------------------------------------ 478 ;V86模式执行的8086程序段 479 V86CodeSeg segment para use16 480 Message db ‘V86 is OK.‘,0dh,0ah,24h 481 assume cs:V86CodeSeg,ds:V86CodeSeg 482 V86Begin: 483 int 0ffh 484 jmp $ 485 ;测试:显示当前寄存器 486 pushf 487 pop ax 488 WriteReg FlagVar,ax,V86DataSeg 489 WriteReg IpVar,<offset IpV>,V86DataSeg 490 WriteReg CsVar,cs,V86DataSeg 491 WriteReg SpVar,sp,V86DataSeg 492 WriteReg SsVar,ss,V86DataSeg 493 WriteReg EsVar,es,V86DataSeg 494 WriteReg DsVar,ds,V86DataSeg 495 WriteReg FsVar,fs,V86DataSeg 496 WriteReg GsVar,gs,V86DataSeg 497 ;显示 498 ShowReg V86DataSeg,0b80ah 499 mov cx,0ffffh 500 VSleep: 501 push cx 502 loop $ 503 pop cx 504 loop VSleep 505 ;处于V86模式 506 mov ah,9 ;显示进入V86模式的信息 507 mov dx,offset Message 508 IpV: 509 int 21h 510 ;jmp $ 511 ;驻留内存方式返回DOS 512 mov ax,RCodeSeg 513 sub ax,GDTSeg ;计算驻留的长度 514 mov dx,(offset TSRLine) + 16 515 shr dx,4 ;以"节"位单位 516 add dx,ax 517 add dx,10h ;含PSP的节数 518 mov ax,3100h 519 int 21h ;退出并驻留 520 V86CodeSeg ends 521 522 ;------------------------------------------ 523 ;临时任务状态段(TSS) 524 TempTSSSeg segment para use16 525 TASKSS<> 526 db 0ffh ;IO许可位结束标志 527 TempTSSLen = $ - TempTSSSeg 528 TempTSSSeg ends 529 530 ;------------------------------------------ 531 ;临时任务代码段 532 TempCodeSeg segment para use16 533 assume cs:TempCodeSeg 534 Virtual: 535 ;进入保护模式后的入口点 536 mov ax,TempTSS_Sel 537 ltr ax ;加载TR指向临时任务TSS 538 mov ax,Normal_Sel ;准备切换到V86任务 539 mov ds,ax ;给各段寄存器赋适当的选择子 540 mov es,ax 541 mov fs,ax 542 mov gs,ax 543 mov ss,ax 544 JUMP16 V86TSS_Sel,0 ;转V86任务(V86模式) 545 ;从INTFF任务回到临时任务的入口点 546 ToReal: 547 clts 548 mov ax,Normal_Sel ;准备切换到V86任务 549 mov ds,ax ;给各段寄存器赋适当的选择子 550 mov es,ax 551 mov fs,ax 552 mov gs,ax 553 mov ss,ax 554 mov eax,cr0 555 and eax,0fffffffeh 556 mov cr0,eax ;返回实模式 557 JUMP16 <seg Real>,<offset Real> 558 TempCodeSeg ends 559 560 561 ;------------------------------------------ 562 ;实模式下的初始化代码和数据 563 RCodeSeg segment para use16 564 VGDTR PDESC<GDTLen - 1,> ;伪GDTR 565 VIDTR PDESC<IDTLen - 1,> ;伪IDTR 566 NORVIDTR PDESC<3ffh,0> ;保存实模式下的IDTR 567 SPVar dw ? ;保存实模式下的堆栈 568 SSVar dw ? 569 assume cs:RCodeSeg,ds:RCodeSeg 570 start: 571 mov ax,RCodeSeg 572 mov ds,ax 573 cld 574 ;初始化GDT 575 call INIT_GDT 576 ;初始化IDT 577 call INIT_IDT 578 ;初始化V86任务 579 mov ax,V86LDTSeg 580 mov fs,ax 581 mov cx,V86LDTNum 582 mov si,offset VLDT 583 call INIT_LDT 584 ;初始化INTFF任务LDT 585 mov ax,INTFFLDTSeg 586 mov fs,ax 587 mov si,offset FLDT 588 mov cx,INTFFLDTNum 589 call INIT_LDT 590 ;初始化TSS 591 call INIT_TSS 592 ;实模式堆栈保护 593 mov SSVar,ss 594 mov SPVar,sp 595 ;装载GDTR和切换到保护模式 596 lgdt fword ptr VGDTR 597 sidt NORVIDTR 598 cli 599 lidt fword ptr VIDTR 600 mov eax,cr0 601 or eax,1 602 mov cr0,eax 603 JUMP16 <TempCode_Sel>,<offset Virtual> 604 Real: 605 ;又回到实模式 606 mov ax,cx 607 mov ds,ax 608 lss sp,dword ptr SPVar 609 lidt NORVIDTR 610 sti 611 mov ax,4c00h 612 int 21h 613 TSRLine label byte 614 ;------------------------------------------ 615 ;初始化全局描述符表的子程序 616 ;(1)把定义时预置的段值转换成32位段基地址并置入描述符内相应字段 617 ;(2)初始化为GDTR准备的伪描述符 618 INIT_GDT proc near 619 push ds 620 mov ax,GDTSeg 621 mov ds,ax 622 mov cx,GDTNum ;初始化描述符的个数 623 mov si,offset EFFGDT ;开始偏移 624 assume si:ptr DESCRIPTOR 625 INITG: 626 mov ax,[si].BaseL ;取出预置的段值 627 movzx eax,ax ;扩展到32位 628 shl eax,4 629 shld edx,eax,16 ;分解到2个16位寄存器 630 mov [si].BaseL,ax ;置入描述符相应字段 631 mov [si].BaseM,dl 632 mov [si].BaseH,dh 633 add si,size DESCRIPTOR ;调整到下一个描述符 634 loop INITG 635 assume si:nothing 636 pop ds 637 ; 638 mov bx,16 ;初始化为GDTR准备的伪描述符 639 mov ax,GDTSeg 640 mul bx 641 mov word ptr VGDTR.Base,ax 642 mov word ptr VGDTR.Base + 2,dx 643 ret 644 INIT_GDT endp 645 646 ;------------------------------------------ 647 ;初始化IDTR伪描述符子程序 648 INIT_IDT proc 649 push ds 650 mov ax,IDTSeg 651 mov ds,ax 652 mov cx,256 - 1 ;对0FFH号特殊处理 653 mov si,offset IDT 654 mov ax,offset TPBegin 655 IDT1: 656 cmp cx,256 - 1 - 13 657 jz IDT2 ;对13号特殊处理 658 mov [si],ax 659 IDT2: 660 add si,8 ;每个门描述符8个字节 661 add ax,7 ;处理程序开始部分长7个字节 662 loop IDT1 663 pop ds 664 ; 665 mov bx,16 666 mov ax,IDTSeg ;设置IDTR 667 mul bx 668 mov word ptr VIDTR.Base,ax 669 mov word ptr VIDTR.Base + 2,dx 670 ret 671 INIT_IDT endp 672 673 ;------------------------------------------ 674 ;初始化演示任务局部描述符表的子程序 675 ;把定义时预置的段值转换成32位段基地址并置入描述符内的相应字段 676 ;入口参数:FS:SI = 第一个要初始化的描述符 677 ; CX = 要初始化的描述符个数 678 INIT_LDT proc 679 assume si:ptr DESCRIPTOR 680 ILDT: 681 mov ax,fs:[si].BaseL 682 movzx eax,ax 683 shl eax,4 684 shld edx,eax,16 685 mov fs:[si].BaseL,ax 686 mov fs:[si].BaseM,dl 687 mov fs:[si].BaseH,dh 688 add si,size DESCRIPTOR 689 loop ILDT 690 assume si:nothing 691 ret 692 INIT_LDT endp 693 ;------------------------------------------ 694 ;初始化TSS段子程序 695 INIT_TSS proc 696 assume si:ptr TASKSS 697 ;初始化INTFF任务的TSS段 698 mov ax,INTFFTSSSeg 699 mov fs,ax 700 mov si,0 701 mov fs:[si].TRLink,0 ;链接字 = 0 702 mov fs:[si].TRCR3,0 ;CR3 = 0 703 mov fs:[si].TREFLAGS,0 ;EFLAGS 704 mov fs:[si].TREFLAGS + 2,0 705 mov fs:[si].TRCS,INTFFCode_Sel ;CS 706 mov fs:[si].TREIP,INTFFBegin ;EIP 707 mov fs:[si].TRSS,INTFFStack0_Sel ;SS 708 mov fs:[si].TRESP,INTFFStack0Len ;ESP 709 mov fs:[si].TRES,Normal_Sel ;ES 710 mov fs:[si].TRDS,Normal_Sel ;DS 711 mov fs:[si].TRFS,Normal_Sel ;FS 712 mov fs:[si].TRGS,Normal_Sel ;GS 713 mov fs:[si].TRLDT,INTFFLDT_Sel ;LDT 714 ;初始化V86任务的TSS段 715 mov ax,V86TSSSeg 716 mov fs,ax 717 mov si,0 718 mov fs:[si].TRLink,0 ;链接字 = 0 719 mov fs:[si].TRCR3,0 ;CR3 = 0 720 mov fs:[si].TREFLAGS,IOPL3 ;EFLAGS(IO特权等级3,VM = 1) 721 mov fs:[si].TREFLAGS + 2,VMFL 722 mov fs:[si].TRSS0,V86Stack0_Sel ;SS0 723 mov fs:[si].TRESP0,V86Stack0Len ;ESP0 724 mov fs:[si].TRCS,V86CodeSeg ;CS 725 mov fs:[si].TREIP,V86Begin ;EIP 726 mov fs:[si].TRSS,V86Stack3Seg ;SS 727 mov fs:[si].TRESP,V86Stack3Len ;ESP 728 mov fs:[si].TRES,V86CodeSeg ;ES(V86方式下的段值) 729 mov fs:[si].TRDS,V86CodeSeg ;DS 730 mov fs:[si].TRFS,V86CodeSeg ;FS 731 mov fs:[si].TRGS,V86CodeSeg ;GS 732 mov fs:[si].TRLDT,V86LDT_Sel ;LDT 733 ;临时任务的TSS段 734 mov ax,TempTSSSeg 735 mov fs,ax 736 mov si,0 737 mov fs:[si].TRLink,0 ;链接字 = 0 738 mov fs:[si].TRCR3,0 ;CR3 = 0 739 mov fs:[si].TREFLAGS,0 ;EFLAGS 740 mov fs:[si].TREFLAGS + 2,0 741 mov fs:[si].TRCS,TempCode_Sel ;CS 742 mov fs:[si].TREIP,0 ;EIP 743 mov fs:[si].TRLDT,0 ;LDT(临时任务不使用LDT) 744 assume si:nothing 745 ret 746 INIT_TSS endp 747 748 RCodeSeg ends 749 end start
4、 关于源代码的说明
1) 同样,段长度计算存在问题,TASM这么写的么?
2) 对源代码的修整比较大,自行对比,另外,源代码在“TPcodeSeg”中堆栈的描述并不清晰,所以我改写了常量的定义。
3) IDT的构造时,采用了重复汇编,定义了对应的256个中断的入口表,这些陷阱门除了13号(通用保护故障)和255号(切入INTFF任务)特别外,其它都采用通用处理,也就是转DOS下对应的功能调用处理程序。
4) TPCodeSeg段是IDT表中除13号和255号中断以外其它陷阱门的直接入口点所在的段。这里也是采用重复汇编,对应256个中断在段起始位置定义了256个跳转的跳转表,它们实际上都跳到一个地方,及“PROCESS”,这么做,我也看不出来有多大意义,实际上我觉得在这个实例中完全就没有意义,它只是暂存了一下中断号,而这个也完全可以不定义这256个跳转。
5) 在IDT中,要使对应中断转入TPcodeSeg跳转表的对应偏移,那么就要进行偏移的初始化。这部分工作在初始化IDT的子过程中完成。一个IDT陷阱门描述符是8个字节,TPCodeSeg跳转表中的每个跳转项长7个字节,所以初始化并移动这两个指针即可完成初始化,须注意的是要跳过13号和255号。
5、 测试效果
6、 源代码分析
1) 整体分析
大致的流程已经在(2)中的具体步骤中说明,由V86模式的相关理论介绍(参考“《80X86汇编语言程序设计教程》二十二 分页管理机制与虚拟8086模式”)可知,V86模式实际上是保护模式下对8086的虚拟,也可以看成是一种混合。一个V86任务由两部分组成:V86监控程序与8086程序。这里在源代码中的格局就是,“V86CodeSeg”代码段中的代码属于8086程序,而代码段“GPCodeSeg”、“TPCodeSeg”和V86任务的LDT表“V86LDTSeg”、IDT表“IDTSeg”以及其它相关数据段、堆栈段共同构成V86监控程序。
V86模式是需要软硬件共同支持的,在硬件上,处理器提供虚拟TSR寄存器,提供最低1M字节物理线性地址空间。在软件是,就是要编写V86监控程序。
V86监控程序的作用在源代码中可以被体现得很清晰,它主要负责在V86模式下运行的8086程序的界面管理、I/O管理、中断和异常管理。也就是说,当8086程序在中有相关指令时,比如说int 21h,那么它的功能调用并不像8086那样直接调用了DOS功能调用,而是转入了V86监控程序的IDT表,在编写操作系统时,如果要支持V86模式,必须在学V86监控程序时定义这张IDT表,并处理int 21h的请求,一般来说如果打算实现它,那么应该转入低端1M空间去执行DOS功能调用(这就是所谓的硬件支持)。V86监控程序是运行在保护模式下的(CPL = 0)。
关于8086程序,这个没有什么好说明的,就是普通的8086程序,需要注意的是,虽然V86监控程序是运行在保护模式下,但是8086程序相对于一种弱化的实模式,两点:第一,它的运作方式从代码上看很像实模式的8086程序,比如说各段寄存器使用的是段值而不是选择子(代码运行在低端1M内存空间中);第二,它弱化体现在执行特权指令和I/O敏感指令有限制条件(CPL = 3)。此外V86模式毕竟虚拟的,所谓的限制条件,其实是V86监控程序不按你的要求去做这些事而已。
2) 任务方式切换进入V86模式
从Temp任务(实模式)切换到V86任务(V86模式)时,采用的是任务门,所以,在切换前须针对V86的TSS做一些初始化,这种初始化与普通的任务切换主要有以下区别:TSS中EFLAGS的VM位置1;TSS中的所有段寄存器赋初值时是采用段值而非选择子;在中断/异常等发生时,要由8086程序切换到V86监控程序,这期间有CPL的变化,所以须提供0级堆栈;V86任务对中断/异常等的管理需要LDT,所以TSS中选填写V86任务LDT的选择子(注意,这里是选择子而不是段值)。
3) V86模式下对中断的处理
在8086程序中(“V86CodeSeg”代码段),有“int 21H”软中断(DOS功能调用)的两条相关指令,一条是显示信息的DOS功能调用,一个是驻留程序的DOS功能调用。它们并不直接执行DOS功能调用,而是要通过V86任务的监控,所以在执行它们以后,即将查找V86的中断表IDT,并转入对应的处理程序。这里的21h号调用直接转入了“TPCodeSeg”段。在这个段中,V86监控程序必须处理8086任务的请求,对允许的请求,主要是转入实模式下对应的中断处理程序。在执行INT n软中断时,从V86模式切换到保护模式,这个时候处理器会进行任务内特权等级变换的跳转,这个跳转对堆栈的变化不同与普通任务,具体参考“《80X86汇编语言程序设计教程》二十二 分页管理机制与虚拟8086模式”,转入V86监控程序后,监控程序需要做的具体步骤如下:
a)在8086程序堆栈(V86任务的3级堆栈)形成返回点现场
b)用实模式下的中断向量跳转地址替换V86监控程序堆栈(V86任务的0级堆栈)中的返回地址
c)从保护模式返回V86模式
软中断没有错误码,对于两次PUSH再保存BP,采用偏移量调整,而不是原书上那样定义了一个错误码的偏移(这样不会有错,但本质是错的)。然后对最低端1M内存定义了一个已访问可读可写数据段的别名“VallMem”,并利用它执行如上的3个操作。
对于第2点:由于切入V86监控程序的中断处理代码段后,返回地址被修改,所以返回时,实际上是切换了实模式的对应中断处理程序。由于只修改了返回地址,而没修改EFLAGS中的VM位,所以跳转时切入的是V86模式,再次进行相关堆栈切换(也即就是说,中断处理程序是在V86模式下被执行的,CPL = 3)。
对于第1点:在实模式对应中断处理程序执行后,它必须跳转到V86模式下的8086程序断点继续执行,所以V86监控程序在返回前必须在8086程序堆栈段形成返回点,从源代码可知,它是修改了8086堆栈(V86任务的3级堆栈),并在栈顶放入了返回点CS:IP以及EFLAGS。
对于第3点:在执行完中断处理程序返回时,由于CPL没有发生变化,所以堆栈也不会发生变化,这个时候检查EFLAGS,发现是V86任务内跳转,所以直接返回V86中断点的下一条指令处继续执行。
4) 驻留程序
驻留程序使用DOS功能调用的32H功能号来实现,关于它的介绍参考“《80X86汇编语言程序设计教程》五 简单运用程序设计”。驻留程序不需要驻留初始化部分,所以,计算驻留长度时为:RCodeSeg – GDTSeg + (offset TSRLint + 16) >> 4 + 10H。其中(RCodeSeg – GDTSeg)为整个代码除RCodeSeg代码段的节数,而(offset TSRLint + 16) >> 4是RCodeSeg代码段内除了初始化部分后其余的节数,10H为PSP节数。
5) V86任务的通用保护异常处理
查找V86中断表并跳转入“GPCodeSeg”代码段,这里是简单的显示了提示信息,我加强了一下,显示了CS:EIP。随后置功能号4ch,并执行DOS功能调用,这个时候再次查找V86中断表并转入“TPCodeSeg”,注意注意的是:转“TPCodeSeg”时须废弃堆栈中的错误码,因为“TPCodeSeg”是认为没有错误码的。“TPCodeSeg”随后转入实模式DOS的4ch号功能调用,结束引发通用保护故障的程序。
6) INTFF任务
当有INT 0ffh软中断时,演示任务切入INTFF任务,它工作于保护模式下,并转入Temp任务,准备返回实模式并退出整个演示任务,而不再驻留。
7、 测试说明
1) V86模式下对中断的处理的测试
V86提示串输出以后,将触发无限保护异常,触发异常的地址是:00000287:000003A7,转为线性地址是00002C17H,这一段很可能是DOS调用区域。这只是我的猜测,因为能够正确输出,那么应该是系统调用返回时地址出了问题。于是,我在进入实模式下处理程序前把它中断下来,看是否是因为堆栈出错导致地址计算错误。
下面是在8086程序中的寄存器状态:
下面是切入V86监控程序进入中断处理程序前的寄存器状态:
可以看到CS:IP从00001BB6:0000024C变到00001BB6:0000024E,这并没有任何异常,因为陷阱类异常切换以后CS:IP保存的是触发陷阱的下一条指令。所以,暂时不知道到底是什么原因导致了这种无限触发通用保护异常的状况
2) INTFF任务
这个任务采用了任务门切换回Temp任务,之前在“《80X86汇编语言程序设计教程》十五 任务切换实例”曾经遇到一个问题,就是通过JUMP任务描述符选择子切换任务时,寄存器的值并不能正确被保存,导致后面切换回来的时候采用了重写TSS中CS:EIP再进行任务切换走回。基于如上问题,为了测试INTFF任务,在V86的汇编程序中,直接使用int 0ffh指令,结果这里的问题更纠结,即使重写,依旧报错,原因未知。
从输出来看,至少验证成功转入了“INTFFCodeSeg”代码段。