找回密码
 注册
查看: 1272|回复: 0

80386保护模式简介

[复制链接]
发表于 2003-5-14 07:53:53 | 显示全部楼层 |阅读模式
【 80386保护模式简介 】                  
    在保护模式下有很多新的名词 ,包含 GDT.LDT.IDT 以及 CR0-CR3 ,笔者保护

模式并不清楚 ,所以底下资料可能有错误。这里使用大量的线性记忆体观念 ,请您

一定要从头往後看 ,否则很可能会看不懂 ,且必须懂线性记忆体计算方式。

【 GDT 介绍 】

    在真实模式下每个区段都等於64K ,可是保护模式下每个区段的大小却是可变动

的 ,每个区段有多大呢 ,就是由 GDT 来决定。
   您可以用 SGDT CS:[BX] 的方式将 GDT 的值读出 ,它的长度为 6 BYTE ,底下

    是笔者写的小程式读出。



    XXXX:0000  FF 0F 00 20 C0 00

                     ^^^^^^^^^^^GDT表所在的线性记忆体位址

               ^^^^^GDT表长度+1



将此表资料读出来.

X:00C02000 00 00 00 00 00 00 00 00-FF FF 00 A0 C2 9B 40 00 ........... B.@.

X:00C02010 FF FF B0 DD 01 93 40 00-FF FF E0 B3 00 9A 00 00 ..0]..@...`3....

X:00C02020 FF FF E0 B3 00 93 00 00-00 00 00 20 C1 82 80 00 ..`3....... A...

X:00C02030 00 00 00 20 C1 93 C0 00-00 00 00 20 C0 93 C0 00 ... A.@.... @.@.

X:00C02040 00 00 00 00 00 92 40 00-FF FF 00 80 0B 92 40 00 ......@.......@.
它所代表的意思是如下图所示∶(每组 8 byte)



        ┌──────────────────────┐

       1│                Limit bit 0-15              │ 0 byte

        ├──────────────────────┤

       3│                Base bit 0-15               │ 2

        ├──────────┬───────────┤

       5│       存取权       │    Base bit 16-23    │ 4

        ├──────────┼───────────┤

       7│   Base bit 24-31   │G│..│limit bit 16-19│ 6

        └──────────┴───────────┘

            "G"代表 Limit 的单位是 Byte 或 PAGE(4K)



所以....



#0000  Segment not present.

#0008  Base=00C2A000  Limit=0000FFFF  Flags=9B  USE32  Byte granularity

#0010  Base=0001DDB0  Limit=0000FFFF  Flags=93  USE32  Byte granularity

#0018  Base=0000B3E0  Limit=0000FFFF  Flags=9A  USE16  Byte granularity

#0020  Base=0000B3E0  Limit=0000FFFF  Flags=93  USE16  Byte granularity

#0028  Base=00C12000  Limit=00000000  Flags=82         Page granularity

#0030  Base=00C12000  Limit=00000000  Flags=93  USE32  Page granularity

#0038  Base=00C02000  Limit=00000000  Flags=93  USE32  Page granularity

#0040  Base=00000000  Limit=00000000  Flags=92  USE32  Byte granularity

#0048  Base=000B8000  Limit=0000FFFF  Flags=92  USE32  Byte granularity

#0050  Base=0001F56C  Limit=000007FF  Flags=92  USE32  Byte granularity

#0058  Base=00000000  Limit=00000144  Flags=92  USE32  Page granularity

#0060  Base=00000000  Limit=00000144  Flags=93  USE32  Page granularity

#0068  Base=00127F48  Limit=0000C32F  Flags=9B  USE16  Byte granularity

#0070  Base=00134278  Limit=000028F7  Flags=93  USE16  Byte granularity

#0078  Base=00000000  Limit=00000000  Flags=92  USE16  Byte granularity

^^^^^Selector                              ^^存取权



Base 就是指这个Secector:00000000对应到线性记忆体的何处 ,也就是说将线性记

忆体从 Base 所指的地方开始长度为 Limit ,剪下来变成一个独立的区段 ,如果您

在该区段想看超过 LIMIT 长度的记忆体 ,则会发生保护模式错误...应用程式可拦

截所发生的中断适当的加以处理。

注意 ,Limit的单位可以是 byte ,也可以是page(4k) ,由 "G" 是否为 1 来决定



至於 Selector 的数值我猜想应该是被标上 8 的倍数吧 ,因为很多书都是如此介

绍它。



--------------------------------------------------------------------------

【 LDT 介绍 】

    上面介绍了 GDT 可以设定很多个Secector ,而 LDT 则是在这些被定义出来

的Selector中再切割出更小的单元。也就是说 LDT 的资料长度只有 2 BYTE ,这

个值直接就是指 Selector。
※这个命令必需在最高权力下才能执行 ,所以笔者使用 386DEBUG 来执行 ,在传

  统 Real Mode/V86 都不能执行。



C:\>386debug 386debug.exp   (改过的.exp档)

000C:0002743C 660F0007                 SLDT     [EDI]

-T

-D EDI

0014:00000000  28 00                    <-- LDT 所指的Selector为0028

根据 GDT 的资料查表得到下表 ,但是由於 0028 这段落禁止观看 ,所以我改看0030

的段落 ,因为它的 Base 是一样的。



#0028  Base=00C12000  Limit=00000000  Flags=82         Page granularity

#0030  Base=00C12000  Limit=00000000  Flags=93  USE32  Page granularity



-D 30:0

0030:00000000  FF 00 F0 CE 09 92 40 00-31 00 00 00 CA 9B C0 00 [email protected].@.

0030:00000010  31 00 00 00 CA 93 C0 00-FF FF 00 80 0B 92 40 00 1...J.@.......@.

0030:00000020  FF 00 F0 CE 09 92 40 00-4D 00 90 CE 09 92 40 00 [email protected]..@.

0030:00000030  44 01 00 00 00 93 C0 00-00 00 00 00 00 92 40 00 D.....@.......@.

0030:00000040  FF FF 00 80 0B 92 40 00-00 00 00 00 00 92 40 00 ......@.......@.

0030:00000050  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

0030:00000060  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

0030:00000070  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................



-DL 0

#0004  Base=0009CEF0  Limit=000000FF  Flags=92  USE32  Byte granularity

#000C  Base=00CA0000  Limit=00000031  Flags=9B  USE32  Page granularity

#0014  Base=00CA0000  Limit=00000031  Flags=93  USE32  Page granularity

#001C  Base=000B8000  Limit=0000FFFF  Flags=92  USE32  Byte granularity

#0024  Base=0009CEF0  Limit=000000FF  Flags=92  USE32  Byte granularity

#002C  Base=0009CE90  Limit=0000004D  Flags=92  USE32  Byte granularity

#0034  Base=00000000  Limit=00000144  Flags=93  USE32  Page granularity

#003C  Base=00000000  Limit=00000000  Flags=92  USE32  Byte granularity

#0044  Base=000B8000  Limit=0000FFFF  Flags=92  USE32  Byte granularity

#004C  Base=00000000  Limit=00000000  Flags=92  USE32  Byte granularity

#0054  Segment not present.

#005C  Segment not present.

#0064  Segment not present.

#006C  Segment not present.

#0074  Segment not present.

#007C  Segment not present.



--------------------------------------------------------------------------

【 IDT 介绍 】

    在以往中断向量表都是用 4 byte 来表示 ,但是在保护模式下则由 8 byte 表

示 ,至於那几个 byte 表示什麽 ,笔者还未搞懂 ,底下只弄懂几个。





C:\>386debug 386debug.exp   (改过的.exp档)

000C:00027434 660F010F                 SIDT     [EDI]

-D EDI

0014:00000000  FF 07 6C F5 01 00 .. ..-.. .. .. .. .. .. .. ..

                     ^^^^^^^^^^^线性记忆体位址

               ^^^^^长+1

因为该线性记忆体已对映到 50:0

#0050  Base=0001F56C  Limit=000007FF  Flags=92  USE32  Byte granularity

所以:

0050:00000000  00 34 08 00 00 EE 00 00-0A 34 08 00 00 EE 00 00 .4...n...4...n..

0050:00000010  14 34 08 00 00 EE 00 00-1E 34 08 00 00 EE 00 00 .4...n...4...n..

0050:00000020  28 34 08 00 00 EE 00 00-32 34 08 00 00 EE 00 00 (4...n..24...n..

0050:00000030  3C 34 08 00 00 EE 00 00-6C 16 C8 0F 00 8E 00 00 <4...n..F4...n..

0050:00000040  50 34 08 00 00 EE 00 00-5A 34 08 00 00 EE 00 00 P4...n..Z4...n..

0050:00000050  64 34 08 00 00 EE 00 00-6E 34 08 00 00 EE 00 00 d4...n..n4...n..

0050:00000060  78 34 08 00 00 EE 00 00-82 34 08 00 00 EE 00 00 x4...n...4...n..

0050:00000070  8C 34 08 00 00 EE 00 00-96 34 08 00 00 EE 00 00 .4...n...4...n..



-DI 0

#0000  Selector=0008  Offset=00003400  Flags=EE         ;int_0

#0001  Selector=0008  Offset=0000340A  Flags=EE         ;int_1

#0002  Selector=0008  Offset=00003414  Flags=EE         ;int_2

#0003  Selector=0008  Offset=0000341E  Flags=EE         ;int_3

#0004  Selector=0008  Offset=00003428  Flags=EE

#0005  Selector=0008  Offset=00003432  Flags=EE

#0006  Selector=0008  Offset=0000343C  Flags=EE

#0007  Selector=0FC8  Offset=0000166C  Flags=8E         ;此处为Q387使用

#0008  Selector=0008  Offset=00003450  Flags=EE

#0009  Selector=0008  Offset=0000345A  Flags=EE

#000A  Selector=0008  Offset=00003464  Flags=EE

#000B  Selector=0008  Offset=0000346E  Flags=EE

#000C  Selector=0008  Offset=00003478  Flags=EE

#000D  Selector=0008  Offset=00003482  Flags=EE

#000E  Selector=0008  Offset=0000348C  Flags=EE

#000F  Selector=0008  Offset=00003496  Flags=EE



请仔细看一看这个表的对应情形 ,笔者故意载入Q387 以便让 INT_7 的 Selector 与

众不同 ,让您更易判断中断表对应关系。

--------------------------------------------------------------------------

实例解说∶

底下是读取 SoftICE INT_0 的程式码范例∶



Load IDT

LDT = FF 07 12 C0 80 00 所以观看 0080C012 的记忆体

  0080C012  47 2C 18 00 00 EE 00 00-4C 2C 18 00 00 EE 00 00  G,...?.L,...?.

  0080C022  51 2C 18 00 00 EE 00 00-56 2C 18 00 00 EE 00 00  Q,...?.V,...?.

  0080C032  5B 2C 18 00 00 EE 00 00-60 2C 18 00 00 EE 00 00  [,...?.`,...?.

  0080C042  65 2C 18 00 00 EE 00 00-6A 2C 18 00 00 EE 00 00  e,...?.j,...?.

  0080C052  6F 2C 18 00 00 EE 00 00-74 2C 18 00 00 EE 00 00  o,...?.t,...?.

  0080C062  79 2C 18 00 00 EE 00 00-7E 2C 18 00 00 EE 00 00  y,...?.~,...?.

由此得知 INT_0 是放在 0018:00002C47 的位址 ,於是查GDT表..


Load GDT

GDT = C8 00 18 C8 80 00 所以观看 0080C818 的记忆体

  0080C818  00 00 00 00 00 00 00 00-FF FF 10 11 83 93 00 00  ............儞..

  0080C828  FF FF 00 6E 81 93 00 00-FF FF 00 6E 81 9B 00 00  ...n亾.....n仜..

  0080C838  FF FF 00 00 00 93 CF 00-FF 7F 00 00 0B 92 00 00  .....摗@.�...�.

  0080C848  FF 7F 00 80 0B 92 00 00-FF FF 00 00 0C 92 00 00  .�.�.�......�.

  0080C858  FF FF F0 32 82 9A 00 00-FF FF 00 C0 80 93 C0 00  ..?倸.....?摗@

  0080C868  0F 00 00 C0 7F 92 C0 00-68 20 00 00 81 8B 00 00  ...?挕@h ..亱..

得到 Selector=0018=线性记忆体位址 816E00 处



於是我们就可以得知该中断程式放在 816E00:2C47 了 ,於是笔者把 816E00 的记忆体

搬到 8000:0000 ,然後用 DEBUG 来查看。



-u 8000:2c47

8000:2C47 6A00           PUSH   00

8000:2C49 E9F4D6         JMP    0340

8000:2C4C 6A01           PUSH   01

8000:2C4E E9C7D8         JMP    0518

8000:2C51 6A02           PUSH   02

8000:2C53 E98ADC         JMP    08E0

8000:2C56 6A03           PUSH   03

8000:2C58 E9D6DC         JMP    0931

8000:2C5B 6A04           PUSH   04

8000:2C5D E9E0D6         JMP    0340

8000:2C60 6A05           PUSH   05

8000:2C62 E9DBD6         JMP    0340

8000:2C65 6A06           PUSH   06

8000:2C67 E943DF         JMP    0BAD

8000:2C6A 6A07           PUSH   07

8000:2C6C E975E0         JMP    0CE4

8000:2C6F 6A08           PUSH   08

8000:2C71 E97BE1         JMP    0DEF

8000:2C74 6A09           PUSH   09

8000:2C76 E91605         JMP    318F

8000:2C79 6A0A           PUSH   0A

8000:2C7B E9C4D5         JMP    0242

8000:2C7E 6A0B           PUSH   0B

8000:2C80 E9BFD5         JMP    0242



--------------------------------------------------------------------------

看了上面几个例子後 ,再来就是练习进入保护模式 ,底下的例子请勿载入 EMM 系

列的保护模式软体 ,以免等级权限相冲当机。



code    segment

        assume  cs:code,ds:code

start   proc    near

        jmp     next



buffer1 db      18h,00h,00h,00h,00h,00h

;               ---+--- ------+--------

;                  |          |

;                  |          |

;                  |          GDT 表的记忆体位址

;                  |

;                  +----------GDT 表的长度

;

;

buffer2 db      000h,000h,000h,000h,000h,000h,000h,000h ;保留段

        db      0ffh,0ffh,000h,000h,000h,09bh,000h,000h ;程式段code:0

        db      0ffh,0ffh,000h,080h,00bh,093h,000h,000h ;萤幕段B800:0

        db      0100h dup (0)

;                         ------+-------

;                               |

;                               |

;                               线性记忆体位址

;

msg_1   db      'Enter Protect Mode !'

msg_2   db      0dh,0ah,'Return Real Mode !',0dh,0ah,'$'

.386p

next :

        mov     ax,0600h        ;

        mov     bx,0700h        ;

        mov     cx,0000h        ;

        mov     dx,184fh        ;

        int     10h             ; CLS

        mov     ah,02h          ;

        mov     bh,00h          ;

        mov     dx,0100h        ;

        int     10h             ;

        mov     ax,cs

        mov     ds,ax

        mov     es,ax

        xor     eax,eax

        xor     ebx,ebx

        mov     ax,cs

        mov     cl,04h

        shl     eax,cl

        mov     bx,offset buffer2

        add     eax,ebx

        mov     bx,offset buffer1+2

        mov     cs:[bx],eax             ;GDT 位址设定

        NOP

        xor     eax,eax

        xor     ebx,ebx

        mov     ax,cs

        mov     cl,04h

        shl     eax,cl

        add     eax,ebx

        mov     bx,offset buffer2

        mov     cs:[bx+0ah],eax                 ;GDT Table 设定

        mov     byte ptr cs:[bx+0dh],9bh        ;存取权

        mov     ax,cs

        mov     ds,ax

        mov     es,ax

        mov     bx,offset buffer1

        xor     ecx,ecx

        cli

        cli

        lgdt    cs:[bx]                         ;载入GDT

        mov     eax,cr0

        or      eax,01h

        mov     cr0,eax

        jmp     protection                      ;进入保护模式

protection :

        db      66h

        mov     ax,code

        mov     ds,ax

        mov     si,offset msg_1

        mov     bx,0010h

        mov     es,bx

        mov     di,0000h

        mov     cx,0014h

        mov     ah,70h

show :

        cld                                     ;将CS:MSG_1搬到 0010:00000000

        lodsb                                   ;(0010的区段=B8000 请参考GDT

        stosw                                   ; 表)

        loop    show                            ;

        mov     eax,cr0

        and     al,not 1

        mov     cr0,eax

        db      0eah

        dw      real_mode,code                  ;返回真实模式

real_mode :

        sti

        mov     ax,cs

        mov     ds,ax

        mov     ah,09h

        mov     dx,offset msg_2

        int     21h

        mov     ax,4cffh

        int     21h

start   endp

code    ends

        end     start



--------------------------------------------------------------------------

    上面这个例子并没有设定 IDT (中断表) ,如果您要设定中断表的话 ,记得返回

真实模式时要还原 IDT 表.

--------------------------------------------------------------------------

    如果您希望在载入 QEMM386 後还能正常进入保护模式的话 ,则必需透过 VCPI

的命令来切入保护模式 ,详情可翻阅 VCPI 的书籍。
                                                           -- 软蛀 --



example.ASM:


code    segment

        assume  cs:code,ds:code

start   proc    near

        jmp     next



buffer1 db      18h,00h,00h,00h,00h,00h

;               ---+--- ------+--------

;                  |          |

;                  |          |

;                  |          GDT 表的记忆体位址

;                  |

;                  +----------GDT 表的长度

;

;

buffer2 db      000h,000h,000h,000h,000h,000h,000h,000h ;保留段

        db      0ffh,0ffh,000h,000h,000h,09bh,000h,000h ;程式段code:0

        db      0ffh,0ffh,000h,080h,00bh,093h,000h,000h ;萤幕段B800:0

        db      0100h dup (0)

;                         ------+-------

;                               |

;                               |

;                               线性记忆体位址

;

msg_1   db      'Enter Protect Mode !'

msg_2   db      0dh,0ah,'Return Real Mode !',0dh,0ah,'$'

.386p

next :

        mov     ax,0600h        ;

        mov     bx,0700h        ;

        mov     cx,0000h        ;

        mov     dx,184fh        ;

        int     10h             ; CLS

        mov     ah,02h          ;

        mov     bh,00h          ;

        mov     dx,0100h        ;

        int     10h             ;

        mov     ax,cs

        mov     ds,ax

        mov     es,ax

        xor     eax,eax

        xor     ebx,ebx

        mov     ax,cs

        mov     cl,04h

        shl     eax,cl

        mov     bx,offset buffer2

        add     eax,ebx

        mov     bx,offset buffer1+2

        mov     cs:[bx],eax             ;GDT 位址设定

        NOP

        xor     eax,eax

        xor     ebx,ebx

        mov     ax,cs

        mov     cl,04h

        shl     eax,cl

        add     eax,ebx

        mov     bx,offset buffer2

mov     cs:[bx+0ah],eax                 ;GDT Table 设定

        mov     byte ptr cs:[bx+0dh],9bh        ;存取权

        mov     ax,cs

        mov     ds,ax

        mov     es,ax

        mov     bx,offset buffer1

        xor     ecx,ecx

        cli

        cli

        lgdt    cs:[bx]                         ;载入GDT

        mov     eax,cr0

        or      eax,01h

        mov     cr0,eax

        jmp     protection                      ;进入保护模式

protection :

        db      66h

        mov     ax,code

        mov     ds,ax

        mov     si,offset msg_1

        mov     bx,0010h

        mov     es,bx

        mov     di,0000h

        mov     cx,0014h

        mov     ah,70h

show :

        cld                                     ;将CS:MSG_1搬到 0010:00000000

        lodsb                                   ;(0010的区段=B8000 请参考GDT

        stosw                                   ; 表)

        loop    show                            ;

        mov     eax,cr0

        and     al,not 1

        mov     cr0,eax

        db      0eah

        dw      real_mode,code                  ;返回真实模式

real_mode :

        sti

        mov     ax,cs

        mov     ds,ax

        mov     ah,09h

        mov     dx,offset msg_2

        int     21h

        mov     ax,4cffh

        int     21h

start   endp

code    ends

        end     start



>
>
>                                                   ┌┐┌┐∞
> 【 80386保护模式简介二 】                    ┘└┘└┘
> --------------------------------------------------------------------------
>     进入保护模式可以得到很多好处 ,让你的程式不再有 640K 限制 ,可以ㄔ ?
> 拟记忆体、拦 I/O ,所有的应用程式读写系统暂存器 ,产生中断....都可以完全拦
> 截 ,而且 TSS 工作切换能力可以让你不占用 DOS 下的堆叠区 ,还有很多好处无法
> 一一叙述 ,因此由笔者来教你如何切入保护模式吧....从简单的开始。
>
>     在保护模式下有很多新的名词 ,包含 GDT.LDT.IDT 以及 CR0-CR3 ,笔者对保护
> 模式并不清楚 ,所以底下资料可能有错误。这里使用大量的线性记忆体观念 ,请您
> 一定要从头往後看 ,否则很可能会看不懂 ,且必须懂线性记忆体计算方式。
>
> --------------------------------------------------------------------------
>     在进入保护模式时 ,首先你要先设定 GDT 表格 ,这个表格描述主要是来定义每
> 个段落的记忆体起始位址与长度、存取权。   这个情形就好像传统 REAL MODE  那
> 样 ,REAL MODE 每个区段的记忆体开始位址与长度都已经由 CPU 定死了 ,比如说当
> 我们看到 1000:0000 ,其实它就是指记忆体的第 64K 位址 ,同理看到 2000:0000
> 就代表是第 128K 位址 ,定址方式就是 Segment:Offset。
>
>     而保护模式的段落起始位址与长度却是可程式变动的 ,这个可变动的段落起始
> 位址与长度就是由 GDT 来设定的 ,根据这个值 ,你可以将每个段落改成64K ,或是
> 1MB...甚至更多 ,可任意设定 1BYTE~4GB ,所以定址方式变成 Selector:Offset
> 或许您曾用过 386DEBUG ,看过定址方式为 XXXX:XXXXXXXX ,根据後面这八位数 ,
> 理论上可定址到 4GB ,其实这是不行的 ,如果你在 GDT 表格设定的记忆体为 1K
> 则你尝试 DUMP 1K 以後的记忆体都会看到 FF ,就好像没有记忆体一般。
>
> ---------------------------------------------------------------------------
> Gdtadds dw      0018h,GdtTable 32 位元线性位址
> GdtTable db     00h,00h,00h,00h,00h,00h,00h,00h         ;
>         db      7fh,ffh,00h,08h,0bh,93h,00h,00h         ;B800:0 32K
>         db      ffh,ffh,56h,34h,12h,93h,0fh,78h         ;
>                 ^^^^^^^ ^^^^^^^^^^^ ^^^ ^^^ ^^^
>                 ↑      ↑          ↑  ↑  ↑
>                 │      │          └──────93=可读写区段
>                 │      │              │  │
>                 └───────────┴────0fffff+1=1MB (Limits)
>                         │                  │
>                         └─────────┴──12345678 (Base)
>
>
> 它所代表的意思是如下图所示∶(每组 8 byte)
>
>         ┌──────────────────────┐
>        1│                Limit bit 0-15              │ 0 byte
>         ├──────────────────────┤
>        3│                Base bit 0-15               │ 2
>         ├──────────┬───────────┤
>        5│       存取权       │    Base bit 16-23    │ 4
>         ├──────────┼────────w──┤
>        7│   Base bit 24-31   │G│..│limit bit 16-19│ 6
>         └──────────┴───────────┘
>             "G"代表 Limit 的单位是 Byte 或 PAGE(4K)
>
> 所以....
>
> #0000  Segment not present.
> #0008  Base=000B8000  Limit=0000FFFF  Flags=93  USE32  Byte granularity
> #0010  Base=12345678  Limit=000FFFFF  Flags=93  USE32  Byte granularity
> ^^^^^Selector                               ^^存取权
>
>
>
> 设定完後 ,就是切入保护模式 ,只要将 CR0 暂存器的 Bit0 设为 '1' ,再用一个
> 跳越指令 ,就进入保护模式了。
>
>
> ---------------------------------------------------------------------------
> 讲不懂没关系 ,现在来看看实例 ,这样比较容易懂..
>
> C:\>386MICE SAMPLE.EXE
> -G 1AE
> EAX=00044A1C  EBX=00000003  ECX=00000000  EDX=00000100
> ESI=00000000  EDI=00000000  EBP=00000000  ESP=0000FFFE
> DS=4A1C  SS=4A1C  ES=4A1C  FS=4A0C  GS=4A0C
> -U 1AE
> 4A1C:000001AE           CLI
> 4A1C:000001AF           LGDT    CS:[BX]     ──→ DUMP CS:[BX] ──→
> 4A1C:00000003  18 00 C9 A1 04 00 <--- GDT 表放在 0004A1C9 长度 18h
> 4A1C:000001B3           MOV
> EAX,CR0                                                        │
> 4A1C:000001B6           OR
> EAX,1                                                          ↓
> 4A1C:000001BA           MOV     CR0,EAX
> 4A1C:00000009  00 00 00 00 00 00 00 00-FF FF C0 A1 04 9B 00-00
> 4A1C:000001BD           JMP     01C0
> 4A1C:00000010  FF FF 00 80 0B 93 00 00 (GDT表)
> 4A1C:000001BF           NOP
> 4A1C:000001C0           MOV     AX,0008H
> 4A1C:000001C3           MOV     DS,AX
> 4A1C:000001C5           MOV     WORD PTR DS:[0000H],7041h
>
>
> 由上面的 GDT 表知道 此程式共规划了三个区段 ,其中 0000 区段是不使用
> 故区段的表示方式如下∶
>
> #0000  Segment not present.
> #0008  Base=0004A1C0  Limit=0000FFFF  Flags=9B  USE32  Byte granularity
> #0010  Base=000B8000  Limit=0000FFFF  Flags=93  USE32  Byte granularity
>
>
>
> -G 1BD
> EAX=00000001  EBX=00000003  ECX=00000000  EDX=00000100
> ESI=00000000  EDI=00000000  EBP=00000000  ESP=0000FFFE
> DS=4A1C  SS=4A1C  ES=4A1C  FS=4A0C  GS=4A0C
> 4A1C:000001BD           JMP     01C0
>
> -T (这儿就算是进入保护模式了)
> EAX=00000001  EBX=00000003  ECX=00000000  EDX=00000100
> ESI=00000000  EDI=00000000  EBP=00000000  ESP=0000FFFE
> DS=0000  SS=0000  ES=0000  FS=0000  GS=0000
> 0000:000001C0           MOV     AX,0008H
> 0000:000001C3           MOV     DS,AX
> 0000:000001C5           MOV     WORD PTR DS:[0000H],7041h
>
>
>
> 因为进入保护模式 ,所以 Selector 的区段应该要去查 GDT 表格 ,这个例
> 子的 Selector 0010 的 Base = B8000 ,所以...
> 保护模式下的 0010:00000000 = 真实模式下的 B800:0000 ,这样您懂了吗?
>
> 在行号 1C5 的位址有一行写入 7041 的动作 ,就是在萤幕秀 'A' 反白字元.
>
> 最後要进入真实模式时 ,只要将 CR0 的 Bit0 设为 '0' ,再用一个跳越指
> 令就回到真实模式了..
>
> --------------------------------------------------------------------------
> 後记:
>     若有问题 ,烦在本站『站内信箱』留信给我....尽量避免使用网路信 ,
> 且尽快提出 ,否则竣U来的课程将会更难懂 ,如果你是完全不懂 ,麻烦也留
> 信给我 ,我会再把这一章节再细细重新说明。至於对组合语言不懂 ,或是对
> 保护模式没兴趣的人 ,本人就帮不上忙了。
>
>     A∶下一次笔者将继续解说 V86 模式下的工作切换
>     B∶等级权限 / 拦 I/O
>
> ┌───────────────────────────────────┐
> │  Soft Bugger 软体蛀虫 90:90/2                    软体新技术的实行者  │
> │  BBS:02-5955461 24HR          ID:Werong Ho               -- 软蛀 --  │
> └──────────w────────────────────────┘
>



>
>                                                     ┌┐┌┐∞
> 【 80386 保护模式简介三 】                     ┘└┘└┘
> ==========================================================================
> 前言∶
>
>     前面两集主要是要告诉各位有关 IDT.GDT 的用法 ,虽M这样已经可以简单的进
> 入保护模式 ,但是它还不足以让你撰写程式 ,因此笔者还必需往下继续叙说 ,不过再
> 往下讲之前 ,又有一票烦且杂的观念要说 ,本篇还是继续在"观念"上打转 ,读者千万
> 不要以为本篇又是「干古」 ,如果本篇不懂的话 ,後面的精彩文章您大概也看不懂 ,
> 笔者会尽量把文章写到容易懂的范围。
>
> --------------------------------------------------------------------------
> ┌────────┐
> │80386 暂存器介绍│
> └────────┘
>
>     80386 的暂存器除了扩充成 32 位元以外 ,亦增加了许多新獐 s器 ,除了一般
> 使用者暂存器(AX.BX....SI.DI)各位已经了解以外 ,也增加了系统暂存器、以及扩充
> 的旗标 暂存器....等等。
>
>
> A.使用者暂存器   → EAX.EBX.ECX.EDX.ESI,EDI.EBP.ESP
>
> B.指令指标暂存器 → CS.EIP 两个暂存器
>
> C.区段暂存器     → CS.SS.DS.ES.FS.GS
>     虽然 80386 已经进入 32 位元时代 ,但是这几个暂存器仍是 16 位元的 ,且多
>     了 FS.GS 两个暂存器 ,这两个暂存器并无特殊意义 ,各位可以把它当做 DS.ES
>     来看待。
>
> D.系统暂存器
>     A. 控制暂存器:包含 CR0.CR2.CR3 三个 ,各位可能看到漏了一个 CR1 ,原因是
>        386.486.586 都没有此暂存器
>     B. 除错暂存器:包含 DR0.DR1.DR2.DR3.DR6.DR7 共六个 ,也是漏了 DR4.DR5 两
>        个 ,原因同上
>     C. 保护模式分段控制:IDT.GDT.LDT.TR
>
> --------------------------------------------------------------------------
> ┌────┐
> │工作切换│
> └────┘
>
>     当您设定某些系统暂存器以後 ,电脑并不会马上反应所设定的工作 ,必需透过工
> 作切换的动作才会起动 ,这个工作切换很难难用文字表达 ,笔者认为工作切换就是等
> 级切换的动作。可造成工作切换的指令包含 INT_X 、JMP TSS区段...等 ,其中INT_X
> 是指在 V86 下的程式若发生中断 ,电脑会自动切换至保护模式 ,并呼叫保护模式下的
> 中断处理程式 ,再由保护模式下的程式决定是否呼叫原来 V86  下的中断向量表 ,而
> 这切换到保护模式、再切回 V86 下 , 共发生两次工作切换......
>
> ┌──┐
> │等级│
> └──┘
>
>     保护模式下 ,等级共有 0.1.2.3 四个等级 ,其中第0级等级最高 ,第3级最低 ,
> 而0级因为是最高等级 ,因此也有人称为「特权等级」 ,而应用程式的等级为多少呢?
> 这表示在 EFLAG ?IOPL (BIT12.13) 里 ,在 V86 下的等级多半是最低的第3级 ,所
> 以此值为 '11'。
>
>     或许各位会认为自己去修改这个旗标将自己的等级调高就好了 ,事实上改好後还
> 要经过工作切换的动作 ,等级才能被修改 ,而经过工作切换的动作後 ,你的程式控制
> 权将转交给别人 ;再简单的说 ,发生 INT_X 时 ,电脑会将等级切换成最高等级(事实
> 上是由中断表上决定的) ,并进入保护模式 ,之後保护模式的程式再来决定将使用者的
> EFLAG 切成什麽等级 ,然後再 IRETD切回 V86 ,於是应用程式根本抢不过最早进入保
> 护模式的家伙。(这样你有办法在V86下抢到最高等级吗....不可能嘛)
>
>     等级的高低可以决定自己有多少控制权 ,例如等级最高的人才可以读写系统暂存
> 器 ,其馀的人想读写系统暂存器都会发生 General Protection Error 0D ,你可以把
> 它想像成等级不够 ,却要读取系统资源 ,会发生 INT_0D ,而原本这行指令将不会被
> 执行 ,而堆叠里所摆的 EIP 值也停在这行上面 ,如果 INT_0D 的处理程式不去跳过
> 这个指令 ,则会永远停在这个指令里(形同当机)。
>
>     在 V86 下发生中断时 ,会自动 PUSH EIP.CS.EFLAG.ESP.SS......数个暂存器 ,
> 并自动将 SS.ESP 的值替换 ,以免发生中断?,会动用到 V86 的堆叠 ,可是如果发
> 生的是 General Protection Error(俗称异常),则会在 PUSH EIP 之前再多摆入一
> 个DWORD 的错误代码 ,如果您的程式在 IRETD 前不减去这个可能存在的错误代码 ,
> 则会发生不可预知的後果。这也是保护模式下的程式不好写的原因之一。 而SS与ESP
> 所替换的值 ,则是最初进入保护模式後 ,由最高等级的人决定的(摆於TSS区段)。
>
>     第二集里笔者有介绍 GDT 表 ,其中有个 93 代表可写区段 ,如果设成 89 ,则表
> 示此区段是 TSS 表格 ,再由 TR 暂存器来指定发生中断时 ,取用那一个区段的表格.
>
> 举例来说 ,下面是 GDT 表格
>
> gdttab  db      000h,000h,000h,000h,000h,000h,000h,000h ;00
>         db      0ffh,0ffh,000h,000h,000h,09bh,000h,000h ;08
>         db      0ffh,0ffh,000h,000h,000h,093h,08fh,000h ;10
>         db      0ffh,0ffh,000h,000h,000h,089h,000h,000h ;18
>         db      0ffh,0ffh,000h,000h,000h,089h,000h,000h ;20
>         db      0ffh,0ffh,000h,000h,000h,093h,000h,000h ;28
>         db      0ffh,007h,000h,000h,000h,093h,000h,000h ;30
>         db      0ffh,0ffh,000h,080h,00bh,093h,000h,000h ;38
>         db      0ffh,0ffh,000h,000h,000h,093h,000h,000h ;40
>
>   我们可以看到 18.20 两个 Selector 正好就是 89h ,也就是说它们俩个都可以是
> TSS 描述表格 ,如果 MOV AX,0018、LTR AX ,则表示发生工作切换时 ,取用 0018 的
> 描述表格。
>
> --------------------------------------------------------------------------
> ┌──────┐
> │TSS 表格简介│
> └──────┘
>     TSS 也有人称为「工作切换」 ,其表格设定如下 ,详情可看书比较详细。
>
> tssltr  dd      00000000h
>         dd      0000ff00h       ;ESP
>         dw      0028h,0000h     ;SS.0
>         dd      0,0,0,0,0
>         dw      offset enter_v86,0000h      ;EIP
>         dd      00000200h       ;EFlag
>         dd      0,0,0,0
>         dd      0000ff00h       ;ESP
>         dd      0,0,0
>         dw      0010h,0000h     ;ES.0
>         dw      0008h,0000h     ;CS.0
>         dw      0028h,0000h     ;SS.0
>         dw      0010h,0000h     ;DS,0
>         dw      0010h,0000h     ;FS.0
>         dw      0010h,0000h     ;GS.0
>         dw      0000h,0000h     ;LDT.0
>         dw      0000h,0068h     ;0.IOMAP起点
>         db      1000h dup (0)   ;4K IOMAP 表
>         dw      0ffffh
>
>
>     如果您的程式使用 JMP XXXX:YYYYYYYY 的方式跳到本区节的话 ,原本指定的
> YYYYYYYY 将无用途 ,因为所有的暂存器将被替换成此表格的数值(含CS.EIP) ,并
> 完成等级切换的动作。
>
>
> --------------------------------------------------------------------------
> ┌───────┐
> │进入 V86  模式│
> └───────┘
>
>         cli
>         lgdt    fword ptr cs:gdtadds
>         lidt    fword ptr cs:idtadds
>         mov     eax,cr0
>         or      al,01h
>         mov     cr0,eax
>         mov     bx,0018h
>         ltr     bx            ;发生工作切换时 ,SS:ESP 将参考 0018 的区段表格
>         jmp     0020h:0000h   ;进入工作切换 ,会跳到此表格内指定的 CS:EIP
>                                (LTR.JMP 不可指向同一表格)
>
> enter_v86 :                   ;假设您已将 CS:EIP 指向此处继续执行
>         xor     eax,eax
>         mov     ax,code
>         push    eax             ;GS
>         push    eax             ;FS
>         push    eax             ;DS
>         push    eax             ;ES
>         push    eax             ;SS
>         mov     ax,0f000h
>         push    eax             ;ESP
>         mov     eax,00023000h   ;设定VM=1    等级=3
>         push    eax             ;Eflag
>         xor     eax,eax
>         mov     ax,code
>         push    eax             ;CS
>         mov     ax,offset return_dos
>         push    eax             ;EIP
>         clts                    ;将 387 切换成 32 位元模式
>         iretd                   ;回到 V86 (共弹出24h BYTE)
>
> 紧接著就程式回到 V86 下继续执行著...
> --------------------------------------------------------------------------
> ┌────────┐
> │中断向量表的处理│
> └────────┘
>
>     在保护模式下 ,产生中断後 ,会切回保护模式 ,於是您必需去呼叫原先 V86 下
> 的中断表 ,以便让程式能够正确执行。
>
>     V86 下发生中断後 ,CPU 会取出 LTR 所设定 SS:ESP 值 ,然後将 V86 下的众
> 多暂存器暂存於此 ,不过因为 CPU 已变成 32 位元模式 ,所以堆叠内的 SP 值会被
> 减 12 byte (原本是6byte ,用以摆放 IP.CS.FLAG) ,且堆叠内的EIP值会指向 V86
> 下的 INT_X 的下一行 ,因此你必需先将 V86 下的 SP 值加 6 byte ,并修改 V86 下
> 的 SS:SP 里的内容为 INT_X 的下一行 ,然後将保护模式下的堆叠 CS:EIP 值指向原
> V86 下的中断位址 ,这样才可以带动 V86 下的中断表。
>
>     底下仅列出部份中断的处理方式....您必需处理 256 个中断表。
>
> new_20 :
>         push    0020h
>         jmp     int_emu
> new_21 :
>         push    0021h
>         jmp     int_emu
> new_22 :
>         push    0022h
>         jmp     int_emu
> new_23 :
>         push    0023h
>         jmp     int_emu
>
> int_emu :
>         push    bp
>         mov     bp,sp
>         add     bp,04h
>         push    eax
>         push    ebx
>         mov     ax,0010h                ;
>         mov     ds,ax                   ;(Selector 0010h 的 Base=0)
>         mov     ax,ss:[bp+0ch]          ;
>         sub     ax,06h                  ;改V86的SP-6
>         mov     ss:[bp+0ch],ax          ;
>         xor     eax,eax                 ;
>         xor     ebx,ebx                 ;修改V86下的SS:SP ,帮它摆入
>         mov     ax,ss:[bp+10h]          ;INT_X 後的下一行位址 ,供V86
>         shl     eax,04h                 ;下的程式IRET返回INT_X的下一行用
>         mov     bx,ss:[bp+0ch]          ;
>         add     ebx,eax                 ;
>         mov     ax,ss:[bp+00h]          ;
>         mov     ds:[ebx],ax             ;
>         mov     ax,ss:[bp+04h]          ;
>         mov     ds:[ebx+02h],ax         ;
>         mov     ax,ss:[bp+08h]          ;
>         mov     ds:[ebx+04h],ax         ;
>         nop
>         xor     ebx,ebx                 ;
>         mov     bx,ss:[bp-02h]          ;
>         shl     ebx,02h                 ;
>         mov     ax,ds:[ebx]             ;IRETD 後到V86中断表所指的位址继续执行
>         mov     ss:[bp+00h],ax          ;(查 0000:0000 的中断表)
>         mov     ax,ds:[ebx+02h]         ;
>         mov     ss:[bp+04h],ax          ;
>         mov     eax,ss:[bp+08h]
>         or      eax,00032000h           ;等级=3  VM=1
>         and     eax,0fffffeffh          ;关闭'T'旗标
>         mov     ss:[bp+08h],eax
>         pop     ebx
>         pop     eax
>         pop     bp
>         add     sp,02h
>         iretd
>
> --------------------------------------------------------------------------
> ┌──w───┐
> │相容性的处理│
> └──────┘
>
>     或许您曾经在挂入 QEMM386、EMM386 之後 ,在 V86 下执行 MOV EAX,CR0 的指
> 令 ,但是前面笔者提到读写系统暂存器必需在最高等级才可执行 ,为什麽 User  仍
> 可在最低等级下执行本命令呢 ?  底下是欺骗方式。
>
>
>         (User)  V86 下执行 MOV EAX,CR0
>                      ↓
>                 发生 General Protection 0D
>                 CPU 自动切入保护模式 ,并执行 INT_0D 的处理程式
>                 (堆叠里多储存了错误代码 DWORD)
>                      ↓
>         (EMM)   检查发生错误的原因
>                 读取 EAX,CR0 (因此时已是最高等级 ,本行可以正确执行)
>                      ↓
>         (EMM)   修改堆叠内的 EIP 值 ,指向下一行指令
>                      ↓
>         (EMM)   修改使用者等级 3 / 设定 VM 旗标等於 1
>                      ↓
>         (EMM)   ESP 值扣掉错误代码 4byte
>                      ↓
>         (EMM)      IRETD 切回 V86
>                      ↓
>         (User) 使用者取得 EAX 的数值
>
>     由於程式有一大半在保护模式下执行 ,所以使用者根本感觉不到 ,只知道自己真
> 的读到系统暂存器。这便是 EMM 系的欺骗手段。
>
>
>     再举例来说 ,笔者所写的 DEBUGOS ,在这个系统下您可以执行 MOV EAX,CR0 ,就
> 是因为笔者有加以处理 ,可是笔者检查保护模式错误原因里并没有处理 MOV EBX,CR0
>  ,於是在这系统下 ,您就没办法执行本命令了 ,您可以试试看。
>
>     本来标准的程式是不会在 V86 下读写系统暂存器 ,可是确实也有不正常的程式
> 是这样搞的 ,例如倚天中文会 MOV EAX,CR3 ,或是一些保护程式会写入除错暂存器
> (DRx)。所以为了相容性 ,这些最好做进去。
>
> --------------------------------------------------------------------------
> ┌──────┐
> │拦 I/O  能力│
> └──────┘
>
>     在进入保护模式後 ,您可以在 IOMAP 里设定某些位元 ,用以管理 I/O 埠 ,每个
> Bit 表示一个埠 ,4K=32768埠 ,当您设定此位元後 ,等级低的人读写此埠就会发生
> General Protection Error 0D ,然後你就可以加以处理啦 ,不过 I/O MAP 只能设定
> 为读写时发生异常 ,无法单独设定为仅读取才发生或仅写入才发生 ,因此拦 I/O 的
> 人要自己去辨认原因。这点也是很麻烦的。
>
> --------------------------------------------------------------------------
>     切入 V86 後 ,还有很多问题要处理 ,包含上面提到的部份 ,和 HIMEM.SYS 相容
> 啦 ,这些问题有待您自己去寻找解决办法。
>
>     有关保护模式的部份笔者只能介绍到此 ,再下去更深的理论我不会解释 ,也掰不
> 出来 ,不过您如果会切入 V86 ,自然也能够写在保护模式下执行的程式才对。如有问
> 题再来信。
>
>     DEBUGOS 这个小软体已经摆於 KPEMU300.ZIP 内了 ,这是一套模拟 KeyPro 的小
> 软体。
>
> ┌───────────────────────────────────┐
> │  Soft Bugger 软体蛀虫 90:90/2                    软体新技术的实行者  │
> │  BBS:02-5955461 24HR          ID:Werong Ho               -- 软蛀 --  │
> └───────────────────────────────────┘



>                                                     ┌┐┌┐∞
> 【 80386 保护模式简介四 】                     ┘└┘└┘
> ==========================================================================
> 前言∶
>     本集的内容主要是由第三集改进解释的方式 ,重新再介绍一?V86 拦 I/O 的
> 动作 ,因为好像有不少人对於第三集的解释方式一知半解....可能是我写的还不是
> 很好吧 ,所以重写一次。
>
> --------------------------------------------------------------------------
> ┌────────┐
> │80386 暂存器介绍│
> └────────┘
>
>     80386 的暂存器除了扩充成 32 位元以外 ,亦增加了许多新的暂存器 ,除了一般
> 使用者暂存器(AX.BX....SI.DI)各位已经了解以外 ,也增加了系统暂存器、以及扩充
> 的旗标 暂存器....等等。
>
>
> A.使用者暂存器   → EAX.EBX.ECX.EDX.ESI,EDI.EBP.ESP
>
> B.指令指标暂存器 → CS.EIP 两个暂存器
>
> C.区段暂存器     → CS.SS.DS.ES.FS.GS
>     虽然 80386 已经进入 32 位元时代 ,但是这几个暂存器仍是 16 位元的 ,且多
>     了 FS.GS 两个暂存器 ,这两个暂存器并无特殊意义 ,各位可以把它当做 DS.ES
>     来看待。
>
> D.系统暂存器
>     A. 控制暂存器:包含 CR0.CR2.CR3 三个 ,各位可能看到漏了一个 CR1 ,原因是
>        386.486.586 都没有此暂存器
>     B. 除错暂存器:包含 DR0.DR1.DR2.DR3.DR6.DR7 共六个 ,也是漏了 DR4.DR5 两
>        个 ,原因同上
>     C. 保护模式分段控制:IDT.GDT.LDT.TR
>
> 注:自 586 起新增 CR4.DR4.DR5 系统暂存器
> --------------------------------------------------------------------------
> ┌────┐
> │工作切换│
> └────┘
>
>     当您设定某些系统暂存器以後 ,电脑并不会马上反应所设定的工作 ,必需透过工
> 作切换的动作才会起动 ,这个工作切换很难用文字表达 ,笔者认为工作切换就是等级
> 切换的动作。可造成工作切换的指令包含 INT_X 、JMP TSS区段...等 ,其中 INT_X
> 是指在 V86下的程式若发生中断 ,电脑会自动切换至保护模式 ,并呼叫保护模式下的
> 中断处理程式 ,再由保护模式下的程式决定是否呼叫原来 V86  下的中断向量表 ,而
> 这切换到保护模式、再切回 V86 下 , 共发生两次工作切换......
>
> ┌──┐
> │等级│
> └──┘
>
>     保护模式下 ,等级共有 0.1.2.3 四个等级 ,其中第0级等级最高 ,第3级最低 ,
> 而0级因为是最高等级 ,因此也有人称为「特权等级」 ,而应用程式的等级为多少呢?
> 这表示在 EFLAG 的 IOPL (BIT12.13) 里 ,在 V86 下的等级多半是最低的第3级 ,所
> 以此值为 '11'。
>
>     或许各位会认为自己去修改这个旗标将自己的等级调高就好了 ,事实上改好後还
> 要经过工作切换的动作 ,等级才能被修改 ,而经过工作切换的动作後 ,你的程式控制
> 权将转交给别人 ;再简单的说 ,发生 INT_X 时 ,电脑会将等级切换成最高等级(事实
> 上是由中断表上决定的) ,并进入保护模式 ,之後保护模式的程式再来决定将使用者的
> EFLAG 切成什麽等级 ,然後再 IRETD切回 V86 ,於是应用程式根本抢不过最早进入保
> 护模式的家伙。(这样你有办法在V86下抢到最高等级吗....不可能嘛)
>
>     等级的高低可以决定自己有多少控制权 ,例如等级最高的人才可以读写系统暂存
> 器 ,其馀的人想读写系统暂存器都会发生 General Protection Error 0D ,你可以把
> 它想像成等级不够 ,却要读取系统资源 ,会发生 INT_0D  ,而原本这行指令将不会被
> 执行 ,而堆叠里所摆的 EIP 值也停在这行上面 ,如果 INT_0D  的处理程式不去跳过
> 这个指令 ,则会永远停在这个指令里(形同当机)。    ※注二
>
>     在 V86 下发生中断时 ,会自动 PUSH EIP.CS.EFLAG.ESP.SS......数个暂存器 ,
> 并自动将 SS.ESP 的值替换 ,以免发生中断时 ,会动用到 V86  的堆叠 ,可是如果发
> 生的是 General Protection Error(俗称异常),则会在 PUSH EIP 之前再多摆入一个
> DWORD  的错误代码 ,如果您的程式在 IRETD 前不减去这个可能存在的错误代码 ,则
> 会发生不可预知的後果。这也是保护模式下的程式不好写的原因之一。 而 SS 与ESP
> 所替换的值 ,则是最初进入保护模式後 ,由最高等级的人决定的(摆於TSS区段)。
>
>     第二集里笔者有介绍 GDT 表 ,其中有个 93 代表可写区段 ,如果设成 89 ,则表
> 示此区段是 TSS 表格 ,再由 TR 暂存器来指定发生中断时 ,取用那一个区段的表格.
>
> 举例来说 ,下面是 GDT 表格
>
> gdttab  db      000h,000h,000h,000h,000h,000h,000h,000h ;00
>         db      0ffh,0ffh,000h,000h,000h,09bh,000h,000h ;08
>         db      0ffh,0ffh,000h,000h,000h,093h,08fh,000h ;10
>         db      0ffh,0ffh,000h,000h,000h,089h,000h,000h ;18
>         db      0ffh,0ffh,000h,000h,000h,089h,000h,000h ;20
>         db      0ffh,0ffh,000h,000h,000h,093h,000h,000h ;28
>         db      0ffh,007h,000h,000h,000h,093h,000h,000h ;30
>         db      0ffh,0ffh,000h,080h,00bh,093h,000h,000h ;38
>         db      0ffh,0ffh,000h,000h,000h,093h,000h,000h ;40
>
>   我们可以看到 18.20 两个 Selector 正好就是 89h ,也就是说它们俩个都可以是
> TSS 描述表格 ,如果 MOV AX,0018、LTR AX ,则表示发生工作切换时 ,取用 0018 的
> 描述表格。
>
> 注一:General Protection Error 发生後会去呼叫该中断 ,但是一般产生中断只会
>      存入 EIP.CS.EFLAG.ESP.SS.... ,但是发生 General Protection Error 的话
>      堆叠会存入 错误代码.EIP.CS.EFLAG.ESP.SS.....
>      堆叠多存放了一个"错误代码" ,记得在切回 V86 前要将此值减去唷 !!
>
> 注二:前面说发生 GP Error #0D 等於呼叫 INT_0D ,这只能说是半对 ,原因『注一』
>      已说明 ,不再重复。
>
> --------------------------------------------------------------------------
> ┌──────┐
> │TSS 表格简介│
> └──────┘
>     TSS 也有人称为「工作切换」 ,其表格设定如下 ,详情可看书比较详细。
>
> tssltr  dd      00000000h
>         dd      0000ff00h       ;ESP
>         dw      0028h,0000h     ;SS.0
>         dd      0,0,0,0,0
>         dw      offset enter_v86,0000h      ;EIP
>         dd      00000200h       ;EFlag
>         dd      0,0,0,0
>         dd      0000ff00h       ;ESP
>         dd      0,0,0
>         dw      0010h,0000h     ;ES.0
>         dw      0008h,0000h     ;CS.0
>         dw      0028h,0000h     ;SS.0
>         dw      0010h,0000h     ;DS,0
>         dw      0010h,0000h     ;FS.0
>         dw      0010h,0000h     ;GS.0
>         dw      0000h,0000h     ;LDT.0
>         dw      0000h,0068h     ;0.IOMAP起点
>         db      1000h dup (0)   ;4K IOMAP 表
>         dw      0ffffh
>
>
>     如果您的程式使用 JMP XXXX:YYYYYYYY 的方式跳到本区节的话 ,原本指定的
> YYYYYYYY 将无用途 ,因为所有的暂存器将被替换成此表格的数值(含CS.EIP) ,并
> 完成等级切换的动作。
>
>
> --------------------------------------------------------------------------
> ┌───────┐
> │进入 V86  模式│
> └───────┘
>
>         cli
>         lgdt    fword ptr cs:gdtadds
>         lidt    fword ptr cs:idtadds
>         mov     eax,cr0
>         or      al,01h
>         mov     cr0,eax
>         mov     bx,0018h
>         ltr     bx            ;发生工作切换时 ,SS:ESP 将参考 0018 的区段表格
>         jmp     0020h:0000h   ;进入工作切换 ,会跳到此表格内指定的 CS:EIP
>                                (LTR.JMP 不可指向同一表格)
>
> enter_v86 :                   ;假设您已将 CS:EIP 指向此处继续执行
>         xor     eax,eax
>         mov     ax,code
>         push    eax             ;GS
>         push    eax             ;FS
>         push    eax             ;DS
>         push    eax             ;ES
>         push    eax             ;SS
>         mov     ax,0f000h
>         push    eax             ;ESP
>         mov     eax,00023000h   ;设定VM=1    等级=3
>         push    eax             ;Eflag
>         xor     eax,eax
>         mov     ax,code
>         push    eax             ;CS
>         mov     ax,offset return_dos
>         push    eax             ;EIP
>         clts                    ;将 387 切换成 32 位元模式
>         iretd                   ;回到 V86 (共弹出24h BYTE)
>
> 紧接著就程式回到 V86 下继续执行著...
> --------------------------------------------------------------------------
> ┌────────┐
> │中断向量表的处理│
> └────────┘
>     在 V86 下产生中断後 ,电脑会自动切回保护模式 ,并从 LTR 所指定的位址取得
> TSS 表格 ,然後以表格内的资料重新设定 SS.ESP ,然後把 V86 下的各暂存器值摆入
> 此堆叠内 ,在此需注意的是它摆放在堆叠的资料是32位元方式 ,所以对於 DS.ES....
> 这类16位元暂存器摆於堆叠 ,不足部份补 '0000' ,用以凑足 32Bit。
>
>     简单来说 ,在真实模式下或 V86下使用一组 SS:SP ,一但透过中断i入保护模式
> 後 ,原先的 SS:SP 暂存器将被置换另一组数值(定义於TSS表) ,然後再将大部份的暂
> 存器值摆放在这个新堆叠区内(包含SS.ESP) ,直到执行 IRETD 回到 V86 後 ,SS:ESP
> 暂存器值才会从原先堆叠中弹出。换句话说 ,在 V86下发生中断会使用自己的堆叠 ,
> 而不会破坏 V86 的堆叠区 ,这也就是为什麽像 S-ICE 除错程式执行 'T' 的命令却
> 不会更动 User 的堆叠资料。
>
>     存於保护模式堆叠内的 CS:EIP 会指向 V86下 "INT_X" 的下一行 ,而 SS:SP 值
> 却仍维持原来数值(不像以往产生中断会自动减6 ,然後堆叠内摆入 FLAG.CS.IP),因
> 此保护模式下处理中断的程式必需修改 V86 的 SP 值减6 ,并将 V86 的 CS.IP.FLAG
> 摆入 V86 的堆叠 ,最後再去查 0000:0000 的表格 ,将保护模式堆叠内的 CS:EIP 值
> 修改、指向此中断向量表 ,最後保护模式的程式执行 IRETD 返回 V86 後 ,跳到 V86
> 下的中断所指位址 ,这样便完成整个模拟 DOS 中断的效果。
>
> PS:保护模式下堆叠会存放 EFLAG.EIP.ECS.ESP.SS...... 忘了 ,比 Real Mode 还要
>    多好多喔。
>
>     底下仅列出部份中断的处理方式....您必需处理 256 个中断表。
>
> new_20 :
>         push    0020h
>         jmp     int_emu
> new_21 :
>         push    0021h
>         jmp     int_emu
> new_22 :
>         push    0022h
>         jmp     int_emu
> new_23 :
>         push    0023h
>         jmp     int_emu
>
> int_emu :
>         push    bp
>         mov     bp,sp
>         add     bp,04h
>         push    eax
>         push    ebx
>         mov     ax,0010h                ;
>         mov     ds,ax                   ;(Selector 0010h 的 Base=0)
>         mov     ax,ss:[bp+0ch]          ;
>         sub     ax,06h                  ;改V86的SP-6
>         mov     ss:[bp+0ch],ax          ;
>         xor     eax,eax                 ;
>         xor     ebx,ebx                 ;修改V86下的SS:SP ,帮它摆入
>         mov     ax,ss:[bp+10h]          ;INT_X 後的下一行位址 ,供V86
>         shl     eax,04h                 ;下的程式IRET返回INT_X的下一行用
>         mov     bx,ss:[bp+0ch]          ;
>         add     ebx,eax                 ;
>         mov     ax,ss:[bp+00h]          ;
>         mov     ds:[ebx],ax             ;
>         mov     ax,ss:[bp+04h]          ;
>         mov     ds:[ebx+02h],ax         ;
>         mov     ax,ss:[bp+08h]          ;
>         mov     ds:[ebx+04h],ax         ;
>         nop
>         xor     ebx,ebx                 ;
>         mov     bx,ss:[bp-02h]          ;
>         shl     ebx,02h                 ;
>         mov     ax,ds:[ebx]             ;IRETD 後到V86中断表所指的位址继续执行
>         mov     ss:[bp+00h],ax          ;(查 0000:0000 的中断表)
>         mov     ax,ds:[ebx+02h]         ;
>         mov     ss:[bp+04h],ax          ;
>         mov     eax,ss:[bp+08h]
>         or      eax,00032000h           ;等级=3  VM=1
>         and     eax,0fffffeffh          ;关闭'T'旗标
>         mov     ss:[bp+08h],eax
>         pop     ebx
>         pop     eax
>         pop     bp
>         add     sp,02h
>         iretd
>
> --------------------------------------------------------------------------
> ┌──────┐
> │拦 I/O  能力│
> └───w──┘
>
>     TSS 表格内除了可定义产生工作切换後 ,SS.ESP.DS.ES....各暂存器替换值 ,也
> 可以开一块记忆体做 IOMAP ,这块记忆体每个 Bit 代表一个 PORT ,一般习惯是开4K
> 大小 (65536埠),当某位元设定为 '1' 後 ,只要不是最高等级的人去读写此埠 ,都会
> 发生 GP Err #0D ,当然在最低等级的 V86 程式也不例外 ,发生此错误後 ,就形同拦
> 到 I/O  动作了 ,紧接著透过最高等级的处理程式去判断发生错误的原因 ,例如判断
> 程式码是否为 『EC  IN AL,DX』、『EE  OUT DX,AL』 ,或是其它程式码 ,就可以分
> 辨发生的原因是读或写产生的 ,d到 I/O 後 ,你是否会写骗 I/O 的程式 ?
>
>     以 S-ICE 的拦 I/O 能力为例 ,它先使用 IO-MAP 的方式去拦 I/O ,然後再判别
> "EE.E4.EC.E6...." 等等程式码。
>
> 注:IOMAP 表是也是 TSS 表格的一部份。
> --------------------------------------------------------------------------
> ┌───────┐
> │相容性的处理一│      系统暂存器的相容处理法
> └───────┘
>
>     或许您曾经在挂入 QEMM386、EMM386 之後 ,在 V86  下执行 MOV EAX,CR0 的指
> 令 ,但是前面笔者提到读写系统暂存器必需在最高等级才可执行 ,为什麽 User 仍可
> 在最低等级下执行本命令呢 ?  底下是欺骗方式。
>
>
>         (User)  V86 下执行 MOV EAX,CR0
>                      ↓
>                 发生 General Protection 0D
>                 CPU 自动切入保护模式 ,并执行 INT_0D 的处理程式
>                 (堆叠里多储存了错误代码 DWORD)
>                      ↓
>         (EMM)   检查发生错误的原因
>                 读取 EAX,CR0 (因此时已是最高等级 ,本行可以正确执行)
>                      ↓
>         (EMM)   修改堆叠内的 EIP 值 ,指向下一行指令
>                      ↓
>         (EMM)   修改使用者等级 3 / 设定 VM 旗标等於 1
>                      ↓
>         (EMM)   ESP 值扣掉错误代码 4byte
>                      ↓
>         (EMM)      IRETD 切回 V86
>                      ↓
>         (User) 使用者取得 EAX 的数值
>
>     由於程式有一大半在保护模式下执行 ,所以使用者根本感觉不到 ,只知道自己真
> 的读到系统暂存器。这便是 EMM 系的欺骗手段。
>
>     本来标准的程式是不会在 V86  下读写系统暂存器 ,可是确实也有不正常的程式
> O这样搞的 ,例如倚天中文会执行 MOV EAX,CR3 ,或是一些保护程式会写入除错暂存
> 器 (DRx)。所以为了相容性 ,这些最好做进去。
>
> 注:判别发生的原因也可以利用判断 I/O 的那种方法 ,但写起来很麻烦。
> --------------------------------------------------------------------------
> ┌───────┐
> │相容性的处理二│      HIMEM.SYS
> └───────┘
>     HIMEM.SYS 是一个可以控制 1MB  以外记忆体的程式 ,不过之前笔者有提过 ,要
> 读写超过 1MB  以外的记忆体必需进入保护模式才行(据说有後门可用) ,那麽载入自
> 己的保护模式程式後 ,再遇到呼叫 HIMEM.SYS 去搬移 1MB 以外的记忆体 ,电脑竟然
> 会发生 GP Err #0D ,原来这是因为 HIMEM.SYS 在执行搬移记忆体的命令後会去呼叫
> BIOS 的 AH=87h  INT_15h 去搬记忆体 ,换句话说就是因为这个 BIOS 中断会进入保
> 护模式去搬记忆体 ,所以才会造成当机 ,因此你的保护模式介面程式必需去模拟这个
> BIOS 函式 ,就可以与 HIMEM.SYS 相容了。
>
> 注:BIOS AH=87h INT_15h 会重设 GDT.IDT 表 ,然後进入保护模式去搬记忆体 ,然後
>    就当在 LIDT 或 LGDT 的命令上。
>
>    另外如果你的程式摆在 1MB 以上的记 擐 }去执行 ,还会有另一个问题产生 ,
>    不过如果你已经学会上面的这些功能 ,再尝试去写个程式去试试 ,你自然会知道
>    它会发生什麽问题 ,解决的办法也很简单 ,你一定会解决。
> --------------------------------------------------------------------------
>     切入 V86  後 ,还有很多问题要处理 ,不过上面提到的两个问题如果你都能处理
> 的话 ,基本上就不会有其它大问题 ,等你会进入保护模式後 ,再来学习  VCPI、DPMI
> 就很简单了。
>
>     如果各位会切入保护模式的话 ,接下来应该是学习 VCPI 的切入方式 ,虽然有很
> 多 y有介绍 ,但是要真正了解并不容易。建议各位去买套大宇出品的激斗战士、战
> 国策 ,它的外加保护就是切入保护模式的最佳范例 ,包含透过 VCPI、自己切286.386
> 保护模式 ,虽然这是不道德的行为 ,但是却是一个最佳范本。花个五百块学新知绝对
> 划算。
>
>     有关保护模式的部份笔者暂时介绍到此 ,下一集笔者将为您介绍虚拟记忆体 ,如
> 果情况允许 ,还会顺便介绍更难懂的分页机能。教各位如何写出类似 S-ICE 的 BPR
> 功能 ,锁定某一块记忆体的读写状态。
>
>
> ┌───────────────────────────────────┐
> │  Soft Bugger 软体蛀虫 90:90/2                    软体新技术的实行者  │
> │  BBS:02-5955461 24HR          ID:Werong Ho               -- 软蛀 --  │
> └───────────────────────────────────┘



>
>                                                     ┌┐┌┐∞
> 【 80386 保护模式简介五 】                     ┘└┘└┘
> ==========================================================================
> 前言∶
>     底下是进入保护模式、进入 V86 的精简范例 ,执行前请确定 CPU 是处在真实模
> 式 ,程式码因为用到 386 指令 ,请用 TASM 3.1 来编译。
> --------------------------------------------------------------------------
> ┌──────┐
> │进入保护模式│
> └──────┘
>
>     进入保护模式的程式范例 ,其目地是进入保护模式 ,并在保护模式下用绝对记忆
> 体读写的方式 ,直接将 'Protection Mode !' 字串写入 Video Ram (B800:0000) ,
> 本程式以最精简的方式撰写 ,没有任何错误处理 ,因此请确定电脑现在处在真实模式
> 下才可执行本程式。(禁挂 EMM 系保护模式软体)
>
>     程式流程如下∶(底下所指记忆体位址皆为 32bit 绝对位址)
>
>         1. 设定 GDTtab 表所在的记忆体位址填入 GDTadds
>
>         2. 设定 Selector 0008 的记忆体起始位址就是现在 CS 的记忆体位址
>            设定 Selector 0010 的记忆体起始位址就是现在 CS 的记忆体位址
>                 Selector 0018 的记忆体起始位址就是 000B8000 = (B800:0000)
>
>         3. 执行 LGDT FWORD PTR CS:GDTadds 告诉 CPU 一但进入保护模式 ,各
>                 区段的记忆体起始位址、长度
>
>         4. 设定 CR0 的 Bit0 = '1' ,并透过 JMP 指令进入保护模式
>              ※ 进入保护模式後 ,DS.ES.SS.CS.GS.FS 等等暂存器定址方式不再
>                 是 Segment ,而变成 Selector
>
>         5. 秀字 将 0010:MSG_1 搬到 0018:0000
>                 意即将 'Protection Mode !' 字串搬到 Video Ram 去
>
>         6. 设定 CR0 的 Bit0 = '0' ,并透过 JMP 指令回到真实模式
>              ※ 回到真实模式後 ,DS.ES.SS.CS.GS.FS 等等暂存器定址方式不再
>                 是 Selector ,而变成 Segment
>
>         5. 秀字 将 CS:MSG_2 搬到 B800:00A0
>                 意即将 'Return Real Mode !' 字串搬到 Video Ram 去
>
>         6. 结束程式
>
> -----------------------------  P.ASM  ------------------------------------
> code    segment
>         assume  cs:code,ds:code
> .386p
> start   proc    near
>         jmp     next
> gdtadds dw      001fh,0000h,0000h
> gdttab  db      000h,000h,00h,00h,00h,00h,00h,00h ;00 Null
>         db      0ffh,0ffh,00h,00h,00h,9bh,00h,00h ;08 PRG Seg
>         db      0ffh,0ffh,00h,00h,00h,93h,00h,00h ;10 PRG Seg
>         db      0ffh,0ffh,00h,80h,0bh,93h,00h,00h ;18 B8000
> msg_1   db      'Protection Mode !'
> msg_2   db      'Return Real Mode !'
>
> next :
>         xor     eax,eax                 ;
>         xor     ebx,ebx                 ;
>         mov     ax,cs                   ;设定 GDTadds
>         shl     eax,04h                 ;
>         mov     bx,offset gdttab        ;
>         add     eax,ebx                 ;
>         mov     di,offset gdtadds+02h   ;
>         mov     cs:[di],eax             ;
>         NOP
>         xor     eax,eax                 ;
>         xor     ebx,ebx                 ;
>         mov     ax,cs                   ;
>         shl     eax,04h                 ;
>         mov     di,offset gdttab+08h    ;设定 GDTtab 内的
>         mov     si,offset gdttab+10h    ;Selector 0008 及 0010
>         mov     cs:[di+02h],ax          ;两个段落的记忆体起始位址
>         mov     cs:[si+02h],ax          ;
>         shr     eax,10h                 ;
>         mov     cs:[di+04h],al          ;
>         mov     cs:[si+04h],al          ;
>         mov     cs:[di+07h],ah          ;
>         mov     cs:[si+07h],ah          ;
>         NOP
>         cli
>         lgdt    fword ptr cs:gdtadds    ;载入 GDT 表格
>         mov     eax,cr0             ;
>         or      al,01h              ;
>         mov     cr0,eax             ;
>         jmp     protection_mode     ;进入保护模式
> protection_mode :                   ;
>         mov     ax,0010h                ;
>         mov     ds,ax                   ;
>         mov     si,offset msg_1         ;
>         mov     ax,0018h                ;将 0010:MSG_1 搬到 0018:0000
>         mov     es,ax                   ;
>         mov     di,0000h                ;
>         mov     ah,70h                  ;
>         mov     cx,0011h                ;
>         cld                             ;
> L1 :                                    ;
>         lodsb                           ;
>         stosw                           ;
>         loop    L1                      ;
>         NOP
>         mov     eax,cr0             ;
>         and     al,0feh             ;
>         mov     cr0,eax             ;回到真实模式
>         jmp     return_real_mode    ;
> return_real_mode :                  ;
>         sti
>         mov     ax,cs                   ;
>         mov     ds,ax                   ;
>         mov     si,offset msg_2         ;
>         mov     ax,0b800h               ;
>         mov     es,ax                   ;将 CS:MSG_2 搬到 B800:00A0
>         mov     di,00a0h                ;
>         mov     ah,70h                  ;
>         mov     cx,0012h                ;
>         cld                             ;
> L2 :                                    ;
>         lodsb                           ;
>         stosw                           ;
>         loop    L2                      ;
>         mov     ax,4cffh
>         int     21h
> start   endp
> code    ends
>         end     start
> --------------------------------------------------------------------------
>     因为保护模式下不能呼叫真实模式下的中断 ,所以笔者以直接填写显示卡记忆体
> 的方式秀字。这是一个简单、尚未使用中断向量表的范例。
>
> 注: 所谓一山不容二虎 ,如果已载入其它保护模式的程式 ,那本程式将会与它打架 ,
>     造成电脑当机。
>
> ┌────────┐
> │进入虚拟 86 模式│            为求精简 ,本程式毫无错误处理能力
> └────────┘
> ------------------------  V86.ASM  ---------------------------------------
> code    segment
>         assume  cs:code,ds:code
> .386p
> start   proc    near
>         jmp     next
> gdtadds dw      002fh,0000h,0000h
> gdttab  db      000h,000h,000h,000h,000h,000h,000h,000h ;00 Null
>         db      0ffh,0ffh,000h,000h,000h,09bh,000h,000h ;08 PRG Seg
>         db      0ffh,0ffh,000h,000h,000h,093h,08fh,000h ;10 Dos=Page
>         db      0ffh,0ffh,000h,000h,000h,089h,000h,000h ;18 TSSltr
>         db      0ffh,0ffh,000h,000h,000h,089h,000h,000h ;20 TSSjmp
>         db      0ffh,003h,000h,000h,000h,093h,000h,000h ;28 Stack (1K)
>
> tssltr  dd      00000000h
>         dd      000003ffh       ;ESP
>         dw      0028h,0000h     ;SS.0
>         dd      0,0,0,0,0
>         dw      offset enter_v86,0000h      ;EIP
>         dd      00000200h       ;EFlag
>         dd      0,0,0,0
>         dd      000003ffh       ;ESP
>         dd      0,0,0
>         dw      0010h,0000h     ;ES.0
>         dw      0008h,0000h     ;CS.0
>         dw      0028h,0000h     ;SS.0
>         dw      0010h,0000h     ;DS,0
>         dw      0010h,0000h     ;FS.0
>         dw      0010h,0000h     ;GS.0
>         dw      0000h,0000h     ;LDT.0
>         dw      0000h,0068h     ;0.IOMAP
>         dw      0ffffh
>
> tssjmp  dd      00000000h
>         dd      000003ffh       ;ESP
>         dw      0028h,0000h     ;SS.0
>         dd      0,0,0,0,0
>         dw      offset enter_v86,0000h      ;EIP
>         dd      00000000h       ;EFlag
>         dd      0,0,0,0
>         dd      000003ffh       ;ESP
>         dd      0,0,0
>         dw      0010h,0000h     ;ES.0
>         dw      0008h,0000h     ;CS.0
>         dw      0028h,0000h     ;SS.0
>         dw      0010h,0000h     ;DS,0
>         dw      0010h,0000h     ;FS.0
>         dw      0010h,0000h     ;GS.0
>         dw      0000h,0000h     ;LDT.0
>         dw      0000h,0068h     ;0.IOMAP
> iomap   db      1000h dup (0)
>         dw      0ffffh
>
> buffer1 db      0400h dup (0)           ;Stack
>
> idtadds dw      07ffh,0000h,0000h
> idttab  dw      offset new_00,0008h,0ee00h,0000h,offset new_01,0008h,0ee00h,0000h
>         dw      offset new_02,0008h,0ee00h,0000h,offset new_03,0008h,0ee00h,0000h
>         dw      offset new_04,0008h,0ee00h,0000h,offset new_05,0008h,0ee00h,0000h
>         dw      offset new_06,0008h,0ee00h,0000h,offset new_07,0008h,0ee00h,0000h
>         dw      offset new_08,0008h,0ee00h,0000h,offset new_09,0008h,0ee00h,0000h
>         dw      offset new_0a,0008h,0ee00h,0000h,offset new_0b,0008h,0ee00h,0000h
>         dw      offset new_0c,0008h,0ee00h,0000h,offset new_0d,0008h,0ee00h,0000h
>         dw      offset new_0e,0008h,0ee00h,0000h,offset new_0f,0008h,0ee00h,0000h
>         dw      offset new_10,0008h,0ee00h,0000h,offset new_11,0008h,0ee00h,0000h
>         dw      offset new_12,0008h,0ee00h,0000h,offset new_13,0008h,0ee00h,0000h
>         dw      offset new_14,0008h,0ee00h,0000h,offset new_15,0008h,0ee00h,0000h
>         dw      offset new_16,0008h,0ee00h,0000h,offset new_17,0008h,0ee00h,0000h
>         dw      offset new_18,0008h,0ee00h,0000h,offset new_19,0008h,0ee00h,0000h
>         dw      offset new_1a,0008h,0ee00h,0000h,offset new_1b,0008h,0ee00h,0000h
>         dw      offset new_1c,0008h,0ee00h,0000h,offset new_1d,0008h,0ee00h,0000h
>         dw      offset new_1e,0008h,0ee00h,0000h,offset new_1f,0008h,0ee00h,0000h
>         dw      offset new_20,0008h,0ee00h,0000h,offset new_21,0008h,0ee00h,0000h
>         dw      offset new_22,0008h,0ee00h,0000h,offset new_23,0008h,0ee00h,0000h
>         dw      offset new_24,0008h,0ee00h,0000h,offset new_25,0008h,0ee00h,0000h
>         dw      offset new_26,0008h,0ee00h,0000h,offset new_27,0008h,0ee00h,0000h
>         dw      offset new_28,0008h,0ee00h,0000h,offset new_29,0008h,0ee00h,0000h
>         dw      offset new_2a,0008h,0ee00h,0000h,offset new_2b,0008h,0ee00h,0000h
>         dw      offset new_2c,0
您需要登录后才可以回帖 登录 | 注册

本版积分规则

GMT+8, 2025-7-18 12:53 , Processed in 0.039531 second(s), 16 queries .

© 2001-2025 Discuz! Team. Powered by Discuz! X3.5.

快速回复 返回顶部 返回列表