《80X86汇编语言程序设计教程》二十四 进入与离开V86模式实例

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”代码段。

时间: 2024-10-07 08:20:52

《80X86汇编语言程序设计教程》二十四 进入与离开V86模式实例的相关文章

《80X86汇编语言程序设计教程》十五 任务切换实例

1.  理论知识参考"<80X86汇编语言程序设计教程>十二 任务状态段.控制门和控制转移",演示内容:直接通过TSS段的任务切换.通过任务门的任务切换.任务内特权级的变换及参数传递.实现的逻辑功能是:从Temp任务切换到Demo任务以后显示原任务(Temp)的挂起点EIP的值. 2.  源代码 "386scd.asm"不再贴上来.参考"<80X86汇编语言程序设计教程>十三 任务内无特权级变换转移实例",演示代码如下:

《80X86汇编语言程序设计教程》十九 操作系统类指令与输入输出保护

1.  通常只在操作系统代码中使用,80386支持4个特权等级,操作系统指令也可分3种:实模式和任何特权级下可执行指令.实模式及特权级0下可执行的指令和仅在保护模式下执行的指令. 1)  实模式和任何特权级下可执行的指令 a)存储全局和中断描述符表寄存器指令 GDT与IDT整个系统各只有一张,它们的定位信息分别保存在GDTR与IDTR中,这两个寄存器的值可以被保存.须注意,LDT表示任务私有,存储LDTR值的指令不属于这一类. i)存储全局描述符表寄存器指令:SGDT  DST DST是48位(

《80X86汇编语言程序设计教程》十 实模式与保护模式的切换实例

1.  再次声明,需要纯DOS系统才能看到满意测试效果.内容是演示实模式与保护模式切换实例,实现功能是16进制显示从110000H开始的256个字节的值 2.  源代码如下: 1 ;功能:演示实模式与保护模式的切换,16进制显示从110000H开始的256个字节的值 2 ;16位偏移的段间直接转移指令的宏定义,这是一个JMP指令到所描述的地址 3 4 JUMP macro selector,offsetv 5 db 0eah ;操作码 6 dw offsetv ;16位偏移 7 dw selec

《80X86汇编语言程序设计教程》二十五 结语(读后感:这本书怎么样)

这本书的推荐星级是:5星.毕竟是经典书籍,没什么好说的. 就汇编本身而言,在编写高效率程序以及对程序的优化,调试,工程的逆向都是一门基础:就理论上的操作系统而言,汇编让你了解CPU,了解计算机的体系结构,它是阅读操作系统源码的前提,这也是<80X86汇编语言程序设计教程>做得比较好的一点,它对386的保护方式下的编程写得比较详实,读完整本书,会发现这学的不仅仅是汇编语言,还有CPU的体系架构,它让你基本猜测得到在编写基于80386CPU的操作系统时,大概要做一些什么事情. 阅读前,我选过几本书

《80X86汇编语言程序设计教程》二十三 分页管理机制实例

1.  理论知识参考"<80X86汇编语言程序设计教程>二十二 分页管理机制与虚拟8086模式".演示分页机制实例:初始化页目录表和部分页表:启用分页管理机制:关闭分页管理机制等.逻辑功能:在屏幕上显示一条表示已启用分页管理机制的提示信息.大体步骤是:在实模式下拷贝显示串程序的代码到预定义区域,转保护模式,初始化页目录和2个页表,开启分页机制,转入预定义区执行显示代码,然后关闭分页机制,重新回到实模式,程序终止. 2.  源代码 "386scd.asm"

《80X86汇编语言程序设计教程》十一 32位代码段和16位代码段切换实例

1.  演示32位代码段与16位代码段之间的切换.实现的功能是以十六进制和ASCII码字符两种形式显示从内存地址100000H开始的16个字节的内容. 2.  源代码如下: 1 ;DosTest.Asm 2 ;16位偏移的段间转移指令的宏定义 3 ;使用于16位段,用于跳转到32位目的段 4 ;注意:标号偏移必须在16位二进制符号数数能表示的范围之内 5 JUMP16 macro selector,offsetv 6 db 0eah ;操作码 7 dw offsetv ;16位偏移 8 dw s

Webpack入门教程二十四

116.输出HtmlWebpackPlugin.files中的内容,修改模板文件index.html,代码如下 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>webpack demo</ti

I学霸官方免费教程二十四:Java常用类之随机数 Math类和Random类

咱先了解即可 Math类:数学类,包括abs()求绝对值,sin()正弦函数,cos()余弦函数等等Random类:获取各种数据类型的随机数 实例: import java.util.Random; /**  * 演示获取随机数  * @author 学霸联盟 - 赵灿  */ public class MathDemo { public static void main(String[] args) { //创建Random对象 Random random = new Random(); //

Oracle Coherence中文教程二十四:在高速缓存中的数据处理

在高速缓存中的数据处理 coherence提供了理想的基础设施建设数据网格服务和客户端和基于服务器的应用程序使用数据网格.在一个基本的层面上,相干可以在大量的服务器在网格管理一个巨大的数据量,它可以提供接近零延迟访问该数据,它支持跨数据的并行查询中的map-reduce方式;它支持数据库和EIS系统,作为该数据的记录系统的集成.此外,Coherence提供一些服务,这是建立有效的数据网格的理想选择. 本章包含以下各节: 有针对性的执行 并行执行 基于查询的执行 数据网格范围内执行 代理针对性,并