; alignchk.asm ; ; Test program: 80486+ Alignment Check exception ; (C) 2010-2012 Jonathan Campbell. ; Hackipedia DOS library. ; ; This code is licensed under the LGPL. ; ; ; proot of concept: ; switching the CPU into 386 16-bit protected mode (and back) ; while playing with the Task State Segment mechanism to ; demonstrate hopping between "ring 0" and "ring 3". ; once in RING 3, we turn on the alignment check function of the 486 ; and deliberately try to cause one. bits 16 ; 16-bit real mode org 0x100 ; MS-DOS .COM style ; assume ES == DS and SS == DS and DS == CS ; SELECTORS NULL_SEL equ 0 CODE_SEL equ 8 DATA_SEL equ 16 VIDEO_SEL equ 24 CODE32_SEL equ 32 DATA32_SEL equ 40 TSS_SEL equ 48 TSS_2_SEL equ 56 TSS_3_SEL equ 64 LDT_SEL equ 72 CODE_SEL3 equ 80 DATA_SEL3 equ 88 VIDEO_SEL3 equ 96 MAX_SEL equ 104 ; ===== ENTRY POINT call cpu_is_486 je is_486 mov dx,str_cpu_not_486 jmp exit2dos_with_message is_486: ; ===== CHECK FOR VIRTUAL 8086 MODE smsw ax ; 486 or higher: If we're in real mode test al,1 ; and bit 0 is already set, we're in virtual 8086 jz is_realmode ; and our switch to prot mode will cause problems. mov dx,str_cpu_v86_mode jmp exit2dos_with_message is_realmode: ; ===== WE NEED TO PATCH SOME OF OUR OWN CODE mov ax,cs mov word [real_entry_patch+3],ax ; overwrite segment field of JMP SEG:OFF ; ===== BUILD THE GLOBAL DESCRIPTOR TABLE AND GDTR REGISTER mov ax,cs mov bx,ax shr bx,12 shl ax,4 ; BX:AX = 32-bit physical addr of our segment mov word [MY_PHYS_BASE],ax mov word [MY_PHYS_BASE+2],bx add ax,GDT adc bx,0 ; BX:AX += offset of GDT mov word [GDTR],MAX_SEL - 1 mov word [GDTR+2],ax mov word [GDTR+4],bx ; GDTR: limit MAX_SEL-1 base=physical mem addr of GDT mov ax,word [MY_PHYS_BASE] mov bx,word [MY_PHYS_BASE+2] add ax,IDT adc bx,0 mov word [IDTR],2047 mov word [IDTR+2],ax mov word [IDTR+4],bx cld ; zero IDT mov di,IDT mov cx,1023 xor ax,ax rep stosw mov di,GDT ; NULL selector (NULL_SEL) xor ax,ax stosw stosw stosw stosw ; Code selector (CODE_SEL) dec ax ; 0x0000 - 1 = 0xFFFF stosw ; LIMIT mov ax,[MY_PHYS_BASE] stosw ; BASE[15:0] mov al,[MY_PHYS_BASE+2] mov ah,0x9A stosw ; BASE[23:16] access byte=executable readable mov al,0x0F mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24] stosw ; Data selector (DATA_SEL) xor ax,ax dec ax ; 0xFFFF stosw ; LIMIT mov ax,[MY_PHYS_BASE] stosw ; BASE[15:0] mov al,[MY_PHYS_BASE+2] mov ah,0x92 stosw ; BASE[23:16] access byte=data writeable mov al,0x0F mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24] stosw ; Data selector (VIDEO_SEL) xor ax,ax dec ax ; 0xFFFF stosw ; LIMIT mov ax,0x8000 stosw ; BASE[15:0] mov al,0x0B ; BASE=0xB8000 mov ah,0x92 stosw ; BASE[23:16] access byte=data writeable mov al,0x0F mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24] stosw ; Code selector (32-bit) (CODE32_SEL) dec ax ; 0x0000 - 1 = 0xFFFF stosw ; LIMIT mov ax,[MY_PHYS_BASE] stosw ; BASE[15:0] mov al,[MY_PHYS_BASE+2] mov ah,0x9E stosw ; BASE[23:16] access byte=executable readable conforming mov al,0xCF mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=granular 32-bit BASE[31:24] stosw ; Data selector (32-bit) (DATA32_SEL) xor ax,ax dec ax ; 0xFFFF stosw ; LIMIT mov ax,[MY_PHYS_BASE] stosw ; BASE[15:0] mov al,[MY_PHYS_BASE+2] mov ah,0x92 stosw ; BASE[23:16] access byte=data writeable mov al,0xCF mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=granular 32-bit BASE[31:24] stosw ; TSS selector (32-bit) (TSS_SEL) mov ax,TSS_AREA_SIZE - 1 stosw ; LIMIT mov ax,[MY_PHYS_BASE] mov bx,[MY_PHYS_BASE+2] add ax,TSS_AREA adc bx,0 stosw ; BASE[15:0] mov al,bl mov ah,0x89 ; present, non-segment, type=9 (TSS busy) stosw ; BASE[23:16] access byte=data writeable mov al,0x00 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24] stosw ; TSS selector (32-bit) (TSS_2_SEL) mov ax,TSS_AREA_2_SIZE - 1 stosw ; LIMIT mov ax,[MY_PHYS_BASE] mov bx,[MY_PHYS_BASE+2] add ax,TSS_AREA_2 adc bx,0 stosw ; BASE[15:0] mov al,bl mov ah,0x89 ; present, non-segment, type=9 (TSS non busy) stosw ; BASE[23:16] access byte=data writeable mov al,0x00 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24] stosw ; TSS selector (32-bit) (TSS_3_SEL) mov ax,TSS_AREA_3_SIZE - 1 stosw ; LIMIT mov ax,[MY_PHYS_BASE] mov bx,[MY_PHYS_BASE+2] add ax,TSS_AREA_3 adc bx,0 stosw ; BASE[15:0] mov al,bl mov ah,0x89 ; present, non-segment, type=9 (TSS non busy) stosw ; BASE[23:16] access byte=data writeable mov al,0x00 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24] stosw ; LDT selector (32-bit) (LDT_SEL) mov ax,LDT_AREA_SIZE - 1 stosw ; LIMIT mov ax,[MY_PHYS_BASE] mov bx,[MY_PHYS_BASE+2] add ax,LDT_AREA adc bx,0 stosw ; BASE[15:0] mov al,bl mov ah,0x82 ; present, non-segment, type=2 (LDT) stosw ; BASE[23:16] access byte=data writeable mov al,0x00 mov ah,bh ; LIMIT[19:16] flags=granular BASE[31:24] stosw ; Code selector (CODE_SEL3) dec ax ; 0x0000 - 1 = 0xFFFF stosw ; LIMIT mov ax,[MY_PHYS_BASE] stosw ; BASE[15:0] mov al,[MY_PHYS_BASE+2] mov ah,0xFA stosw ; BASE[23:16] access byte=executable readable DPL=3 mov al,0x0F mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24] stosw ; Data selector (DATA_SEL3) xor ax,ax dec ax ; 0xFFFF stosw ; LIMIT mov ax,[MY_PHYS_BASE] stosw ; BASE[15:0] mov al,[MY_PHYS_BASE+2] mov ah,0xF2 stosw ; BASE[23:16] access byte=data writeable DPL=3 mov al,0x0F mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24] stosw ; Data selector (VIDEO_SEL3) xor ax,ax dec ax ; 0xFFFF stosw ; LIMIT mov ax,0x8000 stosw ; BASE[15:0] mov al,0x0B ; BASE=0xB8000 mov ah,0xF2 stosw ; BASE[23:16] access byte=data writeable mov al,0x0F mov ah,[MY_PHYS_BASE+3] ; LIMIT[19:16] flags=0 BASE[31:24] stosw ; load CPU registers cli ; disable interrupts lgdt [GDTR] ; load into processor GDTR lidt [IDTR] ; switch into protected mode mov eax,0x00040001 ; protected mode, enable alignment check exception mov cr0,eax jmp CODE_SEL:prot_entry prot_entry: mov ax,DATA_SEL ; now reload the segment registers mov ds,ax mov es,ax mov fs,ax mov gs,ax mov ss,ax mov sp,0xFFF0 ; load LDT mov ax,LDT_SEL lldt ax ; zero the first TSS cld mov edi,TSS_AREA mov ecx,TSS_AREA_SIZE / 4 xor eax,eax rep stosd ; zero the second TSS cld mov edi,TSS_AREA_2 mov ecx,TSS_AREA_2_SIZE / 4 xor eax,eax rep stosd ; set up the task register. for now, leave it at the first one. mov ax,TSS_SEL ltr ax ; prepare the second one cld xor eax,eax ; prepare EAX=0 mov ebx,eax mov ecx,0x12345678 ; check value mov edi,TSS_AREA_2 stosd ; TSS+0x00 = back link mov ax,0xF000 stosd ; TSS+0x04 = ESP0 mov ax,DATA_SEL stosd ; TSS+0x08 = SS0 mov ax,0xF000 stosd ; TSS+0x0C = ESP1 mov ax,DATA_SEL stosd ; TSS+0x10 = SS1 mov ax,0xF000 stosd ; TSS+0x14 = ESP2 mov ax,DATA_SEL stosd ; TSS+0x18 = SS2 xor ax,ax stosd ; TSS+0x1C = CR3 mov eax,tss_jump_1 stosd ; TSS+0x20 = EIP pushfd pop eax stosd ; TSS+0x24 = EFLAGS xor eax,eax stosd ; TSS+0x28 = EAX stosd ; TSS+0x2C = ECX stosd ; TSS+0x30 = EDX stosd ; TSS+0x34 = EBX mov ax,0xF000 stosd ; TSS+0x38 = ESP xor ax,ax stosd ; TSS+0x3C = EBP stosd ; TSS+0x40 = ESI stosd ; TSS+0x44 = EDI mov ax,DATA_SEL stosd ; TSS+0x48 = ES mov ax,CODE_SEL stosd ; TSS+0x4C = CS mov ax,DATA_SEL stosd ; TSS+0x50 = SS stosd ; TSS+0x54 = DS stosd ; TSS+0x58 = FS stosd ; TSS+0x5C = GS mov ax,LDT_SEL stosd ; TSS+0x60 = LDT selector (meh, I don't use it anyway) xor ax,ax stosd ; TSS+0x64 = I/O map base=0, T=0 ; now, SWITCH! jmp TSS_2_SEL:0 ; TSS switch should end up HERE. ; Task register now points to TSS_2_SEL as active task. ; TEST: If the CPU truly loaded state from TSS_2_SEL, all general regs should be zero tss_jump_1: or eax,ebx or eax,ecx or eax,edx or eax,esi or eax,edi jz tss_jump_1_zero mov ax,VIDEO_SEL mov es,ax mov word [es:0],0x4E30 ; '0' mov al,3 out 61h,al ; turn on bell hlt tss_jump_1_zero: ; TEST: All segment registers except CS should be DATA_SEL mov ax,ds sub ax,DATA_SEL mov bx,es sub bx,DATA_SEL or ax,bx mov bx,fs sub bx,DATA_SEL or ax,bx mov bx,gs sub bx,DATA_SEL or ax,bx mov bx,ss sub bx,DATA_SEL or ax,bx jz tss_jump_1_sreg_ok mov ax,VIDEO_SEL mov es,ax mov word [es:0],0x4E31 ; '1' mov al,3 out 61h,al ; turn on bell hlt tss_jump_1_sreg_ok: ; if the CPU truly saved state into TSS_SEL, the memory location ; corresponding to ECX should be 0x12345678 (because we loaded ECX ; with that value prior to switching state, remember?) cmp dword [TSS_AREA+0x2C],0x12345678 jz tss_jump_1_ecx_ok mov ax,VIDEO_SEL mov es,ax mov word [es:0],0x4E32 ; '2' mov al,3 out 61h,al ; turn on bell hlt tss_jump_1_ecx_ok: ; draw directly onto VGA alphanumeric RAM at 0xB8000 cld push es mov ax,VIDEO_SEL mov es,ax mov si,vdraw_msg xor di,di vdraw1: lodsb ; AL = DS:SI++ or al,al jz vdraw1e mov ah,0x1E stosw ; ES:DI = AX, DI += 2 jmp vdraw1 vdraw1e: pop es ; now, jump into 32-bit protected mode jmp CODE32_SEL:prot32_entry bits 32 prot32_entry: mov ax,DATA32_SEL mov ds,ax mov es,ax mov fs,ax mov gs,ax mov ss,ax mov esp,0xFFF0 ; draw directly onto VGA alphanumeric RAM at 0xB8000 cld mov esi,vdraw32_msg mov edi,0xB8000+(80*2) sub edi,[MY_PHYS_BASE] vdraw321: lodsb ; AL = DS:SI++ or al,al jz vdraw321e mov ah,0x1E stosw ; ES:DI = AX, DI += 2 jmp vdraw321 vdraw321e: ; jump 32-bit to 16-bit jmp CODE_SEL:prot32_to_prot bits 16 prot32_to_prot: mov ax,DATA_SEL mov ds,ax mov es,ax mov fs,ax mov gs,ax mov ss,ax ; prepare the third one---ring 3 cld xor eax,eax ; prepare EAX=0 mov ebx,eax mov ecx,0x12345678 ; check value mov edi,TSS_AREA_3 stosd ; TSS+0x00 = back link mov ax,0xE000 stosd ; TSS+0x04 = ESP0 mov ax,DATA_SEL stosd ; TSS+0x08 = SS0 mov ax,0xE000 stosd ; TSS+0x0C = ESP1 mov ax,DATA_SEL stosd ; TSS+0x10 = SS1 mov ax,0xE000 stosd ; TSS+0x14 = ESP2 mov ax,DATA_SEL stosd ; TSS+0x18 = SS2 xor ax,ax stosd ; TSS+0x1C = CR3 mov eax,tss_jump_3 stosd ; TSS+0x20 = EIP pushfd pop eax stosd ; TSS+0x24 = EFLAGS xor eax,eax stosd ; TSS+0x28 = EAX stosd ; TSS+0x2C = ECX stosd ; TSS+0x30 = EDX stosd ; TSS+0x34 = EBX mov ax,0xF000 stosd ; TSS+0x38 = ESP xor ax,ax stosd ; TSS+0x3C = EBP stosd ; TSS+0x40 = ESI stosd ; TSS+0x44 = EDI mov ax,DATA_SEL3 | 3 stosd ; TSS+0x48 = ES mov ax,CODE_SEL3 | 3 stosd ; TSS+0x4C = CS mov ax,DATA_SEL3 | 3 stosd ; TSS+0x50 = SS stosd ; TSS+0x54 = DS stosd ; TSS+0x58 = FS stosd ; TSS+0x5C = GS mov ax,LDT_SEL stosd ; TSS+0x60 = LDT selector (meh, I don't use it anyway) xor ax,ax stosd ; TSS+0x64 = I/O map base=0, T=0 ; Call the TSS, so that we can IRET to return to RING 0 call TSS_3_SEL:0 jmp skip_tss_3 ; now we are 16-bit RING 3 tss_jump_3: mov ax,VIDEO_SEL3 ; PROVE IT mov es,ax ; BY WRITING TO SCREEN mov si,vdraw3_msg mov di,80*4 cld vdraw3: lodsb ; AL = DS:SI++ or al,al jz vdraw3e mov ah,0x1E stosw ; ES:DI = AX, DI += 2 jmp vdraw3 vdraw3e: ; switch on the #AC flag in EFLAGS pushfd pop eax or eax,0x40000 push eax popfd ; while we're in RING 3, cause an alignment check fault to test whether the ; CPU supports it. mov si,IDT + (0x11*8) ; INT 11h alignment check exception mov word [si+0],tss_ring_3_ac ; base[15:0] mov word [si+2],CODE_SEL mov word [si+4],0x8600 ; P=1 DPL=0 16-bit interrupt gate mov word [si+6],0x0000 ; base[31:16] ; cause it! mov ax,[1] ; misaligned WORD read ; return from ring 3 iret ; Alignment check exception lands here ; write our acknowledgement on the screen. ; Note we're running at ring 0 at this point---I couldn't figure out ; how to make RING 3 exception handlers work. tss_ring_3_ac: mov ax,DATA_SEL mov ds,ax mov ax,VIDEO_SEL mov es,ax mov si,alignchk_msg mov di,80*8 cld vdraw3ac: lodsb ; AL = DS:SI++ or al,al jz vdraw3ace mov ah,0x1E stosw ; ES:DI = AX, DI += 2 jmp vdraw3ac vdraw3ace: ; disable alignment check to avoid further exceptions, and to allow ; the program to continue. Note that despite what numerous web sites ; tell you, the AC# exception DOES push an error code onto the stack! ; Both Intel and AMD docs say so too! mov eax,cr0 and eax,~0x40000 mov cr0,eax ; FIXME: I can't fucking figure out how to IRET without crashing, so ; in the meantime, while we're sitting in ring zero we're just going ; to go ahead and hack the TSS segment and forcibly restore state. mov ax,DATA_SEL mov ds,ax mov es,ax mov fs,ax mov gs,ax mov ss,ax mov sp,0xF000 ; the CPU probably changes the TSS to busy, force it back to non-busy mov byte [GDT+TSS_2_SEL+5],0x89 ; having disarmed the busy status, switch. jmp TSS_2_SEL:0 ; TSS RING-3 test COMPLETE skip_tss_3: ; active task is TSS_2_SEL. Prove we can switch tasks again by modifying ; EIP in TSS_SEL, then switching tasks. mov dword [TSS_AREA+0x20],tss_jump_2 jmp TSS_SEL:0 tss_jump_2: ; having switched back to TSS_SEL, the value we left in ECX should still ; be there. cmp ecx,0x12345678 jz tss_jump_2_ecx_ok mov ax,VIDEO_SEL mov es,ax mov word [es:0],0x4E33 ; '3' mov al,3 out 61h,al ; turn on bell hlt tss_jump_2_ecx_ok: ; switch back to real mode. ; unlike the 286, switching back means clearing bit 0 of CR0 xor eax,eax ; clear bit 0 mov cr0,eax real_entry_patch:jmp 0x0000:real_entry ; the segment field is patched by code above real_entry: mov ax,cs mov ds,ax mov es,ax mov fs,ax mov ss,ax mov sp,0xFFF0 ; ===== REBUILD GDTR FOR PROPER REAL MODE OPERATION mov word [GDTR],0xFFFF mov word [GDTR+2],0 mov word [GDTR+4],0 ; GDTR: limit 0xFFFF base 0x00000000 lgdt [GDTR] ; load into processor GDTR mov word [IDTR],0xFFFF mov word [IDTR+2],0 mov word [IDTR+4],0 ; IDTR: limit 0xFFFF base 0x00000000 lidt [IDTR] ; ====== PROVE WE MADE IT TO REAL MODE mov si,vdraw2_msg mov ax,0xB800 mov es,ax mov di,80*6 vdraw2: lodsb ; AL = DS:SI++ or al,al jz vdraw2e mov ah,0x1E stosw ; ES:DI = AX, DI += 2 jmp vdraw2 vdraw2e: mov ax,cs mov es,ax sti ; ===== DONE jmp exit2dos ; ===== EXIT TO DOS WITH ERROR MESSAGE DS:DX exit2dos_with_message: mov ah,9 int 21h ; ===== EXIT TO DOS exit2dos: mov ax,4C00h int 21h ; 8086 test: EFLAGS will always have bits 12-15 set cpu_is_486: pushf pop ax and ax,0x0FFF push ax popf pushf pop ax and ax,0xF000 cmp ax,0xF000 jz cpu_is_486_not ; 286 test: EFLAGS will always have bits 12-15 clear or ax,0xF000 push ax popf pushf pop ax and ax,0xF000 jz cpu_is_486_not ; 386 test: We cannot set #AC (bit 18) on a 386 pushfd pop eax or eax,0x40000 push eax popfd pushfd pop eax and eax,0x40000 jz cpu_is_486_not ; it's a 486 xor ax,ax ; ZF=1 ret cpu_is_486_not: mov ax,1 or ax,ax ; ZF=0 ret ; strings str_cpu_not_486: db "486 or higher required$" str_cpu_v86_mode: db "Virtual 8086 mode detected$" vdraw2_msg: db "This message was drawn on screen back from real mode!",0 vdraw3_msg: db "This message was drawn on screen from 386 16-bit protected mode, ring 3!",0 vdraw_msg: db "This message was drawn on screen from 386 16-bit protected mode!",0 vdraw32_msg: db "This message was drawn on screen from 386 32-bit protected mode!",0 alignchk_msg: db "Yes, this processor supports the Alignment Check exception!",0 ; THESE VARIABLES DO NOT EXIST IN THE ACTUAL .COM FILE. ; They exist in the yet-uninitialized area of RAM just beyond the ; end of the loaded COM file image. align 8 RALLOC: db 0xAA GDTR equ RALLOC+0 IDTR equ GDTR+8 MY_PHYS_BASE equ IDTR+8 GDT equ MY_PHYS_BASE+8 IDT equ GDT+MAX_SEL IDT_SIZE equ 2048 TSS_AREA equ IDT+IDT_SIZE TSS_AREA_SIZE equ 2048 TSS_AREA_2 equ TSS_AREA+TSS_AREA_SIZE TSS_AREA_2_SIZE equ 2048 TSS_AREA_3 equ TSS_AREA_2+TSS_AREA_2_SIZE TSS_AREA_3_SIZE equ 2048 LDT_AREA equ TSS_AREA_3+TSS_AREA_3_SIZE LDT_AREA_SIZE equ 64