3 ; Test program: 80386 virtual 8086 mode
4 ; (C) 2010-2012 Jonathan Campbell.
5 ; Hackipedia DOS library.
7 ; This code is licensed under the LGPL.
8 ; <insert LGPL legal text here>
11 ; switching the CPU into 386 16-bit protected mode (and back)
12 ; while playing with the Task State Segment mechanism to
13 ; demonstrate hopping between "ring 0" and "ring 3".
14 ; also to play around with "virtual 8086" mode.
15 bits 16 ; 16-bit real mode
16 org 0x100 ; MS-DOS .COM style
18 ; assume ES == DS and SS == DS and DS == CS
39 mov dx,str_cpu_not_386
40 jmp exit2dos_with_message
43 ; ===== CHECK FOR VIRTUAL 8086 MODE
44 smsw ax ; 386 or higher: If we're in real mode
45 test al,1 ; and bit 0 is already set, we're in virtual 8086
46 jz is_realmode ; and our switch to prot mode will cause problems.
47 mov dx,str_cpu_v86_mode
48 jmp exit2dos_with_message
51 ; ===== WE NEED TO PATCH SOME OF OUR OWN CODE
53 mov word [real_entry_patch+3],ax ; overwrite segment field of JMP SEG:OFF
55 ; ===== BUILD THE GLOBAL DESCRIPTOR TABLE AND GDTR REGISTER
59 shl ax,4 ; BX:AX = 32-bit physical addr of our segment
60 mov word [MY_PHYS_BASE],ax
61 mov word [MY_PHYS_BASE+2],bx
64 adc bx,0 ; BX:AX += offset of GDT
66 mov word [GDTR],MAX_SEL - 1
68 mov word [GDTR+4],bx ; GDTR: limit MAX_SEL-1 base=physical mem addr of GDT
70 mov ax,word [MY_PHYS_BASE]
71 mov bx,word [MY_PHYS_BASE+2]
88 ; NULL selector (NULL_SEL)
94 ; Code selector (CODE_SEL)
95 dec ax ; 0x0000 - 1 = 0xFFFF
99 mov al,[MY_PHYS_BASE+2]
101 stosw ; BASE[23:16] access byte=executable readable
103 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
105 ; Data selector (DATA_SEL)
109 mov ax,[MY_PHYS_BASE]
111 mov al,[MY_PHYS_BASE+2]
113 stosw ; BASE[23:16] access byte=data writeable
115 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
117 ; Data selector (VIDEO_SEL)
123 mov al,0x0B ; BASE=0xB8000
125 stosw ; BASE[23:16] access byte=data writeable
127 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
129 ; Code selector (32-bit) (CODE32_SEL)
130 dec ax ; 0x0000 - 1 = 0xFFFF
132 mov ax,[MY_PHYS_BASE]
134 mov al,[MY_PHYS_BASE+2]
136 stosw ; BASE[23:16] access byte=executable readable conforming
138 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=granular 32-bit BASE[31:24]
140 ; Data selector (32-bit) (DATA32_SEL)
144 mov ax,[MY_PHYS_BASE]
146 mov al,[MY_PHYS_BASE+2]
148 stosw ; BASE[23:16] access byte=data writeable
150 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=granular 32-bit BASE[31:24]
152 ; TSS selector (32-bit) (TSS_SEL)
153 mov ax,TSS_AREA_SIZE - 1
155 mov ax,[MY_PHYS_BASE]
156 mov bx,[MY_PHYS_BASE+2]
161 mov ah,0x89 ; present, non-segment, type=9 (TSS busy)
162 stosw ; BASE[23:16] access byte=data writeable
164 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24]
166 ; TSS selector (32-bit) (TSS_2_SEL)
167 mov ax,TSS_AREA_2_SIZE - 1
169 mov ax,[MY_PHYS_BASE]
170 mov bx,[MY_PHYS_BASE+2]
175 mov ah,0x89 ; present, non-segment, type=9 (TSS non busy)
176 stosw ; BASE[23:16] access byte=data writeable
178 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24]
180 ; TSS selector (32-bit) (TSS_3_SEL)
181 mov ax,TSS_AREA_3_SIZE - 1
183 mov ax,[MY_PHYS_BASE]
184 mov bx,[MY_PHYS_BASE+2]
189 mov ah,0x89 ; present, non-segment, type=9 (TSS non busy)
190 stosw ; BASE[23:16] access byte=data writeable
192 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24]
194 ; LDT selector (32-bit) (LDT_SEL)
195 mov ax,LDT_AREA_SIZE - 1
197 mov ax,[MY_PHYS_BASE]
198 mov bx,[MY_PHYS_BASE+2]
203 mov ah,0x82 ; present, non-segment, type=2 (LDT)
204 stosw ; BASE[23:16] access byte=data writeable
206 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24]
208 ; Code selector (CODE_SEL3)
209 dec ax ; 0x0000 - 1 = 0xFFFF
211 mov ax,[MY_PHYS_BASE]
213 mov al,[MY_PHYS_BASE+2]
215 stosw ; BASE[23:16] access byte=executable readable DPL=3
217 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
219 ; Data selector (DATA_SEL3)
223 mov ax,[MY_PHYS_BASE]
225 mov al,[MY_PHYS_BASE+2]
227 stosw ; BASE[23:16] access byte=data writeable DPL=3
229 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
231 ; Data selector (VIDEO_SEL3)
237 mov al,0x0B ; BASE=0xB8000
239 stosw ; BASE[23:16] access byte=data writeable
241 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
245 cli ; disable interrupts
246 lgdt [GDTR] ; load into processor GDTR
249 ; switch into protected mode
252 jmp CODE_SEL:prot_entry
253 prot_entry: mov ax,DATA_SEL ; now reload the segment registers
268 mov ecx,TSS_AREA_SIZE / 4
272 ; zero the second TSS
275 mov ecx,TSS_AREA_2_SIZE / 4
279 ; set up the task register. for now, leave it at the first one.
283 ; prepare the second one
285 xor eax,eax ; prepare EAX=0
287 mov ecx,0x12345678 ; check value
289 stosd ; TSS+0x00 = back link
291 stosd ; TSS+0x04 = ESP0
293 stosd ; TSS+0x08 = SS0
295 stosd ; TSS+0x0C = ESP1
297 stosd ; TSS+0x10 = SS1
299 stosd ; TSS+0x14 = ESP2
301 stosd ; TSS+0x18 = SS2
303 stosd ; TSS+0x1C = CR3
305 stosd ; TSS+0x20 = EIP
308 stosd ; TSS+0x24 = EFLAGS
310 stosd ; TSS+0x28 = EAX
311 stosd ; TSS+0x2C = ECX
312 stosd ; TSS+0x30 = EDX
313 stosd ; TSS+0x34 = EBX
315 stosd ; TSS+0x38 = ESP
317 stosd ; TSS+0x3C = EBP
318 stosd ; TSS+0x40 = ESI
319 stosd ; TSS+0x44 = EDI
321 stosd ; TSS+0x48 = ES
323 stosd ; TSS+0x4C = CS
325 stosd ; TSS+0x50 = SS
326 stosd ; TSS+0x54 = DS
327 stosd ; TSS+0x58 = FS
328 stosd ; TSS+0x5C = GS
330 stosd ; TSS+0x60 = LDT selector (meh, I don't use it anyway)
332 stosd ; TSS+0x64 = I/O map base=0, T=0
337 ; TSS switch should end up HERE.
338 ; Task register now points to TSS_2_SEL as active task.
339 ; TEST: If the CPU truly loaded state from TSS_2_SEL, all general regs should be zero
340 tss_jump_1: or eax,ebx
349 mov word [es:0],0x4E30 ; '0'
351 out 61h,al ; turn on bell
354 ; TEST: All segment registers except CS should be DATA_SEL
374 jz tss_jump_1_sreg_ok
378 mov word [es:0],0x4E31 ; '1'
380 out 61h,al ; turn on bell
384 ; if the CPU truly saved state into TSS_SEL, the memory location
385 ; corresponding to ECX should be 0x12345678 (because we loaded ECX
386 ; with that value prior to switching state, remember?)
387 cmp dword [TSS_AREA+0x2C],0x12345678
392 mov word [es:0],0x4E32 ; '2'
394 out 61h,al ; turn on bell
398 ; draw directly onto VGA alphanumeric RAM at 0xB8000
405 vdraw1: lodsb ; AL = DS:SI++
409 stosw ; ES:DI = AX, DI += 2
413 ; now, jump into 32-bit protected mode
414 jmp CODE32_SEL:prot32_entry
416 prot32_entry: mov ax,DATA32_SEL
424 ; draw directly onto VGA alphanumeric RAM at 0xB8000
427 mov edi,0xB8000+(80*2)
428 sub edi,[MY_PHYS_BASE]
429 vdraw321: lodsb ; AL = DS:SI++
433 stosw ; ES:DI = AX, DI += 2
437 ; jump 32-bit to 16-bit
438 jmp CODE_SEL:prot32_to_prot
440 prot32_to_prot: mov ax,DATA_SEL
447 ; prepare IDT for our virtual 8086 shenanigans ahead
448 mov si,IDT + (0x3*8) ; INT 3h in case v86 mode doesn't trigger GPF
449 mov word [si+0],tss_3_fail ; base[15:0]
450 mov word [si+2],CODE_SEL
451 mov word [si+4],0x8E00 ; P=1 DPL=0 32-bit interrupt gate
452 mov word [si+6],0x0000 ; base[31:16]
454 mov si,IDT + (0xD*8) ; INT Dh for v86 GPF fault
455 mov word [si+0],tss_3_complete ; base[15:0]
456 mov word [si+2],CODE_SEL
457 mov word [si+4],0x8E00 ; P=1 DPL=0 32-bit interrupt gate
458 mov word [si+6],0x0000 ; base[31:16]
460 ; prepare the third one---ring 3. it will be virtual 8086 mode.
462 xor eax,eax ; prepare EAX=0
464 mov ecx,0x12345678 ; check value
466 stosd ; TSS+0x00 = back link
468 stosd ; TSS+0x04 = ESP0
470 stosd ; TSS+0x08 = SS0
472 stosd ; TSS+0x0C = ESP1
474 stosd ; TSS+0x10 = SS1
476 stosd ; TSS+0x14 = ESP2
478 stosd ; TSS+0x18 = SS2
480 stosd ; TSS+0x1C = CR3
482 stosd ; TSS+0x20 = EIP
485 or eax,0x20000 ; set bit 17 = VM
486 stosd ; TSS+0x24 = EFLAGS
488 stosd ; TSS+0x28 = EAX
489 stosd ; TSS+0x2C = ECX
490 stosd ; TSS+0x30 = EDX
491 stosd ; TSS+0x34 = EBX
493 stosd ; TSS+0x38 = ESP
495 stosd ; TSS+0x3C = EBP
496 stosd ; TSS+0x40 = ESI
497 stosd ; TSS+0x44 = EDI
498 mov ax,word [real_entry_patch+3] ; our real-mode segment
499 stosd ; TSS+0x48 = ES
500 stosd ; TSS+0x4C = CS
501 stosd ; TSS+0x50 = SS
502 stosd ; TSS+0x54 = DS
503 stosd ; TSS+0x58 = FS
504 stosd ; TSS+0x5C = GS
506 stosd ; TSS+0x60 = LDT selector (meh, I don't use it anyway)
508 stosd ; TSS+0x64 = I/O map base=0, T=0
509 ; Call the TSS, so that we can IRET to return to RING 0
512 ; now we are 16-bit RING 3 virtual 8086 mode
513 tss_jump_3: mov ax,0xB800 ; PROVE IT
514 mov es,ax ; BY WRITING TO SCREEN
518 vdraw3: lodsb ; AL = DS:SI++
522 stosw ; ES:DI = AX, DI += 2
526 ; cause an interrupt exception to jump back into protected mode
527 int 3 ; will cause INT 0x0D not INT 0x03 due to virtual 8086 mode
529 ; if INT 3 actually fired execution will land here---because it's WRONG
533 mov word [es:0],0x4E39 ; '9'
538 ; TSS RING-3 test COMPLETE (back into 16-bit protected mode). Switch back to TSS_2.
539 ; NOTE this is where execution is directed for a GPF, But instead of cleaning up
540 ; the stack we just discard it all and continue on. A real v86 monitor would
541 ; return execution to whatever real-mode code they want to execute and handle all
542 ; traps to I/O and interrupt.
547 mov dword [TSS_AREA_2+0x20],tss_3_complete_2
551 ; active task is TSS_2_SEL. Prove we can switch tasks again by modifying
552 ; EIP in TSS_SEL, then switching tasks.
553 mov dword [TSS_AREA+0x20],tss_jump_2
557 ; having switched back to TSS_SEL, the value we left in ECX should still
564 mov word [es:0],0x4E33 ; '3'
566 out 61h,al ; turn on bell
570 ; switch back to real mode.
571 ; unlike the 286, switching back means clearing bit 0 of CR0
572 xor eax,eax ; clear bit 0
575 real_entry_patch:jmp 0x0000:real_entry ; the segment field is patched by code above
576 real_entry: mov ax,cs
583 ; ===== REBUILD GDTR FOR PROPER REAL MODE OPERATION
584 mov word [GDTR],0xFFFF
586 mov word [GDTR+4],0 ; GDTR: limit 0xFFFF base 0x00000000
587 lgdt [GDTR] ; load into processor GDTR
589 mov word [IDTR],0xFFFF
591 mov word [IDTR+4],0 ; IDTR: limit 0xFFFF base 0x00000000
594 ; ====== PROVE WE MADE IT TO REAL MODE
599 vdraw2: lodsb ; AL = DS:SI++
603 stosw ; ES:DI = AX, DI += 2
613 ; ===== EXIT TO DOS WITH ERROR MESSAGE DS:DX
614 exit2dos_with_message:
618 exit2dos: mov ax,4C00h
621 ; 8086 test: EFLAGS will always have bits 12-15 set
632 ; 286 test: EFLAGS will always have bits 12-15 clear
643 cpu_is_386_not: mov ax,1
648 str_cpu_not_386: db "386 or higher required$"
649 str_cpu_v86_mode: db "Virtual 8086 mode detected$"
650 vdraw2_msg: db "This message was drawn on screen back from real mode!",0
651 vdraw3_msg: db "This message was drawn on screen from virtual 8086 mode!",0
652 vdraw_msg: db "This message was drawn on screen from 386 16-bit protected mode!",0
653 vdraw32_msg: db "This message was drawn on screen from 386 32-bit protected mode!",0
655 ; THESE VARIABLES DO NOT EXIST IN THE ACTUAL .COM FILE.
656 ; They exist in the yet-uninitialized area of RAM just beyond the
657 ; end of the loaded COM file image.
662 MY_PHYS_BASE equ IDTR+8
663 GDT equ MY_PHYS_BASE+8
666 TSS_AREA equ IDT+IDT_SIZE
667 TSS_AREA_SIZE equ 2048
668 TSS_AREA_2 equ TSS_AREA+TSS_AREA_SIZE
669 TSS_AREA_2_SIZE equ 2048
670 TSS_AREA_3 equ TSS_AREA_2+TSS_AREA_2_SIZE
671 TSS_AREA_3_SIZE equ 2048
672 LDT_AREA equ TSS_AREA_3+TSS_AREA_3_SIZE