3 ; Test program: 80386 task switching (Task State Segments) to jump privilege levels
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 ; note that the 286 TSS is very similar (with different values
15 ; for the TYPE fields in the GDT, and different register layout)
16 ; so I will not (yet) bother making a 286 version.
17 bits 16 ; 16-bit real mode
18 org 0x100 ; MS-DOS .COM style
20 ; assume ES == DS and SS == DS and DS == CS
41 mov dx,str_cpu_not_386
42 jmp exit2dos_with_message
45 ; ===== CHECK FOR VIRTUAL 8086 MODE
46 smsw ax ; 386 or higher: If we're in real mode
47 test al,1 ; and bit 0 is already set, we're in virtual 8086
48 jz is_realmode ; and our switch to prot mode will cause problems.
49 mov dx,str_cpu_v86_mode
50 jmp exit2dos_with_message
53 ; ===== WE NEED TO PATCH SOME OF OUR OWN CODE
55 mov word [real_entry_patch+3],ax ; overwrite segment field of JMP SEG:OFF
57 ; ===== BUILD THE GLOBAL DESCRIPTOR TABLE AND GDTR REGISTER
61 shl ax,4 ; BX:AX = 32-bit physical addr of our segment
62 mov word [MY_PHYS_BASE],ax
63 mov word [MY_PHYS_BASE+2],bx
66 adc bx,0 ; BX:AX += offset of GDT
68 mov word [GDTR],MAX_SEL - 1
70 mov word [GDTR+4],bx ; GDTR: limit MAX_SEL-1 base=physical mem addr of GDT
72 mov ax,word [MY_PHYS_BASE]
73 mov bx,word [MY_PHYS_BASE+2]
90 ; NULL selector (NULL_SEL)
96 ; Code selector (CODE_SEL)
97 dec ax ; 0x0000 - 1 = 0xFFFF
101 mov al,[MY_PHYS_BASE+2]
103 stosw ; BASE[23:16] access byte=executable readable
105 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
107 ; Data selector (DATA_SEL)
111 mov ax,[MY_PHYS_BASE]
113 mov al,[MY_PHYS_BASE+2]
115 stosw ; BASE[23:16] access byte=data writeable
117 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
119 ; Data selector (VIDEO_SEL)
125 mov al,0x0B ; BASE=0xB8000
127 stosw ; BASE[23:16] access byte=data writeable
129 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
131 ; Code selector (32-bit) (CODE32_SEL)
132 dec ax ; 0x0000 - 1 = 0xFFFF
134 mov ax,[MY_PHYS_BASE]
136 mov al,[MY_PHYS_BASE+2]
138 stosw ; BASE[23:16] access byte=executable readable conforming
140 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=granular 32-bit BASE[31:24]
142 ; Data selector (32-bit) (DATA32_SEL)
146 mov ax,[MY_PHYS_BASE]
148 mov al,[MY_PHYS_BASE+2]
150 stosw ; BASE[23:16] access byte=data writeable
152 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=granular 32-bit BASE[31:24]
154 ; TSS selector (32-bit) (TSS_SEL)
155 mov ax,TSS_AREA_SIZE - 1
157 mov ax,[MY_PHYS_BASE]
158 mov bx,[MY_PHYS_BASE+2]
163 mov ah,0x89 ; present, non-segment, type=9 (TSS busy)
164 stosw ; BASE[23:16] access byte=data writeable
166 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24]
168 ; TSS selector (32-bit) (TSS_2_SEL)
169 mov ax,TSS_AREA_2_SIZE - 1
171 mov ax,[MY_PHYS_BASE]
172 mov bx,[MY_PHYS_BASE+2]
177 mov ah,0x89 ; present, non-segment, type=9 (TSS non busy)
178 stosw ; BASE[23:16] access byte=data writeable
180 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24]
182 ; TSS selector (32-bit) (TSS_3_SEL)
183 mov ax,TSS_AREA_3_SIZE - 1
185 mov ax,[MY_PHYS_BASE]
186 mov bx,[MY_PHYS_BASE+2]
191 mov ah,0x89 ; present, non-segment, type=9 (TSS non busy)
192 stosw ; BASE[23:16] access byte=data writeable
194 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24]
196 ; LDT selector (32-bit) (LDT_SEL)
197 mov ax,LDT_AREA_SIZE - 1
199 mov ax,[MY_PHYS_BASE]
200 mov bx,[MY_PHYS_BASE+2]
205 mov ah,0x82 ; present, non-segment, type=2 (LDT)
206 stosw ; BASE[23:16] access byte=data writeable
208 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24]
210 ; Code selector (CODE_SEL3)
211 dec ax ; 0x0000 - 1 = 0xFFFF
213 mov ax,[MY_PHYS_BASE]
215 mov al,[MY_PHYS_BASE+2]
217 stosw ; BASE[23:16] access byte=executable readable DPL=3
219 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
221 ; Data selector (DATA_SEL3)
225 mov ax,[MY_PHYS_BASE]
227 mov al,[MY_PHYS_BASE+2]
229 stosw ; BASE[23:16] access byte=data writeable DPL=3
231 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
233 ; Data selector (VIDEO_SEL3)
239 mov al,0x0B ; BASE=0xB8000
241 stosw ; BASE[23:16] access byte=data writeable
243 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
247 cli ; disable interrupts
248 lgdt [GDTR] ; load into processor GDTR
251 ; switch into protected mode
254 jmp CODE_SEL:prot_entry
255 prot_entry: mov ax,DATA_SEL ; now reload the segment registers
270 mov ecx,TSS_AREA_SIZE / 4
274 ; zero the second TSS
277 mov ecx,TSS_AREA_2_SIZE / 4
281 ; set up the task register. for now, leave it at the first one.
285 ; prepare the second one
287 xor eax,eax ; prepare EAX=0
289 mov ecx,0x12345678 ; check value
291 stosd ; TSS+0x00 = back link
293 stosd ; TSS+0x04 = ESP0
295 stosd ; TSS+0x08 = SS0
297 stosd ; TSS+0x0C = ESP1
299 stosd ; TSS+0x10 = SS1
301 stosd ; TSS+0x14 = ESP2
303 stosd ; TSS+0x18 = SS2
305 stosd ; TSS+0x1C = CR3
307 stosd ; TSS+0x20 = EIP
310 stosd ; TSS+0x24 = EFLAGS
312 stosd ; TSS+0x28 = EAX
313 stosd ; TSS+0x2C = ECX
314 stosd ; TSS+0x30 = EDX
315 stosd ; TSS+0x34 = EBX
317 stosd ; TSS+0x38 = ESP
319 stosd ; TSS+0x3C = EBP
320 stosd ; TSS+0x40 = ESI
321 stosd ; TSS+0x44 = EDI
323 stosd ; TSS+0x48 = ES
325 stosd ; TSS+0x4C = CS
327 stosd ; TSS+0x50 = SS
328 stosd ; TSS+0x54 = DS
329 stosd ; TSS+0x58 = FS
330 stosd ; TSS+0x5C = GS
332 stosd ; TSS+0x60 = LDT selector (meh, I don't use it anyway)
334 stosd ; TSS+0x64 = I/O map base=0, T=0
339 ; TSS switch should end up HERE.
340 ; Task register now points to TSS_2_SEL as active task.
341 ; TEST: If the CPU truly loaded state from TSS_2_SEL, all general regs should be zero
342 tss_jump_1: or eax,ebx
351 mov word [es:0],0x4E30 ; '0'
353 out 61h,al ; turn on bell
356 ; TEST: All segment registers except CS should be DATA_SEL
376 jz tss_jump_1_sreg_ok
380 mov word [es:0],0x4E31 ; '1'
382 out 61h,al ; turn on bell
386 ; if the CPU truly saved state into TSS_SEL, the memory location
387 ; corresponding to ECX should be 0x12345678 (because we loaded ECX
388 ; with that value prior to switching state, remember?)
389 cmp dword [TSS_AREA+0x2C],0x12345678
394 mov word [es:0],0x4E32 ; '2'
396 out 61h,al ; turn on bell
400 ; draw directly onto VGA alphanumeric RAM at 0xB8000
407 vdraw1: lodsb ; AL = DS:SI++
411 stosw ; ES:DI = AX, DI += 2
415 ; now, jump into 32-bit protected mode
416 jmp CODE32_SEL:prot32_entry
418 prot32_entry: mov ax,DATA32_SEL
426 ; draw directly onto VGA alphanumeric RAM at 0xB8000
429 mov edi,0xB8000+(80*2)
430 sub edi,[MY_PHYS_BASE]
431 vdraw321: lodsb ; AL = DS:SI++
435 stosw ; ES:DI = AX, DI += 2
439 ; jump 32-bit to 16-bit
440 jmp CODE_SEL:prot32_to_prot
442 prot32_to_prot: mov ax,DATA_SEL
449 ; prepare the third one---ring 3
451 xor eax,eax ; prepare EAX=0
453 mov ecx,0x12345678 ; check value
455 stosd ; TSS+0x00 = back link
457 stosd ; TSS+0x04 = ESP0
459 stosd ; TSS+0x08 = SS0
461 stosd ; TSS+0x0C = ESP1
463 stosd ; TSS+0x10 = SS1
465 stosd ; TSS+0x14 = ESP2
467 stosd ; TSS+0x18 = SS2
469 stosd ; TSS+0x1C = CR3
471 stosd ; TSS+0x20 = EIP
474 stosd ; TSS+0x24 = EFLAGS
476 stosd ; TSS+0x28 = EAX
477 stosd ; TSS+0x2C = ECX
478 stosd ; TSS+0x30 = EDX
479 stosd ; TSS+0x34 = EBX
481 stosd ; TSS+0x38 = ESP
483 stosd ; TSS+0x3C = EBP
484 stosd ; TSS+0x40 = ESI
485 stosd ; TSS+0x44 = EDI
487 stosd ; TSS+0x48 = ES
489 stosd ; TSS+0x4C = CS
491 stosd ; TSS+0x50 = SS
492 stosd ; TSS+0x54 = DS
493 stosd ; TSS+0x58 = FS
494 stosd ; TSS+0x5C = GS
496 stosd ; TSS+0x60 = LDT selector (meh, I don't use it anyway)
498 stosd ; TSS+0x64 = I/O map base=0, T=0
499 ; Call the TSS, so that we can IRET to return to RING 0
503 ; now we are 16-bit RING 3
504 tss_jump_3: mov ax,VIDEO_SEL3 ; PROVE IT
505 mov es,ax ; BY WRITING TO SCREEN
509 vdraw3: lodsb ; AL = DS:SI++
513 stosw ; ES:DI = AX, DI += 2
520 ; TSS RING-3 test COMPLETE
523 ; active task is TSS_2_SEL. Prove we can switch tasks again by modifying
524 ; EIP in TSS_SEL, then switching tasks.
525 mov dword [TSS_AREA+0x20],tss_jump_2
529 ; having switched back to TSS_SEL, the value we left in ECX should still
536 mov word [es:0],0x4E33 ; '3'
538 out 61h,al ; turn on bell
542 ; switch back to real mode.
543 ; unlike the 286, switching back means clearing bit 0 of CR0
544 xor eax,eax ; clear bit 0
547 real_entry_patch:jmp 0x0000:real_entry ; the segment field is patched by code above
548 real_entry: mov ax,cs
555 ; ===== REBUILD GDTR FOR PROPER REAL MODE OPERATION
556 mov word [GDTR],0xFFFF
558 mov word [GDTR+4],0 ; GDTR: limit 0xFFFF base 0x00000000
559 lgdt [GDTR] ; load into processor GDTR
561 mov word [IDTR],0xFFFF
563 mov word [IDTR+4],0 ; IDTR: limit 0xFFFF base 0x00000000
566 ; ====== PROVE WE MADE IT TO REAL MODE
571 vdraw2: lodsb ; AL = DS:SI++
575 stosw ; ES:DI = AX, DI += 2
585 ; ===== EXIT TO DOS WITH ERROR MESSAGE DS:DX
586 exit2dos_with_message:
590 exit2dos: mov ax,4C00h
593 ; 8086 test: EFLAGS will always have bits 12-15 set
604 ; 286 test: EFLAGS will always have bits 12-15 clear
615 cpu_is_386_not: mov ax,1
620 str_cpu_not_386: db "386 or higher required$"
621 str_cpu_v86_mode: db "Virtual 8086 mode detected$"
622 vdraw2_msg: db "This message was drawn on screen back from real mode!",0
623 vdraw3_msg: db "This message was drawn on screen from 386 16-bit protected mode, ring 3!",0
624 vdraw_msg: db "This message was drawn on screen from 386 16-bit protected mode!",0
625 vdraw32_msg: db "This message was drawn on screen from 386 32-bit protected mode!",0
627 ; THESE VARIABLES DO NOT EXIST IN THE ACTUAL .COM FILE.
628 ; They exist in the yet-uninitialized area of RAM just beyond the
629 ; end of the loaded COM file image.
634 MY_PHYS_BASE equ IDTR+8
635 GDT equ MY_PHYS_BASE+8
638 TSS_AREA equ IDT+IDT_SIZE
639 TSS_AREA_SIZE equ 2048
640 TSS_AREA_2 equ TSS_AREA+TSS_AREA_SIZE
641 TSS_AREA_2_SIZE equ 2048
642 TSS_AREA_3 equ TSS_AREA_2+TSS_AREA_2_SIZE
643 TSS_AREA_3_SIZE equ 2048
644 LDT_AREA equ TSS_AREA_3+TSS_AREA_3_SIZE