3 ; Test program: 80486+ Alignment Check exception
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 ; once in RING 3, we turn on the alignment check function of the 486
15 ; and deliberately try to cause one.
16 bits 16 ; 16-bit real mode
17 org 0x100 ; MS-DOS .COM style
19 ; assume ES == DS and SS == DS and DS == CS
40 mov dx,str_cpu_not_486
41 jmp exit2dos_with_message
44 ; ===== CHECK FOR VIRTUAL 8086 MODE
45 smsw ax ; 486 or higher: If we're in real mode
46 test al,1 ; and bit 0 is already set, we're in virtual 8086
47 jz is_realmode ; and our switch to prot mode will cause problems.
48 mov dx,str_cpu_v86_mode
49 jmp exit2dos_with_message
52 ; ===== WE NEED TO PATCH SOME OF OUR OWN CODE
54 mov word [real_entry_patch+3],ax ; overwrite segment field of JMP SEG:OFF
56 ; ===== BUILD THE GLOBAL DESCRIPTOR TABLE AND GDTR REGISTER
60 shl ax,4 ; BX:AX = 32-bit physical addr of our segment
61 mov word [MY_PHYS_BASE],ax
62 mov word [MY_PHYS_BASE+2],bx
65 adc bx,0 ; BX:AX += offset of GDT
67 mov word [GDTR],MAX_SEL - 1
69 mov word [GDTR+4],bx ; GDTR: limit MAX_SEL-1 base=physical mem addr of GDT
71 mov ax,word [MY_PHYS_BASE]
72 mov bx,word [MY_PHYS_BASE+2]
89 ; NULL selector (NULL_SEL)
95 ; Code selector (CODE_SEL)
96 dec ax ; 0x0000 - 1 = 0xFFFF
100 mov al,[MY_PHYS_BASE+2]
102 stosw ; BASE[23:16] access byte=executable readable
104 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
106 ; Data selector (DATA_SEL)
110 mov ax,[MY_PHYS_BASE]
112 mov al,[MY_PHYS_BASE+2]
114 stosw ; BASE[23:16] access byte=data writeable
116 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
118 ; Data selector (VIDEO_SEL)
124 mov al,0x0B ; BASE=0xB8000
126 stosw ; BASE[23:16] access byte=data writeable
128 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
130 ; Code selector (32-bit) (CODE32_SEL)
131 dec ax ; 0x0000 - 1 = 0xFFFF
133 mov ax,[MY_PHYS_BASE]
135 mov al,[MY_PHYS_BASE+2]
137 stosw ; BASE[23:16] access byte=executable readable conforming
139 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=granular 32-bit BASE[31:24]
141 ; Data selector (32-bit) (DATA32_SEL)
145 mov ax,[MY_PHYS_BASE]
147 mov al,[MY_PHYS_BASE+2]
149 stosw ; BASE[23:16] access byte=data writeable
151 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=granular 32-bit BASE[31:24]
153 ; TSS selector (32-bit) (TSS_SEL)
154 mov ax,TSS_AREA_SIZE - 1
156 mov ax,[MY_PHYS_BASE]
157 mov bx,[MY_PHYS_BASE+2]
162 mov ah,0x89 ; present, non-segment, type=9 (TSS busy)
163 stosw ; BASE[23:16] access byte=data writeable
165 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24]
167 ; TSS selector (32-bit) (TSS_2_SEL)
168 mov ax,TSS_AREA_2_SIZE - 1
170 mov ax,[MY_PHYS_BASE]
171 mov bx,[MY_PHYS_BASE+2]
176 mov ah,0x89 ; present, non-segment, type=9 (TSS non busy)
177 stosw ; BASE[23:16] access byte=data writeable
179 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24]
181 ; TSS selector (32-bit) (TSS_3_SEL)
182 mov ax,TSS_AREA_3_SIZE - 1
184 mov ax,[MY_PHYS_BASE]
185 mov bx,[MY_PHYS_BASE+2]
190 mov ah,0x89 ; present, non-segment, type=9 (TSS non busy)
191 stosw ; BASE[23:16] access byte=data writeable
193 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24]
195 ; LDT selector (32-bit) (LDT_SEL)
196 mov ax,LDT_AREA_SIZE - 1
198 mov ax,[MY_PHYS_BASE]
199 mov bx,[MY_PHYS_BASE+2]
204 mov ah,0x82 ; present, non-segment, type=2 (LDT)
205 stosw ; BASE[23:16] access byte=data writeable
207 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24]
209 ; Code selector (CODE_SEL3)
210 dec ax ; 0x0000 - 1 = 0xFFFF
212 mov ax,[MY_PHYS_BASE]
214 mov al,[MY_PHYS_BASE+2]
216 stosw ; BASE[23:16] access byte=executable readable DPL=3
218 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
220 ; Data selector (DATA_SEL3)
224 mov ax,[MY_PHYS_BASE]
226 mov al,[MY_PHYS_BASE+2]
228 stosw ; BASE[23:16] access byte=data writeable DPL=3
230 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
232 ; Data selector (VIDEO_SEL3)
238 mov al,0x0B ; BASE=0xB8000
240 stosw ; BASE[23:16] access byte=data writeable
242 mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24]
246 cli ; disable interrupts
247 lgdt [GDTR] ; load into processor GDTR
250 ; switch into protected mode
251 mov eax,0x00040001 ; protected mode, enable alignment check exception
253 jmp CODE_SEL:prot_entry
254 prot_entry: mov ax,DATA_SEL ; now reload the segment registers
269 mov ecx,TSS_AREA_SIZE / 4
273 ; zero the second TSS
276 mov ecx,TSS_AREA_2_SIZE / 4
280 ; set up the task register. for now, leave it at the first one.
284 ; prepare the second one
286 xor eax,eax ; prepare EAX=0
288 mov ecx,0x12345678 ; check value
290 stosd ; TSS+0x00 = back link
292 stosd ; TSS+0x04 = ESP0
294 stosd ; TSS+0x08 = SS0
296 stosd ; TSS+0x0C = ESP1
298 stosd ; TSS+0x10 = SS1
300 stosd ; TSS+0x14 = ESP2
302 stosd ; TSS+0x18 = SS2
304 stosd ; TSS+0x1C = CR3
306 stosd ; TSS+0x20 = EIP
309 stosd ; TSS+0x24 = EFLAGS
311 stosd ; TSS+0x28 = EAX
312 stosd ; TSS+0x2C = ECX
313 stosd ; TSS+0x30 = EDX
314 stosd ; TSS+0x34 = EBX
316 stosd ; TSS+0x38 = ESP
318 stosd ; TSS+0x3C = EBP
319 stosd ; TSS+0x40 = ESI
320 stosd ; TSS+0x44 = EDI
322 stosd ; TSS+0x48 = ES
324 stosd ; TSS+0x4C = CS
326 stosd ; TSS+0x50 = SS
327 stosd ; TSS+0x54 = DS
328 stosd ; TSS+0x58 = FS
329 stosd ; TSS+0x5C = GS
331 stosd ; TSS+0x60 = LDT selector (meh, I don't use it anyway)
333 stosd ; TSS+0x64 = I/O map base=0, T=0
338 ; TSS switch should end up HERE.
339 ; Task register now points to TSS_2_SEL as active task.
340 ; TEST: If the CPU truly loaded state from TSS_2_SEL, all general regs should be zero
341 tss_jump_1: or eax,ebx
350 mov word [es:0],0x4E30 ; '0'
352 out 61h,al ; turn on bell
355 ; TEST: All segment registers except CS should be DATA_SEL
375 jz tss_jump_1_sreg_ok
379 mov word [es:0],0x4E31 ; '1'
381 out 61h,al ; turn on bell
385 ; if the CPU truly saved state into TSS_SEL, the memory location
386 ; corresponding to ECX should be 0x12345678 (because we loaded ECX
387 ; with that value prior to switching state, remember?)
388 cmp dword [TSS_AREA+0x2C],0x12345678
393 mov word [es:0],0x4E32 ; '2'
395 out 61h,al ; turn on bell
399 ; draw directly onto VGA alphanumeric RAM at 0xB8000
406 vdraw1: lodsb ; AL = DS:SI++
410 stosw ; ES:DI = AX, DI += 2
414 ; now, jump into 32-bit protected mode
415 jmp CODE32_SEL:prot32_entry
417 prot32_entry: mov ax,DATA32_SEL
425 ; draw directly onto VGA alphanumeric RAM at 0xB8000
428 mov edi,0xB8000+(80*2)
429 sub edi,[MY_PHYS_BASE]
430 vdraw321: lodsb ; AL = DS:SI++
434 stosw ; ES:DI = AX, DI += 2
438 ; jump 32-bit to 16-bit
439 jmp CODE_SEL:prot32_to_prot
441 prot32_to_prot: mov ax,DATA_SEL
448 ; prepare the third one---ring 3
450 xor eax,eax ; prepare EAX=0
452 mov ecx,0x12345678 ; check value
454 stosd ; TSS+0x00 = back link
456 stosd ; TSS+0x04 = ESP0
458 stosd ; TSS+0x08 = SS0
460 stosd ; TSS+0x0C = ESP1
462 stosd ; TSS+0x10 = SS1
464 stosd ; TSS+0x14 = ESP2
466 stosd ; TSS+0x18 = SS2
468 stosd ; TSS+0x1C = CR3
470 stosd ; TSS+0x20 = EIP
473 stosd ; TSS+0x24 = EFLAGS
475 stosd ; TSS+0x28 = EAX
476 stosd ; TSS+0x2C = ECX
477 stosd ; TSS+0x30 = EDX
478 stosd ; TSS+0x34 = EBX
480 stosd ; TSS+0x38 = ESP
482 stosd ; TSS+0x3C = EBP
483 stosd ; TSS+0x40 = ESI
484 stosd ; TSS+0x44 = EDI
486 stosd ; TSS+0x48 = ES
488 stosd ; TSS+0x4C = CS
490 stosd ; TSS+0x50 = SS
491 stosd ; TSS+0x54 = DS
492 stosd ; TSS+0x58 = FS
493 stosd ; TSS+0x5C = GS
495 stosd ; TSS+0x60 = LDT selector (meh, I don't use it anyway)
497 stosd ; TSS+0x64 = I/O map base=0, T=0
498 ; Call the TSS, so that we can IRET to return to RING 0
502 ; now we are 16-bit RING 3
503 tss_jump_3: mov ax,VIDEO_SEL3 ; PROVE IT
504 mov es,ax ; BY WRITING TO SCREEN
508 vdraw3: lodsb ; AL = DS:SI++
512 stosw ; ES:DI = AX, DI += 2
516 ; switch on the #AC flag in EFLAGS
523 ; while we're in RING 3, cause an alignment check fault to test whether the
525 mov si,IDT + (0x11*8) ; INT 11h alignment check exception
526 mov word [si+0],tss_ring_3_ac ; base[15:0]
527 mov word [si+2],CODE_SEL
528 mov word [si+4],0x8600 ; P=1 DPL=0 16-bit interrupt gate
529 mov word [si+6],0x0000 ; base[31:16]
531 mov ax,[1] ; misaligned WORD read
536 ; Alignment check exception lands here
537 ; write our acknowledgement on the screen.
538 ; Note we're running at ring 0 at this point---I couldn't figure out
539 ; how to make RING 3 exception handlers work.
548 vdraw3ac: lodsb ; AL = DS:SI++
552 stosw ; ES:DI = AX, DI += 2
555 ; disable alignment check to avoid further exceptions, and to allow
556 ; the program to continue. Note that despite what numerous web sites
557 ; tell you, the AC# exception DOES push an error code onto the stack!
558 ; Both Intel and AMD docs say so too!
562 ; FIXME: I can't fucking figure out how to IRET without crashing, so
563 ; in the meantime, while we're sitting in ring zero we're just going
564 ; to go ahead and hack the TSS segment and forcibly restore state.
572 ; the CPU probably changes the TSS to busy, force it back to non-busy
573 mov byte [GDT+TSS_2_SEL+5],0x89
574 ; having disarmed the busy status, switch.
577 ; TSS RING-3 test COMPLETE
580 ; active task is TSS_2_SEL. Prove we can switch tasks again by modifying
581 ; EIP in TSS_SEL, then switching tasks.
582 mov dword [TSS_AREA+0x20],tss_jump_2
586 ; having switched back to TSS_SEL, the value we left in ECX should still
593 mov word [es:0],0x4E33 ; '3'
595 out 61h,al ; turn on bell
599 ; switch back to real mode.
600 ; unlike the 286, switching back means clearing bit 0 of CR0
601 xor eax,eax ; clear bit 0
604 real_entry_patch:jmp 0x0000:real_entry ; the segment field is patched by code above
605 real_entry: mov ax,cs
612 ; ===== REBUILD GDTR FOR PROPER REAL MODE OPERATION
613 mov word [GDTR],0xFFFF
615 mov word [GDTR+4],0 ; GDTR: limit 0xFFFF base 0x00000000
616 lgdt [GDTR] ; load into processor GDTR
618 mov word [IDTR],0xFFFF
620 mov word [IDTR+4],0 ; IDTR: limit 0xFFFF base 0x00000000
623 ; ====== PROVE WE MADE IT TO REAL MODE
628 vdraw2: lodsb ; AL = DS:SI++
632 stosw ; ES:DI = AX, DI += 2
642 ; ===== EXIT TO DOS WITH ERROR MESSAGE DS:DX
643 exit2dos_with_message:
647 exit2dos: mov ax,4C00h
650 ; 8086 test: EFLAGS will always have bits 12-15 set
661 ; 286 test: EFLAGS will always have bits 12-15 clear
669 ; 386 test: We cannot set #AC (bit 18) on a 386
682 cpu_is_486_not: mov ax,1
687 str_cpu_not_486: db "486 or higher required$"
688 str_cpu_v86_mode: db "Virtual 8086 mode detected$"
689 vdraw2_msg: db "This message was drawn on screen back from real mode!",0
690 vdraw3_msg: db "This message was drawn on screen from 386 16-bit protected mode, ring 3!",0
691 vdraw_msg: db "This message was drawn on screen from 386 16-bit protected mode!",0
692 vdraw32_msg: db "This message was drawn on screen from 386 32-bit protected mode!",0
693 alignchk_msg: db "Yes, this processor supports the Alignment Check exception!",0
695 ; THESE VARIABLES DO NOT EXIST IN THE ACTUAL .COM FILE.
696 ; They exist in the yet-uninitialized area of RAM just beyond the
697 ; end of the loaded COM file image.
702 MY_PHYS_BASE equ IDTR+8
703 GDT equ MY_PHYS_BASE+8
706 TSS_AREA equ IDT+IDT_SIZE
707 TSS_AREA_SIZE equ 2048
708 TSS_AREA_2 equ TSS_AREA+TSS_AREA_SIZE
709 TSS_AREA_2_SIZE equ 2048
710 TSS_AREA_3 equ TSS_AREA_2+TSS_AREA_2_SIZE
711 TSS_AREA_3_SIZE equ 2048
712 LDT_AREA equ TSS_AREA_3+TSS_AREA_3_SIZE