在保护模式下有很多新的名词 ,包含 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
(出处:http://www.sheup.com)