diff --git a/README.md b/README.md index c95c23d..f29ed62 100644 --- a/README.md +++ b/README.md @@ -125,11 +125,15 @@ executed: | ---- | ------------------------------------------------------------------ | | 0x00 | Real mode initialisation | | 0x01 | Conditional jumps and loops | +| 0x1D | Flag instructions in real mode | | 0x02 | Quick tests of unsigned 32-bit multiplication and division | | 0x03 | Move segment registers in real mode | | 0x04 | Store, move, scan, and compare string data in real mode | | 0x05 | Calls in real mode | | 0x06 | Load full pointer in real mode | +| 0x1E | Load and store GDTR and IDTR in real mode ** | +| 0x1F | XLAT in real mode ** | +| 0x07 | Interrupts in real mode ** | | 0x08 | GDT, LDT, PDT, and PT setup, enter protected mode | | 0x09 | Stack functionality * | | 0x20 | Test user mode (ring 3) switching | diff --git a/src/protected_inth.asm b/src/protected_inth.asm index b15f8cd..453d53e 100644 --- a/src/protected_inth.asm +++ b/src/protected_inth.asm @@ -1,36 +1,3 @@ -; -; Kernel mode interrupt handler -; -kernelInterrupt: - testCPL 0 ; Elevates to CPL 0 - push ebx - push ecx - push ds - lds ebx, [cs:ptrTSSprot] ; Get the TSS - mov ecx, [ebx+4] ; Get TSS ESP0 - sub ecx, 0x14+0xC ; Where we should end up on the kernel stack, taking into account what we just pushed - cmp esp, ecx ; Did the stack decrease correctly? - jne error - mov cx, ss - cmp cx, word [ebx+8] ; Did the stack pointer load correctly? - jne error - pop ds - pop ecx - mov bx, cs - cmp bx, C_SEG_PROT32 ; Did we end up in kernel mode correctly? - jne error - pop ebx - cmp dword [esp+0x00], kernelModeInterruptReturn - jne error ; Invalid return address - cmp dword [esp+0x04], CU_SEG_PROT32|3 - jne error ; Invalid return code segment - ; Ignore eflags - cmp dword [esp+0x0C], ESP_R3_PROT - jne error ; Invalid return ESP - cmp dword [esp+0x10], SU_SEG_PROT32|3 - jne error ; Invalid user stack segment - iret ; Simply return to user mode - ; ; Kernel mode interrupt handler ; @@ -39,7 +6,7 @@ kernelInterrupt286: push ebx push ecx push ds - lds ebx, [cs:ptrTSSprot] ; Get the TSS + lds ebx, [cs:ptrTSSprot_R0] ; Get the TSS mov ecx, [ebx+4] ; Get TSS ESP0 sub ecx, 0x0A+0xC ; Where we should end up on the kernel stack, taking into account what we just pushed cmp esp, ecx ; Did the stack decrease correctly? @@ -50,12 +17,12 @@ kernelInterrupt286: pop ds pop ecx mov bx, cs - cmp bx, C_SEG_PROT32 ; Did we end up in kernel mode correctly? + cmp bx, C_SEG_PROT32low ; Did we end up in kernel mode correctly? jne error pop ebx cmp word [esp+0x00], kernelModeInterruptReturn286 jne error ; Invalid return address - cmp word [esp+0x02], CU_SEG_PROT32|3 + cmp word [esp+0x02], CU_SEG_PROT32low|3 jne error ; Invalid return code segment ; Ignore eflags cmp word [esp+0x06], ESP_R3_PROT @@ -90,7 +57,7 @@ kernelInterruptRestoreKernelStack: pop ebx cmp dword [esp+0x00], kernelModeInterruptKernelStackReturn jne error ; Invalid return address - cmp dword [esp+0x04], CU_SEG_PROT32|3 + cmp dword [esp+0x04], CU_SEG_PROT32low|3 jne error ; Invalid return code segment ; Ignore eflags cmp dword [esp+0x0C], ESP_R3_PROT @@ -110,7 +77,7 @@ kernelOnlyInterrupt: push ebx push ds push ecx - lds ebx, [cs:ptrTSSprot] ; Get the TSS + lds ebx, [cs:ptrTSSprot_R0] ; Get the TSS mov ecx, eax ; Get ESP for the kernel mode program (stored into eax) sub ecx, 0xC+0xC ; Where we should end up on the kernel stack, taking into account what we just pushed cmp esp, ecx ; Did the stack decrease correctly? @@ -121,13 +88,13 @@ kernelOnlyInterrupt: jne error pop ecx mov bx, cs - cmp bx, C_SEG_PROT32 ; Did we arrive at proper kernel code? + cmp bx, C_SEG_PROT32low ; Did we arrive at proper kernel code? jne error pop ds pop ebx cmp dword [esp+0x00], kernelModeOnlyInterruptReturn jne error ; Invalid return address - cmp dword [esp+0x04],C_SEG_PROT32 + cmp dword [esp+0x04],C_SEG_PROT32low jne error ; Invalid return code segment ; Ignore eflags iret ; Simply return to kernel mode @@ -150,12 +117,12 @@ kernelConformingInterrupt: jne error pop ecx mov bx, cs - cmp bx, CC_SEG_PROT32|3 ; Did we arrive at proper conforming code? + cmp bx, CC_SEG_PROT32low|3 ; Did we arrive at proper conforming code? jne error pop ebx cmp dword [esp+0x00], kernelConformingInterruptReturn jne error ; Invalid return address - cmp dword [esp+0x04], CU_SEG_PROT32|3 + cmp dword [esp+0x04], CU_SEG_PROT32low|3 jne error ; Invalid return code segment ; Ignore eflags iret ; Simply return to user mode, stays at CPL 3 @@ -171,7 +138,7 @@ kernelOnlyConformingInterrupt: push ebx push ds push ecx - lds ebx, [cs:ptrTSSprot] ; Get the TSS + lds ebx, [cs:ptrTSSprot_R0] ; Get the TSS mov ecx, eax ; Get ESP for the kernel mode program (stored into eax) sub ecx, 0xC+0xC ; Where we should end up on the kernel stack, taking into account what we just pushed cmp esp, ecx ; Did the stack decrease correctly? @@ -182,13 +149,13 @@ kernelOnlyConformingInterrupt: jne error pop ecx mov bx, cs - cmp bx, CC_SEG_PROT32 ; Did we arrive at proper conforming kernel code? + cmp bx, CC_SEG_PROT32low ; Did we arrive at proper conforming kernel code? jne error pop ds pop ebx cmp dword [esp+0x00], kernelOnlyConformingInterruptReturn jne error ; Invalid return address - cmp dword [esp+0x04],C_SEG_PROT32 + cmp dword [esp+0x04],C_SEG_PROT32low jne error ; Invalid return code segment ; Ignore eflags iret ; Simply return to kernel mode @@ -211,12 +178,12 @@ userModeInterrupt: jne error pop ecx mov bx, cs - cmp bx, CU_SEG_PROT32|3 ; Did we arrive at proper user mode code? + cmp bx, CU_SEG_PROT32low|3 ; Did we arrive at proper user mode code? jne error pop ebx cmp dword [esp+0x00], userInterruptReturn jne error ; Invalid return address - cmp dword [esp+0x04],CU_SEG_PROT32|3 + cmp dword [esp+0x04],CU_SEG_PROT32low|3 jne error ; Invalid return code segment ; Ignore eflags iret ; Simply return to caller, stays as user mode. @@ -239,3 +206,6 @@ kernelInterrupt_validateIsV86mode: test dword [esp+8],0x20000 ;V86 bit set? jz error ;V86 bit not properly set? iret + +DefaultExcHandlerLow: + jmp C_SEG_PROT32:DefaultExcHandler \ No newline at end of file diff --git a/src/protected_m.asm b/src/protected_m.asm index 141b922..975b416 100644 --- a/src/protected_m.asm +++ b/src/protected_m.asm @@ -136,6 +136,37 @@ %assign LDTSelDesc LDTSelDesc+8 %endmacro +; +; Defines a LDT descriptor, given a name (%1), base (%2), limit (%3), type (%4), and ext (%5) +; +%assign LDTSelDesc 4 +%macro defLDTDescPrototype 1 + %assign %1 LDTSelDesc + %assign LDTSelDesc LDTSelDesc+8 + ;The LDT is prototyped. + %define LDTprototyped +%endmacro + +;Second, the implementation +%macro defLDTDescImplementation 1-5 0,0,0,0 + %ifndef LDTprototyped + %assign %1 LDTSelDesc + %assign nested 0 + %else + %assign nested 1 + %endif + lds ebx, [cs:ptrLDTprot] ; this macro is used in prot mode to set up prot mode env. + mov eax, %1 + mov esi, %2 + mov edi, %3 + mov dx, %4|%5 + initDescriptor + %if nested==0 + %assign LDTSelDesc LDTSelDesc+8 + %endif +%endmacro + + ; ; Updates a LDT descriptor, given a name (%1), base (%2), limit (%3), type (%4), and ext (%5) ; @@ -209,6 +240,10 @@ lss esp, [cs:ptrSSprot] %endmacro +%macro loadProtModeStackLow 0 + lss esp, [cs:ptrSSprot_R0] +%endmacro + ; ; Set a int gate on the IDT in protected mode @@ -257,6 +292,54 @@ popad %endmacro +; +; Set a int gate on the IDT in protected mode +; +; %1: vector +; %2: offset +; %3: DPL, use ACC_DPL_* equs (optional) +; +; the stack must be initialized +; +%macro setProtModeIntGateLow 2-3 -1 + pushad + pushf + mov ax, ds ; save ds + push ax + mov eax, %1 + mov edi, %2 + %if %3 != -1 + mov dx, %3 + %else + mov dx, cs + and dx, 7 + shl dx, 13 + %endif + cmp dx, ACC_DPL_0 + jne %%dpl3 +%%dpl0: + mov esi, C_SEG_PROT32low + jmp %%cont +%%dpl3: + mov esi, CU_SEG_PROT32low +%%cont: + mov cx, cs + test cx, 7 + jnz %%ring3 +%%ring0: + lds ebx, [cs:ptrIDTprot_R0] + call far C_SEG_PROT32:initIntGateProtFar + jmp %%call +%%ring3: + lds ebx, [cs:ptrIDTUprot_R3] + call far CU_SEG_PROT32|3:initIntGateProtFar +%%call: + pop ax + mov ds, ax ; restore ds + popf + popad +%endmacro + ; ; Tests a fault ; @@ -276,6 +359,16 @@ setProtModeIntGate %1, DefaultExcHandler, ACC_DPL_0 %endmacro +%macro protModeFaultTestLow 3+ + setProtModeIntGateLow %1, %%continue +%%test: + %3 + jmp error +%%continue: + protModeExcCheckLow %1, %2, %%test + setProtModeIntGateLow %1, DefaultExcHandlerLow, ACC_DPL_0 +%endmacro + ; %1: vector ; %2: expected error code ; %3: the provilege level the test code will run in @@ -303,6 +396,33 @@ setProtModeIntGate %1, DefaultExcHandler, ACC_DPL_0 %endmacro +; %1: vector +; %2: expected error code +; %3: the provilege level the test code will run in +; %4: the expected value of pushed EIP (specify if %5 is a call, otherwise use -1) +; %5: fault causing code (can be a call to procedure) +; +; The fault handler is executed in ring 0. The caller must reset the data segments. +; +%macro protModeFaultTestExLow 5+ + setProtModeIntGateLow %1, %%continue, ACC_DPL_0 +%%test: + %5 + jmp error +%%continue: + %if %3 = 0 + %assign expectedCS C_SEG_PROT32low + %else + %assign expectedCS CU_SEG_PROT32low|3 + %endif + %if %4 = -1 + protModeExcCheckLow %1, %2, %%test, expectedCS + %else + protModeExcCheckLow %1, %2, %4, expectedCS + %endif + setProtModeIntGateLow %1, DefaultExcHandlerLow, ACC_DPL_0 +%endmacro + ; %1: vector ; %2: expected error code ; %3: the provilege level the test code will run in @@ -312,15 +432,15 @@ ; The fault handler is executed in ring 0. The caller must reset the data segments. ; %macro protModeFaultTestExV86 5+ - setProtModeIntGate %1, %%continue, ACC_DPL_0 + setProtModeIntGateLow %1, %%continue, ACC_DPL_0 %%test: %5 jmp error %%continue: %if %3 = 0 - %assign expectedCS C_SEG_PROT32 + %assign expectedCS C_SEG_PROT32low %else - %assign expectedCS 0xF000 + %assign expectedCS 0xE000 %endif %if %4 = -1 protModeExcCheckV86 %1, %2, %%test, expectedCS @@ -328,7 +448,7 @@ protModeExcCheckV86 %1, %2, %4, expectedCS %endif call switchedToRing0V86_cleanup ;Cleanup the user-mode stack and restore segment registers for kernel mode - setProtModeIntGate %1, DefaultExcHandler, ACC_DPL_0 + setProtModeIntGateLow %1, DefaultExcHandlerLow, ACC_DPL_0 %endmacro ; Tests an user-mode exception @@ -351,6 +471,22 @@ jmp %%startinglabel testCPL 0 %endmacro +%macro testUserFaultLow 3+ +jmp %%startinglabel +%%usercodelabel: + call switchToRing3low + mov ax, DU_SEG_PROT|3 + mov ss, ax + mov esp, 0x1004 +%%instructionlabel: + %3 + jmp error +%%startinglabel: + loadProtModeStackLow + protModeFaultTestExLow %1, %2, 3, %%instructionlabel, call %%usercodelabel + testCPL 0 +%endmacro + ; Tests an user-mode exception (Virtual 8086 mode) under IOPL 0 ;%1: exception number ;%2: fault error code @@ -365,7 +501,7 @@ jmp %%startinglabel jmp error bits 32 %%startinglabel: - loadProtModeStack + loadProtModeStackLow protModeFaultTestExV86 %1, %2, 3, %%instructionlabel, call %%usercodelabel testCPL 0 %endmacro @@ -384,7 +520,7 @@ jmp %%startinglabel jmp error bits 32 %%startinglabel: - loadProtModeStack + loadProtModeStackLow protModeFaultTestExV86 %1, %2, 3, %%instructionlabel, call %%usercodelabel testCPL 0 %endmacro @@ -414,6 +550,26 @@ jmp %%startinglabel testCPL 0 %endmacro +%macro testUserFaultExLow 4+ +jmp %%startinglabel +%%usercodelabel: + call switchToRing3low + mov ax, DU_SEG_PROT|3 + mov ss, ax + mov esp, 0x1004 +%%instructionlabel: + %4 + jmp error +%%startinglabel: + loadProtModeStackLow + %if %3 = -1 + protModeFaultTestExLow %1, %2, 3, %%instructionlabel, call %%usercodelabel + %else + protModeFaultTestExLow %1, %2, 3, %3, call %%usercodelabel + %endif + testCPL 0 +%endmacro + ; Tests an user-mode exception with custom fault point (Virtual 8086 mode) under IOPL 0 ;%1: exception number ;%2: fault error code @@ -429,7 +585,7 @@ jmp %%startinglabel jmp error bits 32 %%startinglabel: - loadProtModeStack + loadProtModeStackLow %if %3 = -1 protModeFaultTestExV86 %1, %2, 3, %%instructionlabel, call %%usercodelabel %else @@ -453,7 +609,7 @@ jmp %%startinglabel jmp error bits 32 %%startinglabel: - loadProtModeStack + loadProtModeStackLow %if %3 = -1 protModeFaultTestExV86 %1, %2, 3, %%instructionlabel, call %%usercodelabel %else @@ -499,6 +655,35 @@ jmp %%startinglabel add esp, 12+exc_errcode %endmacro +%macro protModeExcCheckLow 3-4 -1 + %if %1 == 8 || (%1 > 10 && %1 <= 14) + %assign exc_errcode 4 + cmp [ss:esp], dword %2 + jne error + %else + %assign exc_errcode 0 + %endif + %if %4 != -1 + cmp [ss:esp+exc_errcode+4], dword %4 + jne error + %else + mov bx, cs + test bx, 7 + jnz %%ring3 + %%ring0: + cmp [ss:esp+exc_errcode+4], dword C_SEG_PROT32low + jne error + jmp %%continue + %%ring3: + cmp [ss:esp+exc_errcode+4], dword CU_SEG_PROT32low|3 + jne error + %%continue: + %endif + cmp [ss:esp+exc_errcode], dword %3 + jne error + add esp, 12+exc_errcode +%endmacro + ; ; Checks exception result and restores the previous handler ; @@ -542,11 +727,11 @@ jmp %%startinglabel test bx, 7 jnz %%ring3 %%ring0: - cmp [ss:esp+exc_errcode+4], dword 0xF000 + cmp [ss:esp+exc_errcode+4], dword 0xE000 jne error jmp %%continue %%ring3: - cmp [ss:esp+exc_errcode+4], dword 0xF000 + cmp [ss:esp+exc_errcode+4], dword 0xE000 jne error %%continue: %endif diff --git a/src/protected_p.asm b/src/protected_p.asm index be74bd2..0934f7f 100644 --- a/src/protected_p.asm +++ b/src/protected_p.asm @@ -4,6 +4,10 @@ initIntGateProt: initIntGate ret +initIntGateProtFar: + call initIntGate + retf + initIntTaskGateProt: initIntTaskGate ret @@ -45,6 +49,10 @@ initCallGate: mov word [fs:ebx+6], di ; DESTINATION OFFSET 31-16 ret +initCallGateFar: + call initCallGate + retf + ; ; Defines a Call Gate in GDT ; @@ -76,3 +84,7 @@ initCallGate286: mov word [fs:ebx+4], dx ; ACC byte | WORD COUNT 4-0 mov word [fs:ebx+6], 0xFFFF ; DESTINATION OFFSET 31-16 (isn't used. Load invalid linear address value to validate it) ret + +initCallGate286Far: + call initCallGate286 + retf diff --git a/src/protected_rings_p.asm b/src/protected_rings_p.asm index ac3c13a..7f3b9af 100644 --- a/src/protected_rings_p.asm +++ b/src/protected_rings_p.asm @@ -83,7 +83,7 @@ switchToRing3FLATkernel: push dword ESP_R3_PROT ; push user mode esp pushfd ; push eflags or dword [ss:esp], 0x200 ; reenable interrupts in ring 3 (can't use privileged sti) - push dword CU_SEG_PROT32|3 ; push user code segment with RPL=3 + push dword CU_SEG_PROT32low|3 ; push user code segment with RPL=3 push dword edx ; push return EIP iretd @@ -131,104 +131,6 @@ switchToRing3FLATuser: push dword edx ; push return EIP iretd - -; -; Switches from Ring 0 to Ring 3 in V86 mode -; -; After calling this procedure consider all the registers and flags as trashed. -; Also, the stack will be different, so saving the CPU state there will be pointless. -; -switchToRing3V86_0: - ; In order to swich to user mode (ring 3) we need to execute an IRET with these - ; values on the stack: - ; - the instruction to continue execution at - the value of EIP. - ; - the code segment selector to change to. - ; - the value of the EFLAGS register to load. - ; - the stack pointer to load. - ; - the stack segment selector to change to. - ; We also need: - ; - a 32bit code descriptor in GDT with DPL 3 - ; - a 32bit data descriptor in GDT with DPL 3 (for the new stack) - ; - to put the ring 0 stack in TSS.SS0 and TSS.ESP0 - testCPL 0 ; we must be in ring 0 - pop edx ; read the return offset - mov ax, ds - lds ebx, [cs:ptrTSSprot] - ; save ring 0 data segments, they'll be restored with interrupts/exceptions - mov [ebx+0x54], ax ; save DS - mov ax, es - mov [ebx+0x48], ax ; save ES - mov ax, fs - mov [ebx+0x58], ax ; save FS - mov ax, gs - mov [ebx+0x5C], ax ; save GS - ; set ring 0 SS:ESP - mov [ebx+4], esp - mov eax, ss - mov [ebx+8], eax - cli ; disable ints during switching - push dword V86_GS ; V86 mode GS default to ROM - push dword V86_FS ; V86 mode FS default to ROM - push dword V86_DS ; V86 mode DS default to ROM - push dword V86_ES ; V86 mode ES default to ROM - push dword 0x1000 ; push user stack with RPL=3 - push dword ESP_R3_PROT ; push user mode esp - pushfd ; push eflags - or dword [ss:esp], 0x20200 ; reenable interrupts in ring 3 (can't use privileged sti), V8086 flag set, IOPL 0 - and dword [ss:esp], 0xF0FFF ; setup IOPL 0 properly - push dword 0xF000 ; push user code segment with RPL=3 - push dword edx ; push return EIP - iretd - - -; -; Switches from Ring 0 to Ring 3 in V86 mode -; -; After calling this procedure consider all the registers and flags as trashed. -; Also, the stack will be different, so saving the CPU state there will be pointless. -; -switchToRing3V86_3: - ; In order to swich to user mode (ring 3) we need to execute an IRET with these - ; values on the stack: - ; - the instruction to continue execution at - the value of EIP. - ; - the code segment selector to change to. - ; - the value of the EFLAGS register to load. - ; - the stack pointer to load. - ; - the stack segment selector to change to. - ; We also need: - ; - a 32bit code descriptor in GDT with DPL 3 - ; - a 32bit data descriptor in GDT with DPL 3 (for the new stack) - ; - to put the ring 0 stack in TSS.SS0 and TSS.ESP0 - testCPL 0 ; we must be in ring 0 - pop edx ; read the return offset - mov ax, ds - lds ebx, [cs:ptrTSSprot] - ; save ring 0 data segments, they'll be restored with interrupts/exceptions - mov [ebx+0x54], ax ; save DS - mov ax, es - mov [ebx+0x48], ax ; save ES - mov ax, fs - mov [ebx+0x58], ax ; save FS - mov ax, gs - mov [ebx+0x5C], ax ; save GS - ; set ring 0 SS:ESP - mov [ebx+4], esp - mov eax, ss - mov [ebx+8], eax - cli ; disable ints during switching - push dword V86_GS ; V86 mode GS default to ROM - push dword V86_FS ; V86 mode FS default to ROM - push dword V86_DS ; V86 mode DS default to ROM - push dword V86_ES ; V86 mode ES default to ROM - push dword 0x1000 ; push user stack with RPL=3 - push dword ESP_R3_PROT ; push user mode esp - pushfd ; push eflags - or dword [ss:esp], 0x23200 ; reenable interrupts in ring 3 (can't use privileged sti), V8086 flag set, IOPL 3 - push dword 0xF000 ; push user code segment with RPL=3 - push dword edx ; push return EIP - iretd - - ; ; Switches from Ring 3 to Ring 0 (Non-V86 mode) ; @@ -287,190 +189,6 @@ switchToRing0FromFlatUser: push ecx ret - -; -; Test routine for call gate with parameters -; -testCallGateWithParameters: - push dword switchToRing0_2 ; Where to start - jmp switchToRing3 ; Switch to ring 3 to start the test - - -; -; Switches from Ring 3 to Ring 0 (Non-V86 mode) and calls back into kernel mode with parameters -; -; After calling this procedure consider all the registers and flags as trashed. -; -switchToRing0_2: - testCPL 3 ; we must be in ring 3 - ; In order to swich to kernel mode (ring 0) we'll use a Call Gate. - ; A placeholder for a Call Gate is already present in the GDT. - mov ecx, ring0_2TestEndLocation ; read the return offset - ;Setup a 32-bit call gate - lfs ebx, [cs:ptrGDTUprot] - mov eax, RING0_GATE2 - mov esi, C_SEG_PROT32 - mov edi, .ring0_2 - mov dx, ACC_DPL_3|0xA ; the DPL needs to be 3 - call initCallGate - - push ecx ;Save caller - - ; Create a stack of 32-bit test values to transfer using the call gate - push dword 0x12347654 - push dword 0x5678CBA9 - push dword 0x9ABC3333 - push dword 0xDEF02222 - push dword 0x11221111 - push dword 0x33447777 - push dword 0x55665555 - push dword 0x7788AAAA - push dword 0x99AA4444 - push dword 0xBBCCFFFF - - ; 32-bit call gate - call RING0_GATE2|3:0 ; the RPL needs to be 3, the offset will be ignored. - - ;Setup a 16-bit call gate - lfs ebx, [cs:ptrGDTUprot] - mov eax, RING0_GATE2 - mov esi, C_SEG_PROT32 - mov edi, .ring0_3 - mov dx, ACC_DPL_3|0xA ; the DPL needs to be 3 - call initCallGate286 - - ; Create a stack of 16-bit test values to transfer using the call gate - push word 0x1234 - push word 0x5678 - push word 0x9ABC - push word 0xDEF0 - push word 0x1122 - push word 0x3344 - push word 0x5566 - push word 0x7788 - push word 0x99AA - push word 0xBBCC - - ; 16-bit call gate - call RING0_GATE2|3:0 ; the RPL needs to be 3, the offset will be ignored. - - pop ecx ; Restore caller - call RING0_GATE|3:0 ; the RPL needs to be 3, the offset will be ignored. - - ; This time we return to the caller proper -.ring0_2: ;32-bit call gate entry point - push ds - push ebp - push ebx - lds ebp, [cs:ptrTSSprot] - mov ebx, [ds:ebp+4] ; Get the base - sub ebx, 0x10+0xC+40 ; Where we should end up in the kernel stack now - cmp esp, ebx ; Wrong kernel stack? - jnz error - mov ebx, [ds:ebp+8] ; Get the stack - mov bp, ss - cmp bx, bp - jnz error ; Invalid kernel stack - pop ebx - pop ebp - pop ds - push eax - mov ax, cs - cmp ax, C_SEG_PROT32 - jnz error - pop eax - ; Validate the parameters on the kernel stack - cmp [esp+0x2C], dword 0x12347654 - jne error - cmp [esp+0x28], dword 0x5678CBA9 - jne error - cmp [esp+0x24], dword 0x9ABC3333 - jne error - cmp [esp+0x20], dword 0xDEF02222 - jne error - cmp [esp+0x1C], dword 0x11221111 - jne error - cmp [esp+0x18], dword 0x33447777 - jne error - cmp [esp+0x14], dword 0x55665555 - jne error - cmp [esp+0x10], dword 0x7788AAAA - jne error - cmp [esp+0x0C], dword 0x99AA4444 - jne error - cmp [esp+0x08], dword 0xBBCCFFFF - jne error - retf 40 ; Return to the user mode code, discarding the parameters from the stack. -.ring0_3: ;16-bit call gate entry point - push ds - push ebp - push ebx - lds ebp, [cs:ptrTSSprot] - mov ebx, [ds:ebp+4] ; Get the base - sub ebx, 0x8+0xC+20 ; Where we should end up in the kernel stack now - cmp esp, ebx ; Wrong kernel stack? - jnz error - mov ebx, [ds:ebp+8] ; Get the stack - mov bp, ss - cmp bx, bp - jnz error ; Invalid kernel stack - pop ebx - pop ebp - pop ds - push eax - mov ax, cs - cmp ax, C_SEG_PROT32 - jnz error - pop eax - ; Validate the parameters on the kernel stack - cmp [esp+0x16], word 0x1234 - jne error - cmp [esp+0x14], word 0x5678 - jne error - cmp [esp+0x12], word 0x9ABC - jne error - cmp [esp+0x10], word 0xDEF0 - jne error - cmp [esp+0x0E], word 0x1122 - jne error - cmp [esp+0x0C], word 0x3344 - jne error - cmp [esp+0x0A], word 0x5566 - jne error - cmp [esp+0x08], word 0x7788 - jne error - cmp [esp+0x06], word 0x99AA - jne error - cmp [esp+0x04], word 0xBBCC - jne error - o16 retf 20 ; Return to the user mode code, discarding the parameters from the stack. - - - -; -; Handles cleanup after returning from Ring 3 (Virtual 8086 mode) to Ring 0 -; -; After calling this procedure consider all the registers and flags as trashed. Assumes that the error code has already been popped. -; -switchedToRing0V86_cleanup: - push ds - push ebx - lds ebx, [cs:ptrTSSprot] - push eax - ; restore ring 0 data segments, as they'll be restored with interrupts/exceptions calling us. - mov ax, [ebx+0x54] ; restore DS - mov [esp+8], ax ; Store on the stack to pop later - mov ax, [ebx+0x48] ; restore ES - mov es, ax - mov ax, [ebx+0x58] ; restore FS - mov fs, ax - mov ax, [ebx+0x5C] ; restore GS - mov gs, ax - pop eax - pop ebx - pop ds - ret - ; ; Handles cleanup after returning from Ring 3 (Flat 32-bit mode) to Ring 0 ; @@ -493,17 +211,4 @@ switchedToRing0FromFlat_cleanup: pop eax pop ebx pop ds - ret - - -; -; Switches from Ring 3 to Ring 0 (V86 mode). This is a jump target, not a call target. -; -; After calling this procedure consider all the registers and flags as trashed. -; -switchToRing0V86: - bits 16 - pop eax ; read the return offset - int 0x25 - bits 32 - + ret \ No newline at end of file diff --git a/src/protected_rings_tssp.asm b/src/protected_rings_tssp.asm new file mode 100644 index 0000000..6dfb845 --- /dev/null +++ b/src/protected_rings_tssp.asm @@ -0,0 +1,462 @@ +; +; Switches from Ring 0 to Ring 3 +; +; After calling this procedure consider all the registers and flags as trashed. +; Also, the stack will be different, so saving the CPU state there will be pointless. +; +switchToRing3low: + ; In order to swich to user mode (ring 3) we need to execute an IRET with these + ; values on the stack: + ; - the instruction to continue execution at - the value of EIP. + ; - the code segment selector to change to. + ; - the value of the EFLAGS register to load. + ; - the stack pointer to load. + ; - the stack segment selector to change to. + ; We also need: + ; - a 32bit code descriptor in GDT with DPL 3 + ; - a 32bit data descriptor in GDT with DPL 3 (for the new stack) + ; - to put the ring 0 stack in TSS.SS0 and TSS.ESP0 + testCPL 0 ; we must be in ring 0 + pop edx ; read the return offset + mov ax, ds + lds ebx, [cs:ptrTSSprot_R0] + ; save ring 0 data segments, they'll be restored with switchToRing0 + mov [ebx+0x54], ax ; save DS + mov ax, es + mov [ebx+0x48], ax ; save ES + mov ax, fs + mov [ebx+0x58], ax ; save FS + mov ax, gs + mov [ebx+0x5C], ax ; save GS + ; set ring 0 SS:ESP + mov [ebx+4], esp + mov eax, ss + mov [ebx+8], eax + cli ; disable ints during switching + push dword SU_SEG_PROT32|3 ; push user stack with RPL=3 + push dword ESP_R3_PROT ; push user mode esp + pushfd ; push eflags + or dword [ss:esp], 0x200 ; reenable interrupts in ring 3 (can't use privileged sti) + push dword CU_SEG_PROT32low|3 ; push user code segment with RPL=3 + push dword edx ; push return EIP + iretd + +; +; Switches from Ring 0 to Ring 3, to return to ring 0 in flat mode later +; +; After calling this procedure consider all the registers and flags as trashed. +; Also, the stack will be different, so saving the CPU state there will be pointless. +; +switchToRing3FLATkernelLow: + ; In order to swich to user mode (ring 3) we need to execute an IRET with these + ; values on the stack: + ; - the instruction to continue execution at - the value of EIP. + ; - the code segment selector to change to. + ; - the value of the EFLAGS register to load. + ; - the stack pointer to load. + ; - the stack segment selector to change to. + ; We also need: + ; - a 32bit code descriptor in GDT with DPL 3 + ; - a 32bit data descriptor in GDT with DPL 3 (for the new stack) + ; - to put the ring 0 stack in TSS.SS0 and TSS.ESP0 + testCPL 0 ; we must be in ring 0 + pop edx ; read the return offset + mov ax, ds + lds ebx, [cs:ptrTSSprot_R0] + ; save ring 0 data segments, they'll be restored with switchToRing0 + mov [ebx+0x54], ax ; save DS + mov ax, es + mov [ebx+0x48], ax ; save ES + mov ax, fs + mov [ebx+0x58], ax ; save FS + mov ax, gs + mov [ebx+0x5C], ax ; save GS + ; set ring 0 SS:ESP + mov [ebx+4], esp + pushfd + or dword [ebx+4], 0xE0010000 ; make sure we return to a proper ring0 stack + popfd + mov eax, D_SEG_PROT32FLAT + mov [ebx+8], eax + cli ; disable ints during switching + push dword SU_SEG_PROT32|3 ; push user stack with RPL=3 + push dword ESP_R3_PROT ; push user mode esp + pushfd ; push eflags + or dword [ss:esp], 0x200 ; reenable interrupts in ring 3 (can't use privileged sti) + push dword CU_SEG_PROT32low|3 ; push user code segment with RPL=3 + push dword edx ; push return EIP + iretd +; +; Switches from Ring 0 to Ring 3 in flat mode +; +; After calling this procedure consider all the registers and flags as trashed. +; Also, the stack will be different, so saving the CPU state there will be pointless. +; +switchToRing3FLATuserLow: + ; In order to swich to user mode (ring 3) we need to execute an IRET with these + ; values on the stack: + ; - the instruction to continue execution at - the value of EIP. + ; - the code segment selector to change to. + ; - the value of the EFLAGS register to load. + ; - the stack pointer to load. + ; - the stack segment selector to change to. + ; We also need: + ; - a 32bit code descriptor in GDT with DPL 3 + ; - a 32bit data descriptor in GDT with DPL 3 (for the new stack) + ; - to put the ring 0 stack in TSS.SS0 and TSS.ESP0 + testCPL 0 ; we must be in ring 0 + pop edx ; read the return offset + mov ax, ds + lds ebx, [cs:ptrTSSprot_R0] + ; save ring 0 data segments, they'll be restored with switchedToRing0FromFlat_cleanup + mov [ebx+0x68], ax ; save DS + mov ax, es + mov [ebx+0x6A], ax ; save ES + mov ax, fs + mov [ebx+0x6C], ax ; save FS + mov ax, gs + mov [ebx+0x6E], ax ; save GS + ; set ring 0 SS:ESP + mov [ebx+4], esp + mov eax, ss + mov [ebx+8], eax + cli ; disable ints during switching + push dword DU_SEG_PROT32FLAT|3 ; push user stack with RPL=3 + push dword ESP_R3_PROTFLAT ; push user mode esp + pushfd ; push eflags + ; don't reenable interrupts in ring 3 (can't use privileged sti) for ease of testing + push dword CU_SEG_PROT32FLAT|3 ; push user code segment with RPL=3 + or edx,0xE0000 ; Fix flat instruction address + push dword edx ; push return EIP + iretd + + +; +; Switches from Ring 0 to Ring 3 in V86 mode +; +; After calling this procedure consider all the registers and flags as trashed. +; Also, the stack will be different, so saving the CPU state there will be pointless. +; +switchToRing3V86_0: + ; In order to swich to user mode (ring 3) we need to execute an IRET with these + ; values on the stack: + ; - the instruction to continue execution at - the value of EIP. + ; - the code segment selector to change to. + ; - the value of the EFLAGS register to load. + ; - the stack pointer to load. + ; - the stack segment selector to change to. + ; We also need: + ; - a 32bit code descriptor in GDT with DPL 3 + ; - a 32bit data descriptor in GDT with DPL 3 (for the new stack) + ; - to put the ring 0 stack in TSS.SS0 and TSS.ESP0 + testCPL 0 ; we must be in ring 0 + pop edx ; read the return offset + mov ax, ds + lds ebx, [cs:ptrTSSprot_R0] + ; save ring 0 data segments, they'll be restored with interrupts/exceptions + mov [ebx+0x54], ax ; save DS + mov ax, es + mov [ebx+0x48], ax ; save ES + mov ax, fs + mov [ebx+0x58], ax ; save FS + mov ax, gs + mov [ebx+0x5C], ax ; save GS + ; set ring 0 SS:ESP + mov [ebx+4], esp + mov eax, ss + mov [ebx+8], eax + cli ; disable ints during switching + push dword V86_GS ; V86 mode GS default to ROM + push dword V86_FS ; V86 mode FS default to ROM + push dword V86_DS ; V86 mode DS default to ROM + push dword V86_ES ; V86 mode ES default to ROM + push dword 0x1000 ; push user stack with RPL=3 + push dword ESP_R3_PROT ; push user mode esp + pushfd ; push eflags + or dword [ss:esp], 0x20200 ; reenable interrupts in ring 3 (can't use privileged sti), V8086 flag set, IOPL 0 + and dword [ss:esp], 0xF0FFF ; setup IOPL 0 properly + push dword 0xE000 ; push user code segment with RPL=3 + push dword edx ; push return EIP + iretd + + +; +; Switches from Ring 0 to Ring 3 in V86 mode +; +; After calling this procedure consider all the registers and flags as trashed. +; Also, the stack will be different, so saving the CPU state there will be pointless. +; +switchToRing3V86_3: + ; In order to swich to user mode (ring 3) we need to execute an IRET with these + ; values on the stack: + ; - the instruction to continue execution at - the value of EIP. + ; - the code segment selector to change to. + ; - the value of the EFLAGS register to load. + ; - the stack pointer to load. + ; - the stack segment selector to change to. + ; We also need: + ; - a 32bit code descriptor in GDT with DPL 3 + ; - a 32bit data descriptor in GDT with DPL 3 (for the new stack) + ; - to put the ring 0 stack in TSS.SS0 and TSS.ESP0 + testCPL 0 ; we must be in ring 0 + pop edx ; read the return offset + mov ax, ds + lds ebx, [cs:ptrTSSprot_R0] + ; save ring 0 data segments, they'll be restored with interrupts/exceptions + mov [ebx+0x54], ax ; save DS + mov ax, es + mov [ebx+0x48], ax ; save ES + mov ax, fs + mov [ebx+0x58], ax ; save FS + mov ax, gs + mov [ebx+0x5C], ax ; save GS + ; set ring 0 SS:ESP + mov [ebx+4], esp + mov eax, ss + mov [ebx+8], eax + cli ; disable ints during switching + push dword V86_GS ; V86 mode GS default to ROM + push dword V86_FS ; V86 mode FS default to ROM + push dword V86_DS ; V86 mode DS default to ROM + push dword V86_ES ; V86 mode ES default to ROM + push dword 0x1000 ; push user stack with RPL=3 + push dword ESP_R3_PROT ; push user mode esp + pushfd ; push eflags + or dword [ss:esp], 0x23200 ; reenable interrupts in ring 3 (can't use privileged sti), V8086 flag set, IOPL 3 + push dword 0xE000 ; push user code segment with RPL=3 + push dword edx ; push return EIP + iretd + + +; +; Switches from Ring 3 to Ring 0 (Non-V86 mode) +; +; After calling this procedure consider all the registers and flags as trashed. +; +switchToRing0low: + testCPL 3 ; we must be in ring 3 + ; In order to swich to kernel mode (ring 0) we'll use a Call Gate. + ; A placeholder for a Call Gate is already present in the GDT. + pop ecx ; read the return offset + lfs ebx, [cs:ptrGDTUprot_R2] + mov eax, RING0_GATE + mov esi, C_SEG_PROT32low + mov edi, .ring0 + mov dx, ACC_DPL_3 ; the DPL needs to be 3 + call CU_SEG_PROT32:initCallGateFar + call RING0_GATE|3:0 ; the RPL needs to be 3, the offset will be ignored. +.ring0: + add esp, 16 ; remove from stack CS:EIP+SS:ESP pushed by the CALL to RING0_GATE + ; restore ring 0 data segments saved by switchToRing3 + lds ebx, [cs:ptrTSSprot_R2] + mov ax, [ebx+0x48] ; restore ES + mov es, ax + mov ax, [ebx+0x58] ; restore FS + mov fs, ax + mov ax, [ebx+0x5C] ; restore GS + mov gs, ax + mov ax, [ebx+0x54] ; restore DS + mov ds, ax + ; return to caller + push ecx + ret + +; +; Test routine for call gate with parameters +; +testCallGateWithParameters: + push dword switchToRing0_2 ; Where to start + jmp switchToRing3low ; Switch to ring 3 to start the test + +errorTSS: + ; If we end up here, the test failed. Loop forever. + cli + hlt + +; +; Switches from Ring 3 to Ring 0 (Non-V86 mode) and calls back into kernel mode with parameters +; +; After calling this procedure consider all the registers and flags as trashed. +; +switchToRing0_2: + testCPL 3 ; we must be in ring 3 + ; In order to swich to kernel mode (ring 0) we'll use a Call Gate. + ; A placeholder for a Call Gate is already present in the GDT. + mov ecx, ring0_2TestEndLocation ; read the return offset + ;Setup a 32-bit call gate + lfs ebx, [cs:ptrGDTUprot_R2] + mov eax, RING0_GATE2 + mov esi, C_SEG_PROT32low + mov edi, .ring0_2 + mov dx, ACC_DPL_3|0xA ; the DPL needs to be 3 + call CU_SEG_PROT32:initCallGateFar + + push ecx ;Save caller + + ; Create a stack of 32-bit test values to transfer using the call gate + push dword 0x12347654 + push dword 0x5678CBA9 + push dword 0x9ABC3333 + push dword 0xDEF02222 + push dword 0x11221111 + push dword 0x33447777 + push dword 0x55665555 + push dword 0x7788AAAA + push dword 0x99AA4444 + push dword 0xBBCCFFFF + + ; 32-bit call gate + call RING0_GATE2|3:0 ; the RPL needs to be 3, the offset will be ignored. + + ;Setup a 16-bit call gate + lfs ebx, [cs:ptrGDTUprot_R2] + mov eax, RING0_GATE2 + mov esi, C_SEG_PROT32low + mov edi, .ring0_3 + mov dx, ACC_DPL_3|0xA ; the DPL needs to be 3 + call CU_SEG_PROT32:initCallGate286Far + + ; Create a stack of 16-bit test values to transfer using the call gate + push word 0x1234 + push word 0x5678 + push word 0x9ABC + push word 0xDEF0 + push word 0x1122 + push word 0x3344 + push word 0x5566 + push word 0x7788 + push word 0x99AA + push word 0xBBCC + + ; 16-bit call gate + call RING0_GATE2|3:0 ; the RPL needs to be 3, the offset will be ignored. + + pop ecx ; Restore caller + call RING0_GATE|3:0 ; the RPL needs to be 3, the offset will be ignored. + + ; This time we return to the caller proper +.ring0_2: ;32-bit call gate entry point + push ds + push ebp + push ebx + lds ebp, [cs:ptrTSSprot_R2] + mov ebx, [ds:ebp+4] ; Get the base + sub ebx, 0x10+0xC+40 ; Where we should end up in the kernel stack now + cmp esp, ebx ; Wrong kernel stack? + jnz errorTSS + mov ebx, [ds:ebp+8] ; Get the stack + mov bp, ss + cmp bx, bp + jnz errorTSS ; Invalid kernel stack + pop ebx + pop ebp + pop ds + push eax + mov ax, cs + cmp ax, C_SEG_PROT32low + jnz errorTSS + pop eax + ; Validate the parameters on the kernel stack + cmp [esp+0x2C], dword 0x12347654 + jne errorTSS + cmp [esp+0x28], dword 0x5678CBA9 + jne errorTSS + cmp [esp+0x24], dword 0x9ABC3333 + jne errorTSS + cmp [esp+0x20], dword 0xDEF02222 + jne errorTSS + cmp [esp+0x1C], dword 0x11221111 + jne errorTSS + cmp [esp+0x18], dword 0x33447777 + jne errorTSS + cmp [esp+0x14], dword 0x55665555 + jne errorTSS + cmp [esp+0x10], dword 0x7788AAAA + jne errorTSS + cmp [esp+0x0C], dword 0x99AA4444 + jne errorTSS + cmp [esp+0x08], dword 0xBBCCFFFF + jne errorTSS + retf 40 ; Return to the user mode code, discarding the parameters from the stack. +.ring0_3: ;16-bit call gate entry point + push ds + push ebp + push ebx + lds ebp, [cs:ptrTSSprot_R2] + mov ebx, [ds:ebp+4] ; Get the base + sub ebx, 0x8+0xC+20 ; Where we should end up in the kernel stack now + cmp esp, ebx ; Wrong kernel stack? + jnz errorTSS + mov ebx, [ds:ebp+8] ; Get the stack + mov bp, ss + cmp bx, bp + jnz errorTSS ; Invalid kernel stack + pop ebx + pop ebp + pop ds + push eax + mov ax, cs + cmp ax, C_SEG_PROT32low + jnz errorTSS + pop eax + ; Validate the parameters on the kernel stack + cmp [esp+0x16], word 0x1234 + jne errorTSS + cmp [esp+0x14], word 0x5678 + jne errorTSS + cmp [esp+0x12], word 0x9ABC + jne errorTSS + cmp [esp+0x10], word 0xDEF0 + jne errorTSS + cmp [esp+0x0E], word 0x1122 + jne errorTSS + cmp [esp+0x0C], word 0x3344 + jne errorTSS + cmp [esp+0x0A], word 0x5566 + jne errorTSS + cmp [esp+0x08], word 0x7788 + jne errorTSS + cmp [esp+0x06], word 0x99AA + jne errorTSS + cmp [esp+0x04], word 0xBBCC + jne errorTSS + o16 retf 20 ; Return to the user mode code, discarding the parameters from the stack. + + + +; +; Handles cleanup after returning from Ring 3 (Virtual 8086 mode) to Ring 0 +; +; After calling this procedure consider all the registers and flags as trashed. Assumes that the error code has already been popped. +; +switchedToRing0V86_cleanup: + push ds + push ebx + lds ebx, [cs:ptrTSSprot_R2] + push eax + ; restore ring 0 data segments, as they'll be restored with interrupts/exceptions calling us. + mov ax, [ebx+0x54] ; restore DS + mov [esp+8], ax ; Store on the stack to pop later + mov ax, [ebx+0x48] ; restore ES + mov es, ax + mov ax, [ebx+0x58] ; restore FS + mov fs, ax + mov ax, [ebx+0x5C] ; restore GS + mov gs, ax + pop eax + pop ebx + pop ds + ret + + +; +; Switches from Ring 3 to Ring 0 (V86 mode). This is a jump target, not a call target. +; +; After calling this procedure consider all the registers and flags as trashed. +; +switchToRing0V86: + bits 16 + pop eax ; read the return offset + int 0x25 + bits 32 + diff --git a/src/protected_tssinth.asm b/src/protected_tssinth.asm index 2103fa2..cdcc5f3 100644 --- a/src/protected_tssinth.asm +++ b/src/protected_tssinth.asm @@ -3,16 +3,71 @@ BITS 32 ptrTSSprot_R2: ; pointer to the task state segment dd 0 dw TSSU_DSEG_PROT32|3 +ptrTSSprot_R0: ; pointer to the task state segment + dd 0 + dw TSS_DSEG_PROT ptrTSSprot16_R2: ; pointer to the 16-bit task state segment dd 0 dw TSSU_DSEG_PROT16|3 ptrTSSerrorR0_2: ; pointer to the error condition, from ring 0 or ring 2 dd error dw CU_SEG_PROT32|3 - - +ptrGDTUprot_R2: ; pointer to the GDT for pmode (user mode data segment) + dd 0 + dw GDTU_DSEG_PROT|3 +ptrIDTprot_R0: ; pointer to the IDT for pmode + dd 0 ; 32-bit offset + dw IDT_SEG_PROT ; 16-bit segment selector +ptrIDTUprot_R3: ; pointer to the IDT for pmode + dd 0 ; 32-bit offset + dw IDTU_SEG_PROT ; 16-bit segment selector +ptrSSprot_R0: ; pointer to the stack for pmode + dd ESP_R0_PROT + dw S_SEG_PROT32 errorCPLn: jmp far [cs:ptrTSSerrorR0_2] ;JMP to the error condition. + +; +; Kernel mode interrupt handler +; +kernelInterrupt: + testCPL 0 ; Elevates to CPL 0 + push ebx + push ecx + push ds + lds ebx, [cs:ptrTSSprot_R2] ; Get the TSS + mov ecx, [ebx+4] ; Get TSS ESP0 + sub ecx, 0x14+0xC ; Where we should end up on the kernel stack, taking into account what we just pushed + cmp esp, ecx ; Did the stack decrease correctly? + jne errorCPLn + mov cx, ss + cmp cx, word [ebx+8] ; Did the stack pointer load correctly? + jne errorCPLn + pop ds + pop ecx + mov bx, cs + cmp bx, C_SEG_PROT16CS ; Did we end up in kernel mode correctly? + jne errorCPLn + pop ebx + cmp dword [esp+0x00], kernelModeInterruptReturn + jne errorCPLn ; Invalid return address + cmp dword [esp+0x04], CU_SEG_PROT32low|3 + jne errorCPLn ; Invalid return code segment + ; Ignore most eflags + test dword [esp+0x08], PS_IF ;EFLAGS input + jz errorCPLn + cmp dword [esp+0x0C], ESP_R3_PROT + jne errorCPLn ; Invalid return ESP + cmp dword [esp+0x10], SU_SEG_PROT32|3 + jne errorCPLn ; Invalid user stack segment + push eax + pushfd + pop eax + test eax, PS_IF ;Incorrect flags. + jnz errorCPLn + pop eax + iret ; Simply return to user mode + ; ; Kernel mode interrupt handler (ring 2) for 32-bit TSS ; @@ -132,15 +187,15 @@ kernelInterrupt_validateV86mode16bit: mov ecx, dword [bx+4] ; Get ESP for the kernel mode program (stored into eax) sub ecx, 0xC+0x12 ; Where we should end up on the kernel stack, taking into account what we just pushed cmp esp, ecx ; Did the stack decrease correctly? - jne error + jne errorCPLn mov cx, ss mov bx, word [bx+8] ; Expected: kernel mode stack cmp cx, bx ; Did the stack pointer load correctly? - jne error + jne errorCPLn pop ecx mov bx, cs cmp bx, C_SEG_PROT16CS ; Did we arrive at proper kernel code? - jne error + jne errorCPLn pop ds pop ebx ;Basic stack pointer verified. Now check if the data on the stack is correct, while building a new stack to return to user mode. @@ -149,38 +204,75 @@ kernelInterrupt_validateV86mode16bit: and eax,0xFFFF ;Mask off upper 16 bits. mov ax, word [esp+(0x04+0x10)] ;GS cmp ax,V86_GS ;Correct? - jne error ;Error if incorrect. + jne errorCPLn ;Error if incorrect. push eax ;Save destination GS mov ax, word [esp+(0x08+0x0E)] ;FS cmp ax,V86_FS ;Correct? - jne error ;Error if incorrect. + jne errorCPLn ;Error if incorrect. push eax ;Save destination FS mov ax, word [esp+(0x0C+0x0C)] ;DS cmp ax,V86_DS ;Correct? - jne error ;Error if incorrect. + jne errorCPLn ;Error if incorrect. push eax ;Save destination DS mov ax, word [esp+(0x10+0x0A)] ;ES cmp ax,V86_ES ;Correct? - jne error ;Error if incorrect. + jne errorCPLn ;Error if incorrect. push eax ;Save destination ES mov ax, word [esp+(0x14+0x08)] ;SS cmp ax,0x1000 ;Correct? - jne error ;Error if incorrect. + jne errorCPLn ;Error if incorrect. push eax ;Save destination SS mov ax, word [esp+(0x18+0x06)] ;SP cmp ax,ESP_R3_PROT ;Correct? - jne error ;Error if incorrect. + jne errorCPLn ;Error if incorrect. push eax ;Save destination ESP mov ax, word [esp+(0x1C+0x04)] ;FLAGS push eax ;Save destination EFLAGS mov ax, word [esp+(0x20+0x02)] ;CS - cmp ax,0xF000 ;Correct? - jne error ;Error if incorrect. + cmp ax,0xE000 ;Correct? + jne errorCPLn ;Error if incorrect. push eax ;Save destination CS mov ax, word [esp+(0x24+0x00)] ;IP cmp ax,userV86_16bitinterruptRET ;IP correct? - jne error ;Incorrect IP address. + jne errorCPLn ;Incorrect IP address. push eax ;Save destination EIP or dword [esp+0x08],0x20000 ;Set VM bit to properly return. mov eax,[esp+0x2C] ;Load original EAX register to restore it. iret +; +; Kernel mode interrupt handler, callable from user mode. Validates if the Virtual 8086 mode is used. +; +BITS 16 +realmodeError: + push word 0xF000 + push word error + retf +realmodeInterrupt: + push ebx + push ecx + mov ecx, eax ; Get ESP for the interrupt program (stored into eax) + sub ecx, 0x6+0x8 ; Where we should end up on the kernel stack, taking into account what we just pushed + cmp sp, cx ; Did the stack decrease correctly? + jne realmodeError + pop ecx + mov bx, cs + cmp bx, 0xE000 ; Did we arrive at proper kernel code? + jne realmodeError + ;Basic stack pointer verified. Now check if the data on the stack is correct, while building a new stack to return to real mode. + ;Ignore most flags + test word [esp+(0x4+0x04)],PS_IF ;EFLAGS input + jz realmodeError + and esp,0xFFFF ;Real mode safety. + mov bx, word [esp+(0x4+0x02)] ;CS + cmp bx,0xE000 ;Correct? + jne error ;Error if incorrect. + mov bx, word [esp+0x4] ;IP + cmp bx,realmodeInterruptRET ;IP correct? + jne realmodeError ;Incorrect IP address. + pushfd + pop eax + test eax,PS_IF ;Incorrect flags. + jnz realmodeError + pop ebx + iret +BITS 32 \ No newline at end of file diff --git a/src/systembiosexpansionarea.asm b/src/systembiosexpansionarea.asm index 6d0a05f..375c8e9 100644 --- a/src/systembiosexpansionarea.asm +++ b/src/systembiosexpansionarea.asm @@ -26,7 +26,23 @@ defGDTDescPrototype C_SEG_PROT32_R2 defGDTDescPrototype S_SEG_PROT32_R2 defGDTDescPrototype C_SEG_PROT16CS - + defGDTDescPrototype SU_SEG_PROT32 + defGDTDescPrototype C_SEG_PROT32low + defGDTDescPrototype CC_SEG_PROT32low + defGDTDescPrototype CU_SEG_PROT32low + defGDTDescPrototype C_SEG_PROT32 + defGDTDescPrototype S_SEG_PROT32 + defGDTDescPrototype C_SEG_PROT32FLAT + defGDTDescPrototype D_SEG_PROT32FLAT + defGDTDescPrototype GDTU_DSEG_PROT + defGDTDescPrototype IDT_SEG_PROT + defGDTDescPrototype IDTU_SEG_PROT + defGDTDescPrototype CC_SEG_PROT32 + defGDTDescPrototype RING0_GATE ; placeholder for a call gate used to switch to ring 0 + defGDTDescPrototype RING0_GATE2 ; placeholder for a second call gate used to switch to ring 0 +;LDT entries + defLDTDescPrototype D_SEG_PROT32 + defLDTDescPrototype DU_SEG_PROT section .system_bios_extensions_area start=0x00000 ;Start of high BIOS ; TSS helper macros @@ -40,6 +56,11 @@ section .system_bios_extensions_area start=0x00000 ; %include "protected_tssh.asm" BITS 32 +%include "protected_rings_tssp.asm" + ; + ; Interrupt handlers (installed during POST 8) + ; +%include "protected_inth.asm" ; ; 386 TSS user mode code ; @@ -322,7 +343,372 @@ TSStest1finished: nextlowbios: mov dword [esp],test386TSSend+0xF0000 ;Return to the F0000 segment in flat protected mode retfd ;Actually return + +%include "tests/dtr_m.asm" +;Real mode code for these tests +BITS 16 +XLATrealmodeError: + push word 0xF000 + push word error + retf +POST8_9_Aentrypoint: +;------------------------------------------------------------------------------- + POST 1E +;------------------------------------------------------------------------------- +; +; Load/save GDTR/IDTR in real mode +; + testDTR 0,lidt,sidt + testDTR 0,lgdt,sgdt + testDTR 1,lidt,sidt + testDTR 1,lgdt,sgdt + + +;------------------------------------------------------------------------------- + POST 1F +;------------------------------------------------------------------------------- +; +; XLAT in real mode +; + ;Test XLAT + mov cx,0x100 ;Length of the lookup table. + mov al,0x0 ;First entry + mov bx,0 ;Initialize data pointer. +fillXLATLookupTable: + neg al ;Store negated entries. + mov [bx],al ;Create the lookup table entry. + inc bx ;Next entry. + neg al ;Restore negated entry. + inc al + loop fillXLATLookupTable + mov cx,0x100 ;Length of the lookup table. + mov bx,0 ;Initialize data pointer to the start of the table. +checkXLAT: + mov ax,0x100 ;Calculate the entry we're going to check + sub ax,cx ;Create the entry number to validate. + mov dl,al ;Copy the entry number, as we're getting overwritten by the instruction. + neg dl ;Convert the input to the expected result. + xlat ;Execute the tested instruction + cmp al,dl ;Is the result as expected? + jnz XLATrealmodeError + loop checkXLAT ;Check all input values. + +;------------------------------------------------------------------------------- + POST 7 +;------------------------------------------------------------------------------- +; +; Interrupts in real mode +; + + ;Test real mode interrupts + ;First, install the interrupt handler. + push ds + mov ax,0 + mov ds,ax ;Point to the Interrupt Vector Table. + ;Set all real mode interupt handlers to an error vector. + mov bx,0 ;Reset pointer + mov cx,0x100 ;All interrupt vectors. + jmp skipinterruptsclearing +nextRealModeInterruptSetError: + ;Set the interrupt to error + mov word [bx+2],0xF000 + mov word [bx],error + add bx,4 ;Next pointer + loop nextRealModeInterruptSetError + skipinterruptsclearing: + ;Set just our test interrupt vector. + mov ax,cs + mov [0x82],ax + mov ax,realmodeInterrupt + mov [0x80],ax + pop ds + ;Now, call the real mode interrupt + pushfd ;Save interrupts + pushfd ;Modify flags + or word [esp],PS_IF ;Enable interrupts and flags bits to test + popfd + mov eax,esp ;Stack pointer for the interrupt to check. + int 0x20 + realmodeInterruptRET: + popfd ;Restore interrupts + ;Return to the main lower BIOS. + push word 0xF000 + push word POSTAreturnpoint + retf + +test386POST20start: +BITS 32 +;------------------------------------------------------------------------------- + POST 20 +;------------------------------------------------------------------------------- +; +; Test user mode (ring 3) switching +; + call far C_SEG_PROT32:clearTSSfar + mov ax, D_SEG_PROT32 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + pushfd + + ; Test I/O port access permissions + or word [esp], 0x3000 ; Make I/O ports available on user mode + popfd ; Make I/O ports availble now + call switchToRing3low ; Switch to user mode (ring 3) + in al, 0x64 ; Read some I/O port freely + call switchToRing0low ; Switch back to kernel mode (ring 0) + ; CS must be C_SEG_PROT32low|0 (CPL=0) + mov ax, cs + cmp ax, C_SEG_PROT32low + jne errorTSS + pushfd + and word [esp], 0xFFF ; Block ports on user mode again + popfd + call switchToRing3low ; back to user mode (ring 3) again + ; CS must be CU_SEG_PROT32low|3 (CPL=3) + mov ax, cs + cmp ax, CU_SEG_PROT32low|3 + jne errorTSS + ; data segments must be NULL + mov ax, ds + cmp ax, 0 + jne errorTSS + mov ax, es + cmp ax, 0 + jne errorTSS + mov ax, fs + cmp ax, 0 + jne errorTSS + mov ax, gs + cmp ax, 0 + jne errorTSS + + ; test privileged instructions in user mode (ring 3) + protModeFaultTestLow EX_GP, 0, cli + protModeFaultTestLow EX_GP, 0, hlt + protModeFaultTestLow EX_GP, 0, in al,0x64 + + ; test invalid interrupt call + protModeFaultTestLow EX_GP, 0x118|0x2, int 0x23 + + ; We should have the intial user mode stack setup right now for the below checks to validate. + call switchToRing0low ; back to kernel mode (ring 0) again + call switchToRing3low ; back to user mode (ring 3) again. Thus setting the Interrupt flag for our test. + + ; Interrupt from user mode to kernel mode using a 32-bit interrupt gate + %if ROM128 + int 0x20 +kernelModeInterruptReturn: + %endif + ; Interrupt from user mode to kernel using a 16-bit interrupt gate + int 0x27 +kernelModeInterruptReturn286: + ; Interrupt from user mode to kernel conforming + int 0x21 +kernelConformingInterruptReturn: + ; Interrupt from user mode to user mode + int 0x22 +userInterruptReturn: + call CU_SEG_PROT32low|3:userFarFunc ; User-mode far call test + jmp CU_SEG_PROT32low|3:userJmpFunc ; User-mode far jump test + +userFarFunc: + retf ; Simply return to the caller on the same privilege level + +userRetfErrorFunction: + ; From user mode to kernel mode error address, which isn't allowed. + push C_SEG_PROT32low + push errorTSS +userRetfErrorLocation: + retf +userRetfImmErrorFunction: + ; From user mode to kernel mode error address, with immediate, which isn't allowed. + push C_SEG_PROT32low + push errorTSS +userRetfImmErrorLocation: + retf 1 +userV86ExitFuncLocation: + bits 16 + push userV86ExitFuncRet ; Where to continue + jmp switchToRing0V86 + bits 32 +userV86IretInterruptRet: + bits 16 + push dword userV86IretExitFuncLocationRet + jmp switchToRing0V86 + bits 32 +userV86IretRealModeFunc: + bits 16 + ; Perform some pushf(d)/popf(d) with IOPL 3 test + pushf ; This doubles as a pushf IOPL 3 test + popf ; This doubles as a popf IOPL 3 test + pushfd ; This doubles as a pushfd IOPL3 test + popfd ; This doubles as a popfd IOPL3 test + pushf ; Real pushf we need for parameters now. + push cs + push word userV86IretInterruptRet + ; Also, STI/CLI are allowed in this case, perform the test here. + sti + cli + iret + bits 32 +userV86IretErrorFuncLocation: + bits 16 + push cs + push errorTSS +userV86IretErrorFuncLocationInstruction: + iret + jmp errorTSS +userV86IOInstruction0: + in al, 0x64 + jmp errorTSS + bits 32 + +userJmpFunc: + call switchToRing0low ; switch back to kernel mode (ring 0) + ; CS must be C_SEG_PROT32low|0 (CPL=0) + mov ax, cs + cmp ax, C_SEG_PROT32low + jne errorTSS + + ; Test call gates now + jmp testCallGateWithParameters ; Test call gate with parameters +ring0_2TestEndLocation: + + ; Perform some user mode exception tests + testUserFaultLow EX_GP, C_SEG_PROT32low, jmp C_SEG_PROT32low|3:0 ; Basic jump from user mode to kernel mode + testUserFaultLow EX_GP, C_SEG_PROT32low, call C_SEG_PROT32low|3:0 ; Basic call from user mode to kernel mode + testUserFaultExLow EX_GP, C_SEG_PROT32low, userRetfErrorLocation, jmp userRetfErrorFunction ; Far return from user mode to kernel mode + testUserFaultExLow EX_GP, C_SEG_PROT32low, userRetfImmErrorLocation, jmp userRetfImmErrorFunction ; Far return from user mode to kernel mode + + ; Test kernel mode only interrupt + mov eax, esp ; Save the stack pointer for us to check, as the interrupt doesn't have a comparison. + int 0x23 +kernelModeOnlyInterruptReturn: + + ; Interrupt from kernel mode to user mode is forbidden, so test for that. + protModeFaultTestLow EX_GP, CU_SEG_PROT32low, int 0x22 + + ; Interrupt from kernel mode to kernel mode (conforming) + mov eax, esp ; Save the stack pointer for us to check, as the interrupt doesn't have a comparison. + int 0x24 +kernelOnlyConformingInterruptReturn: + + ; Test user to kernel stack switch using different address spaces + ; Interrupt from user mode to kernel mode (flat address space) + call switchToRing3FLATkernelLow ;Switch to ring 3 with flat kernel stack prepared + int 0x26 +kernelModeInterruptKernelStackReturn: + push kernelModeInterruptKernelStackReturnPoint + call switchToRing0low + kernelModeInterruptKernelStackReturnPoint: + ; Back in normal 32-bit protected mode with 16-bit segment limits again + +;------------------------------------------------------------------------------- + POST 21 +;------------------------------------------------------------------------------- +; +; Test Virtual-8086 mode +; + + ; Check invalid virtual 8086 mode interrupts + ; interrupt without IOPL 3 faults with #GP(0) + testUserV86_0_Fault EX_GP, 0, int 0x22 + ; CLI/STI without IOPL 3 faults with #GP(0) + testUserV86_0_Fault EX_GP, 0, cli + testUserV86_0_Fault EX_GP, 0, sti + ; pushf(d) isn't allowed with IOPL 0 + testUserV86_0_Fault EX_GP, 0, pushf + testUserV86_0_Fault EX_GP, 0, pushfd + ; popf(d) isn't allowed with IOPL 0 + testUserV86_0_Fault EX_GP, 0, popf + testUserV86_0_Fault EX_GP, 0, popfd + ; port i/o isn't allowed with IOPL 0 and TSS I/O map set or out of range + testUserV86_0_FaultEx EX_GP, 0, userV86IOInstruction0, call userV86IOInstruction0 + + ; Manipulate TSS to allow port I/O for a bit. + push es + push ebp + les ebp, [cs:ptrTSSprot_R2] ; Load our TSS + mov word [es:ebp+0x66], 0 ; Make it available temporarily + mov word [es:ebp+0xC], 0 ; Make the port available + pop ebp + pop es + call switchToRing3V86_3 ; Switch to v86 mode to test + bits 16 + in al, 0x64 ; Some valid I/O port to use that is harmless. + push dword V86IOSucceedFinish ; Return to kernel mode + jmp switchToRing0V86 + bits 32 +V86IOSucceedFinish: + push es + push ebp + les ebp, [cs:ptrTSSprot_R2] ; Load our TSS + mov word [es:ebp+0x66], 0x68 ; Make the port unavailable again + pop ebp + pop es + + ; If we reach here, the return to kernel mode was successful. + + ; iret without IOPL 3 faults with #GP(0) + testUserV86_0_FaultEx EX_GP, 0, userV86IretErrorFuncLocationInstruction, call userV86IretErrorFuncLocation + ; interrupt with IOPL 3 to non-V86 privilege level 0 faults with #GP(usermodesegment) + testUserV86_3_Fault EX_GP, CU_SEG_PROT32low, int 0x22 + ; interrupt with IOPL 3 to non-V86 monitor privilege level 0 faults with #GP(kernel conforming segment) + testUserV86_3_Fault EX_GP, CC_SEG_PROT32low, int 0x21 + ; HLT is privileged and raises #GP(0) no matter what IOPL is used + testUserV86_3_Fault EX_GP, 0, hlt + testUserV86_0_Fault EX_GP, 0, hlt + + ; iret with IOPL 3 proceeds as in real mode + + ; Validate simply exiting Virtual 8086 mode, using the interrupt + call switchToRing3V86_3 + bits 16 + int 0x2D ;Validate we're actually in V86 mode. + jmp userV86IretRealModeFunc + jmp errorTSS + bits 32 +userV86IretExitFuncLocationRet: + + ; Validate simply exiting Virtual 8086 mode, using the interrupt + call switchToRing3V86_3 + bits 16 + jmp userV86ExitFuncLocation + jmp errorTSS + bits 32 +errorInTSS32Load: + mov ax,DU_SEG_PROT32FLAT|3 ;SS safe value + mov ss,ax + mov esp,ESP_R3_PROTFLAT ;Restore our stack pointer + jmp errorTSS ;Error out! + +userV86ExitFuncRet: + ;Now we're going to test 16-bit interrupts in Virtual 8086 mode. + %if ROM128 + call switchToRing3V86_3 + BITS 16 + int 0x2E ;Verify 16-bit interrupts in Virtual 8086 mode + userV86_16bitinterruptRET: + push dword userV86ExitFuncRet_16bitinterrupts ; Where to continue + jmp switchToRing0V86 +userV86ExitFuncRet_16bitinterrupts: + %endif + BITS 32 + pushfd + pop eax + and eax,0xCDFF ;Clear IOPL and interrupt flag again, as it cannot be changed in user mode + push eax + popfd + +;Return to the high BIOS to continue testing. + push C_SEG_PROT32 + push test386POST21end + retf +BITS 16 + ;End of high BIOS ;Pad to 64KB times 0x10000-($-$$) nop diff --git a/src/test386.asm b/src/test386.asm index cfbb0ca..45d1352 100644 --- a/src/test386.asm +++ b/src/test386.asm @@ -153,6 +153,16 @@ cpuTest: testLoopZ testLoopNZ +;------------------------------------------------------------------------------- + POST 1D +;------------------------------------------------------------------------------- +; +; Flag instructions +; +%include "tests/flags_m.asm" + testCarryFlagInstructions + testDirectionFlagInstructions + testInterruptFlagInstructions ;------------------------------------------------------------------------------- POST 2 @@ -242,6 +252,15 @@ cpuTest: advTestSegReal +%if ROM128 + ; Jump to the entry point of the test. + push word 0xE000 + push word POST8_9_Aentrypoint + retf + POSTAreturnpoint: +%endif + + ; ============================================================================== ; Protected mode tests @@ -308,22 +327,25 @@ initGDT: defGDTDescImplementation C_SEG_PROT32_R2, 0x000e0000,0x0000ffff,ACC_TYPE_CODE_R|ACC_PRESENT|ACC_DPL_2,EXT_32BIT defGDTDescImplementation S_SEG_PROT32_R2, 0x00010000,0x0000ffff,ACC_DPL_2|ACC_TYPE_DATA_W|ACC_PRESENT,EXT_32BIT defGDTDescImplementation C_SEG_PROT16CS, 0x000e0000,0x0000ffff,ACC_TYPE_CODE_R|ACC_PRESENT,EXT_32BIT + defGDTDescImplementation SU_SEG_PROT32, 0x00010000,0x0007ffff,ACC_TYPE_DATA_W|ACC_PRESENT|ACC_DPL_3,EXT_32BIT + defGDTDescImplementation C_SEG_PROT32low, 0x000e0000,0x0000ffff,ACC_TYPE_CODE_R|ACC_PRESENT,EXT_32BIT + defGDTDescImplementation CC_SEG_PROT32low, 0x000e0000,0x0000ffff,ACC_TYPE_CODE_R|ACC_TYPE_CONFORMING|ACC_PRESENT|EXT_32BIT + defGDTDescImplementation CU_SEG_PROT32low, 0x000e0000,0x0000ffff,ACC_TYPE_CODE_R|ACC_PRESENT|ACC_DPL_3,EXT_32BIT + defGDTDescImplementation C_SEG_PROT32, 0x000f0000,0x0000ffff,ACC_TYPE_CODE_R|ACC_PRESENT,EXT_32BIT + defGDTDescImplementation S_SEG_PROT32, 0x00010000,0x0007ffff,ACC_TYPE_DATA_W|ACC_PRESENT,EXT_32BIT + defGDTDescImplementation C_SEG_PROT32FLAT, 0x00000000,0x000fffff,ACC_TYPE_CODE_R|ACC_PRESENT,EXT_32BIT|EXT_PAGE + defGDTDescImplementation D_SEG_PROT32FLAT, 0x00000000,0x000fffff,ACC_TYPE_DATA_W|ACC_PRESENT,EXT_32BIT|EXT_PAGE + defGDTDescImplementation GDTU_DSEG_PROT,0x00000600,0x0000031f,ACC_TYPE_DATA_W|ACC_PRESENT|ACC_DPL_3 + defGDTDescImplementation IDT_SEG_PROT, 0x00000400,0x0000016F,ACC_TYPE_DATA_W|ACC_PRESENT + defGDTDescImplementation IDTU_SEG_PROT, 0x00000400,0x0000016F,ACC_TYPE_DATA_W|ACC_PRESENT|ACC_DPL_3 + defGDTDescImplementation CC_SEG_PROT32, 0x000f0000,0x0000ffff,ACC_TYPE_CODE_R|ACC_TYPE_CONFORMING|ACC_PRESENT|EXT_32BIT defGDTDesc C_SEG_PROT16, 0x000f0000,0x0000ffff,ACC_TYPE_CODE_R|ACC_PRESENT - defGDTDesc C_SEG_PROT32, 0x000f0000,0x0000ffff,ACC_TYPE_CODE_R|ACC_PRESENT,EXT_32BIT - defGDTDesc C_SEG_PROT32FLAT, 0x00000000,0x000fffff,ACC_TYPE_CODE_R|ACC_PRESENT,EXT_32BIT|EXT_PAGE - defGDTDesc CC_SEG_PROT32, 0x000f0000,0x0000ffff,ACC_TYPE_CODE_R|ACC_TYPE_CONFORMING|ACC_PRESENT|EXT_32BIT - defGDTDesc IDT_SEG_PROT, 0x00000400,0x0000016F,ACC_TYPE_DATA_W|ACC_PRESENT - defGDTDesc IDTU_SEG_PROT, 0x00000400,0x0000016F,ACC_TYPE_DATA_W|ACC_PRESENT|ACC_DPL_3 defGDTDesc GDT_DSEG_PROT, 0x00000600,0x0000031f,ACC_TYPE_DATA_W|ACC_PRESENT - defGDTDesc GDTU_DSEG_PROT,0x00000600,0x0000031f,ACC_TYPE_DATA_W|ACC_PRESENT|ACC_DPL_3 defGDTDesc LDT_DSEG_PROT, 0x00000A00,0x000005ff,ACC_TYPE_DATA_W|ACC_PRESENT defGDTDesc PG_SEG_PROT, 0x00001000,0x00003fff,ACC_TYPE_DATA_W|ACC_PRESENT - defGDTDesc S_SEG_PROT32, 0x00010000,0x0007ffff,ACC_TYPE_DATA_W|ACC_PRESENT,EXT_32BIT - defGDTDesc D_SEG_PROT32FLAT, 0x00000000,0x000fffff,ACC_TYPE_DATA_W|ACC_PRESENT,EXT_32BIT|EXT_PAGE - defGDTDesc SU_SEG_PROT32, 0x00010000,0x0007ffff,ACC_TYPE_DATA_W|ACC_PRESENT|ACC_DPL_3,EXT_32BIT defGDTDesc FLAT_SEG_PROT, 0x00000000,0xffffffff,ACC_TYPE_DATA_W|ACC_PRESENT - defGDTDesc RING0_GATE ; placeholder for a call gate used to switch to ring 0 - defGDTDesc RING0_GATE2 ; placeholder for a second call gate used to switch to ring 0 + defGDTDescImplementation RING0_GATE ; placeholder for a call gate used to switch to ring 0 + defGDTDescImplementation RING0_GATE2 ; placeholder for a second call gate used to switch to ring 0 jmp initIDT @@ -422,50 +444,83 @@ initIDT: ; Install some interrupt handlers for user mode tests ; Interrupt 20h: non-conforming kernel interrupt, callable from user mode - mov esi, C_SEG_PROT32 + mov esi, C_SEG_PROT16CS + %if ROM128 mov edi, kernelInterrupt + %else + ;Not enough room to test. Enter an invalid address. + mov edi, 0xFFFFFFFF + %endif mov dx, ACC_DPL_3 mov eax, 0x20 call initIntGateReal ; Interrupt 21h: conforming kernel interrupt, callable from user mode - mov esi, CC_SEG_PROT32 + mov esi, CC_SEG_PROT32low + %if ROM128 mov edi, kernelConformingInterrupt + %else + mov edi, 0xFFFFFFFF ;Unused, placeholder + %endif inc eax call initIntGateReal ; Interrupt 22h: user mode only interrupt - mov esi, CU_SEG_PROT32 + mov esi, CU_SEG_PROT32low + %if ROM128 mov edi, userModeInterrupt + %else + mov edi, 0xFFFFFFFF ;Unused, placeholder + %endif inc eax call initIntGateReal ; Interrupt 23h: kernel mode only interrupt - mov esi, C_SEG_PROT32 + mov esi, C_SEG_PROT32low + %if ROM128 mov edi, kernelOnlyInterrupt + %else + mov edi, 0xFFFFFFFF ;Unused, placeholder + %endif mov dx, ACC_DPL_0 inc eax call initIntGateReal ; Interrupt 24h: kernel mode only conforming interrupt - mov esi, CC_SEG_PROT32 + mov esi, CC_SEG_PROT32low + %if ROM128 mov edi, kernelOnlyConformingInterrupt + %else + mov edi, 0xFFFFFFFF ;Unused, placeholder + %endif mov dx, ACC_DPL_0 inc eax call initIntGateReal ; Interrupt 25h: kernel mode interrupt, callable from user mode. Switches out of V86 mode - mov esi, C_SEG_PROT32 + mov esi, C_SEG_PROT32low + %if ROM128 mov edi, V86ModeExitInterrupt + %else + mov edi, 0xFFFFFFFF ;Unused, placeholder + %endif mov dx, ACC_DPL_3 inc eax call initIntGateReal ; Interrupt 26h: non-conforming kernel interrupt, flat memory, callable from user mode. Restores kernel stack to 32-bit mov esi, C_SEG_PROT32FLAT + %if ROM128 mov edi, kernelInterruptRestoreKernelStack - or edi,0xE00F0000 ;Make sure that the offset is located on a valid 32-bit mapped address by mapping the high 16 bits. + %else + mov edi, 0xFFFFFFFF ;Unused, placeholder + %endif + or edi,0xE00E0000 ;Make sure that the offset is located on a valid 32-bit mapped address by mapping the high 16 bits. mov dx, ACC_DPL_3 inc eax call initIntGateReal ; Interrupt 27h: non-conforming 286 kernel interrupt, callable from user mode. - mov esi, C_SEG_PROT32 + mov esi, C_SEG_PROT32low + %if ROM128 mov edi, kernelInterrupt286 + %else + mov edi, 0xFFFFFFFF ;Unused, placeholder + %endif or edi,0xFFFF0000 ;Make sure that the offset is located on a invalid 32-bit mapped address by mapping the high 16 bits, which are not to be used. mov dx, ACC_DPL_3 inc eax @@ -492,7 +547,7 @@ initIDT: %if ROM128 mov edi, kernelInterrupt_R2_386 %else - mov edi, kernelInterrupt ;Unused, placeholder + mov edi, 0xFFFFFFFF ;Unused, placeholder %endif mov dx, ACC_DPL_3 inc eax @@ -503,7 +558,7 @@ initIDT: %if ROM128 mov edi, kernelInterrupt_R2_286 %else - mov edi, kernelInterrupt ;Unused, placeholder + mov edi, 0xFFFFFFFF ;Unused, placeholder %endif mov dx, ACC_DPL_3 inc eax @@ -514,15 +569,17 @@ initIDT: %if ROM128 mov edi, kernelInterrupt_validateAndClearTS %else - mov edi, kernelInterrupt ;Unused, placeholder + mov edi, 0xFFFFFFFF ;Unused, placeholder %endif mov dx, ACC_DPL_3 inc eax call initIntGateReal ; Interrupt 2D: validate V86 mode - mov esi, C_SEG_PROT32 + mov esi, C_SEG_PROT32low + %if ROM128 mov edi, kernelInterrupt_validateIsV86mode + %endif mov dx, ACC_DPL_3 inc eax call initIntGateReal @@ -532,7 +589,7 @@ initIDT: %if ROM128 mov edi, kernelInterrupt_validateV86mode16bit %else - mov edi, kernelInterrupt ;Unused, placeholder + mov edi, 0xFFFFFFFF ;Unused, placeholder %endif or edi,0xFFFF0000 ;Make sure that the offset is located on a invalid 32-bit mapped address by mapping the high 16 bits, which are not to be used. mov dx, ACC_DPL_3 @@ -632,9 +689,9 @@ toProt32: %include "protected_p.asm" initLDT: + defLDTDescImplementation D_SEG_PROT32, TEST_BASE, 0x000fffff,ACC_TYPE_DATA_W|ACC_PRESENT,EXT_32BIT + defLDTDescImplementation DU_SEG_PROT, TEST_BASE, 0x000fffff,ACC_TYPE_DATA_W|ACC_PRESENT|ACC_DPL_3 defLDTDesc D_SEG_PROT16, TEST_BASE, 0x000fffff,ACC_TYPE_DATA_W|ACC_PRESENT - defLDTDesc D_SEG_PROT32, TEST_BASE, 0x000fffff,ACC_TYPE_DATA_W|ACC_PRESENT,EXT_32BIT - defLDTDesc DU_SEG_PROT, TEST_BASE, 0x000fffff,ACC_TYPE_DATA_W|ACC_PRESENT|ACC_DPL_3 defLDTDesc D1_SEG_PROT, TEST_BASE1,0x000fffff,ACC_TYPE_DATA_W|ACC_PRESENT defLDTDesc D2_SEG_PROT, TEST_BASE2,0x000fffff,ACC_TYPE_DATA_W|ACC_PRESENT defLDTDesc DC_SEG_PROT32, TEST_BASE1,0x000fffff,ACC_TYPE_CODE_R|ACC_PRESENT,EXT_32BIT @@ -751,264 +808,14 @@ protTests: advTestSegProt -;------------------------------------------------------------------------------- - POST 20 -;------------------------------------------------------------------------------- -; -; Test user mode (ring 3) switching -; - call clearTSS - mov ax, D_SEG_PROT32 - mov ds, ax - mov es, ax - mov fs, ax - mov gs, ax - pushfd - - ; Test I/O port access permissions - or word [esp], 0x3000 ; Make I/O ports available on user mode - popfd ; Make I/O ports availble now - call switchToRing3 ; Switch to user mode (ring 3) - in al, 0x64 ; Read some I/O port freely - call switchToRing0 ; Switch back to kernel mode (ring 0) - ; CS must be C_SEG_PROT32|0 (CPL=0) - mov ax, cs - cmp ax, C_SEG_PROT32 - jne error - pushfd - and word [esp], 0xFFF ; Block ports on user mode again - popfd - call switchToRing3 ; back to user mode (ring 3) again - ; CS must be CU_SEG_PROT32|3 (CPL=3) - mov ax, cs - cmp ax, CU_SEG_PROT32|3 - jne error - ; data segments must be NULL - mov ax, ds - cmp ax, 0 - jne error - mov ax, es - cmp ax, 0 - jne error - mov ax, fs - cmp ax, 0 - jne error - mov ax, gs - cmp ax, 0 - jne error - - ; test privileged instructions in user mode (ring 3) - protModeFaultTest EX_GP, 0, cli - protModeFaultTest EX_GP, 0, hlt - protModeFaultTest EX_GP, 0, in al,0x64 - - ; test invalid interrupt call - protModeFaultTest EX_GP, 0x118|0x2, int 0x23 - - ; We should have the intial user mode stack setup right now for the below checks to validate. - - ; Interrupt from user mode to kernel mode using a 32-bit interrupt gate - int 0x20 -kernelModeInterruptReturn: - ; Interrupt from user mode to kernel using a 16-bit interrupt gate - int 0x27 - -kernelModeInterruptReturn286: - ; Interrupt from user mode to kernel conforming - int 0x21 -kernelConformingInterruptReturn: - ; Interrupt from user mode to user mode - int 0x22 -userInterruptReturn: - call CU_SEG_PROT32|3:userFarFunc ; User-mode far call test - jmp CU_SEG_PROT32|3:userJmpFunc ; User-mode far jump test - -userFarFunc: - retf ; Simply return to the caller on the same privilege level - - ; - ; Interrupt handlers (installed during POST 8) - ; -%include "protected_inth.asm" - -userRetfErrorFunction: - ; From user mode to kernel mode error address, which isn't allowed. - push C_SEG_PROT32 - push error -userRetfErrorLocation: - retf -userV86ExitFuncLocation: - bits 16 - push userV86ExitFuncRet ; Where to continue - jmp switchToRing0V86 - bits 32 -userV86IretInterruptRet: - bits 16 - push dword userV86IretExitFuncLocationRet - jmp switchToRing0V86 - bits 32 -userV86IretRealModeFunc: - bits 16 - ; Perform some pushf(d)/popf(d) with IOPL 3 test - pushf ; This doubles as a pushf IOPL 3 test - popf ; This doubles as a popf IOPL 3 test - pushfd ; This doubles as a pushfd IOPL3 test - popfd ; This doubles as a popfd IOPL3 test - pushf ; Real pushf we need for parameters now. - push cs - push word userV86IretInterruptRet - ; Also, STI/CLI are allowed in this case, perform the test here. - sti - cli - iret - bits 32 -userV86IretErrorFuncLocation: - bits 16 - push cs - push error -userV86IretErrorFuncLocationInstruction: - iret - jmp error -userV86IOInstruction0: - in al, 0x64 - jmp error - bits 32 - -userJmpFunc: - call switchToRing0 ; switch back to kernel mode (ring 0) - ; CS must be C_SEG_PROT32|0 (CPL=0) - mov ax, cs - cmp ax, C_SEG_PROT32 - jne error - - ; Test call gates now - jmp testCallGateWithParameters ; Test call gate with parameters -ring0_2TestEndLocation: - - ; Perform some user mode exception tests - testUserFault EX_GP, C_SEG_PROT32, jmp C_SEG_PROT32|3:0 ; Basic jump from user mode to kernel mode - testUserFault EX_GP, C_SEG_PROT32, call C_SEG_PROT32|3:0 ; Basic call from user mode to kernel mode - testUserFaultEx EX_GP, C_SEG_PROT32, userRetfErrorLocation, jmp userRetfErrorFunction ; Far return from user mode to kernel mode - - ; Test kernel mode only interrupt - mov eax, esp ; Save the stack pointer for us to check, as the interrupt doesn't have a comparison. - int 0x23 -kernelModeOnlyInterruptReturn: - - ; Interrupt from kernel mode to user mode is forbidden, so test for that. - protModeFaultTest EX_GP, CU_SEG_PROT32, int 0x22 - - ; Interrupt from kernel mode to kernel mode (conforming) - mov eax, esp ; Save the stack pointer for us to check, as the interrupt doesn't have a comparison. - int 0x24 -kernelOnlyConformingInterruptReturn: - - ; Test user to kernel stack switch using different address spaces - ; Interrupt from user mode to kernel mode (flat address space) - call switchToRing3FLATkernel ;Switch to ring 3 with flat kernel stack prepared - int 0x26 -kernelModeInterruptKernelStackReturn: - push kernelModeInterruptKernelStackReturnPoint - call switchToRing0 - kernelModeInterruptKernelStackReturnPoint: - ; Back in normal 32-bit protected mode with 16-bit segment limits again - -;------------------------------------------------------------------------------- - POST 21 -;------------------------------------------------------------------------------- -; -; Test Virtual-8086 mode -; - - ; Check invalid virtual 8086 mode interrupts - ; interrupt without IOPL 3 faults with #GP(0) - testUserV86_0_Fault EX_GP, 0, int 0x22 - ; CLI/STI without IOPL 3 faults with #GP(0) - testUserV86_0_Fault EX_GP, 0, cli - testUserV86_0_Fault EX_GP, 0, sti - ; pushf(d) isn't allowed with IOPL 0 - testUserV86_0_Fault EX_GP, 0, pushf - testUserV86_0_Fault EX_GP, 0, pushfd - ; popf(d) isn't allowed with IOPL 0 - testUserV86_0_Fault EX_GP, 0, popf - testUserV86_0_Fault EX_GP, 0, popfd - ; port i/o isn't allowed with IOPL 0 and TSS I/O map set or out of range - testUserV86_0_FaultEx EX_GP, 0, userV86IOInstruction0, call userV86IOInstruction0 - - ; Manipulate TSS to allow port I/O for a bit. - push es - push ebp - les ebp, [cs:ptrTSSprot] ; Load our TSS - mov word [es:ebp+0x66], 0 ; Make it available temporarily - mov word [es:ebp+0xC], 0 ; Make the port available - pop ebp - pop es - call switchToRing3V86_3 ; Switch to v86 mode to test - bits 16 - in al, 0x64 ; Some valid I/O port to use that is harmless. - push dword V86IOSucceedFinish ; Return to kernel mode - jmp switchToRing0V86 - bits 32 -V86IOSucceedFinish: - push es - push ebp - les ebp, [cs:ptrTSSprot] ; Load our TSS - mov word [es:ebp+0x66], 0x68 ; Make the port unavailable again - pop ebp - pop es - - ; If we reach here, the return to kernel mode was successful. - - ; iret without IOPL 3 faults with #GP(0) - testUserV86_0_FaultEx EX_GP, 0, userV86IretErrorFuncLocationInstruction, call userV86IretErrorFuncLocation - ; interrupt with IOPL 3 to non-V86 privilege level 0 faults with #GP(usermodesegment) - testUserV86_3_Fault EX_GP, CU_SEG_PROT32, int 0x22 - ; interrupt with IOPL 3 to non-V86 monitor privilege level 0 faults with #GP(kernel conforming segment) - testUserV86_3_Fault EX_GP, CC_SEG_PROT32, int 0x21 - ; HLT is privileged and raises #GP(0) no matter what IOPL is used - testUserV86_3_Fault EX_GP, 0, hlt - testUserV86_0_Fault EX_GP, 0, hlt - - ; iret with IOPL 3 proceeds as in real mode - - ; Validate simply exiting Virtual 8086 mode, using the interrupt - call switchToRing3V86_3 - bits 16 - int 0x2D ;Validate we're actually in V86 mode. - jmp userV86IretRealModeFunc - jmp error - bits 32 -userV86IretExitFuncLocationRet: - - ; Validate simply exiting Virtual 8086 mode, using the interrupt - call switchToRing3V86_3 - bits 16 - jmp userV86ExitFuncLocation - jmp error - bits 32 -errorInTSS32Load: - mov ax,DU_SEG_PROT32FLAT|3 ;SS safe value - mov ss,ax - mov esp,ESP_R3_PROTFLAT ;Restore our stack pointer - jmp error ;Error out! - -userV86ExitFuncRet: - ;Now we're going to test 16-bit interrupts in Virtual 8086 mode. + ;Run POST 20 from the lower ROM, if enabled. %if ROM128 - call switchToRing3V86_3 - BITS 16 - int 0x2E ;Verify 16-bit interrupts in Virtual 8086 mode - userV86_16bitinterruptRET: - push dword userV86ExitFuncRet_16bitinterrupts ; Where to continue - jmp switchToRing0V86 -userV86ExitFuncRet_16bitinterrupts: + POST 20 + push C_SEG_PROT32low + push test386POST20start + retf ;Jump to the entry point of the test. + test386POST21end: %endif - BITS 32 - pushfd - pop eax - and eax,0xCDFF ;Clear IOPL and interrupt flag again, as it cannot be changed in user mode - push eax - popfd ;------------------------------------------------------------------------------- POST 22 diff --git a/src/tests/dtr_m.asm b/src/tests/dtr_m.asm new file mode 100644 index 0000000..c1a613e --- /dev/null +++ b/src/tests/dtr_m.asm @@ -0,0 +1,86 @@ +; +; Tests GDT/IDT loading and saving. +; Uses: EAX, EBX, ECX, EDX, Flags +; + +; testDTRpattern +; Parameters: +; %1: 0 for 16-bit load, 1 for 32-bit load +; %2: load instruction +; %3: save instruction +; %4: value to load (base) +; %5: value to load (limit) +; %6: error jump +%macro testDTRpattern 6 + ;Load input + mov dword [0],%5 + mov dword [2],%4 + ;Initialize expected output storage + mov dword [8],0xDEADDEAD + mov dword [0xC],0xDEADDEAD + mov ebx,dword [2] + %if %1==0 + ;16-bits load clears the top byte + and ebx,0xFFFFFF + %endif + mov ecx,[4] + %if %1==1 + ;Load it + o32 %2 [0] + %else + ;Load it + o16 %2 [0] + %endif + ;Save the result (full 32-bit) + o32 %3 [8] + ;Load base calculated + mov eax,dword [0xA] + ; Compare 32-bits + cmp eax,ebx + jne %6 + ;Load the limit that was loaded/stored. + mov ax,word [0x8] + ;Only 16 bits stored. + cmp ax,%5 + jne %6 + mov dword [8],0xDEADDEAD + mov dword [0xC],0xDEADDEAD + + ;Save the result (full 16-bit version). Undocumented: it stores the upper 8 bits too, just like with the 32-bits version. + o16 %3 [8] + ;Load base calculated + mov eax,dword [0xA] + ; Compare 32-bits + cmp eax,ebx + jne %6 + ;Load the limit that was loaded/stored. + mov ax,word [0x8] + ;Only 16 bits stored. + cmp ax,%5 + jne %6 +%endmacro + +; testDTR +; Parameters: +; %1: 0 for 16-bit load, 1 for 32-bit load +; %2: load instruction +; %3: save instruction + +%macro testDTR 3 + jmp %%skiperror +%%error: + push word 0xF000 + push word error + retf +%%skiperror: + testDTRpattern %1,%2,%3,0,0,%%error + testDTRpattern %1,%2,%3,0x12345678,0xAABB,%%error + testDTRpattern %1,%2,%3,0xCCDDEEFF,0x1122,%%error + testDTRpattern %1,%2,%3,0xFFFFFFFF,0xEEEE,%%error + ;Cleanup: Restore real mode tables. + mov word [0],0x3FF + mov dword [2],0 + o16 lidt [0] + mov word [0],0xFFFF + o16 lgdt [0] +%endmacro diff --git a/src/tests/flags_m.asm b/src/tests/flags_m.asm new file mode 100644 index 0000000..635b7ac --- /dev/null +++ b/src/tests/flags_m.asm @@ -0,0 +1,86 @@ +; +; Tests the carry flag instructions +; Needs the stack +; +%macro testCarryFlagInstructions 0 + pushf + ;Initialize flags to cleared + push word 0 + popf + ;Carry still set from pop? + jc error + cmc ;Set flag by complementing + ;Carry not complemented? + jnc error + cmc ;Clear flag by complementing + ;Carry not complemented? + jc error + stc + ;Carry not set? + jnc error + clc + ;Carry not cleared? + jc error + popf +%endmacro + +; +; Tests the direction flag instructions +; Needs the stack +; +%macro testDirectionFlagInstructions 0 + pushf + ;Initialize flags to cleared + push word 0 + popf + pushf ;Check the flags + pop ax + pushf + cmp ax,2 ;Not properly cleared? + jnz error + popf ;Restore our testing flags + std ;Set the direction flag + pushf + pushf + pop ax + cmp ax,2|PS_DF ;Direction flag properly set? + jne error + popf ;Restore flags + cld ;Clear the direction flag + pushf + pop ax + cmp ax,2 ;Direction flag properly cleared? + jne error + popf +%endmacro + +; +; Tests the interrupt flag instructions +; Needs the stack +; +%macro testInterruptFlagInstructions 0 + pushf + ;Initialize flags to cleared + push word 0 + popf + pushf ;Check the flags + pop ax + pushf + cmp ax,2 ;Not properly cleared? + jnz error + popf ;Restore our testing flags + sti ;Set the direction flag + pushf + pushf + pop ax + cmp ax,2|PS_IF ;Interrupt flag properly set? + jne error + popf ;Restore flags + cli ;Clear the direction flag + pushf + pop ax + cmp ax,2 ;Interrupt flag properly cleared? + jne error + popf +%endmacro + diff --git a/src/tss_p.asm b/src/tss_p.asm index db62da0..bc9e128 100644 --- a/src/tss_p.asm +++ b/src/tss_p.asm @@ -72,6 +72,10 @@ clearTSS: pop ebp ret +;Far call version of clearTSS +clearTSSfar: + call clearTSS + retf ; Prepare 32-bit TSS for task switching diff --git a/src/x86_e.asm b/src/x86_e.asm index 7f693ac..0a79daa 100644 --- a/src/x86_e.asm +++ b/src/x86_e.asm @@ -8,6 +8,9 @@ PS_IF equ 0x0200 PS_DF equ 0x0400 PS_OF equ 0x0800 PS_NT equ 0x4000 +PS_RF equ 0x10000 +PS_VM equ 0x20000 + PS_ARITH equ (PS_CF | PS_PF | PS_AF | PS_ZF | PS_SF | PS_OF) PS_LOGIC equ (PS_CF | PS_PF | PS_ZF | PS_SF | PS_OF) PS_MULTIPLY equ (PS_CF | PS_OF) ; only CF and OF are "defined" following MUL or IMUL