Hi,
Jeko wrote:I know that INT 3 is the Breakpoint Exception...
But:
Brendan wrote:The "INT3" instruction is only one byte (which makes it the smallest possible option) but it's probably better to use it for debugging purposes.
INT 3 is the breakpoint exception, but I can use it how I want.
Yes.
Jnc100 is right - INT3 is intended for debuggers to use, so that any instruction can be easily replaced with a breakpoint.
However, if you don't care if debuggers are able to do that (or if you don't want debuggers to be able to do that) then using INT3 can make sense. For example, for my OS's boot code I normally do use INT3 for a "boot API". This is because I do my debugging with Bochs and don't want other people to do their own debugging with INT3, because the code is only run once during boot (size matters more) and because I start using this API before I've done CPU detection (working out if SYSCALL/SYSENTER is supported is more annoying than it's worth in this case).
Jeko wrote:However when an invalid opcode exception occurs how can I check that it's a SYSCALL/SYSENTER or a SYSRET/SYSEXIT?
That's easy enough. Let's start from the start...
Regardless of which method/s you use, it's likely you'll have many kernel API functions, and the caller will put the kernel function number into a register before calling the kernel API. Basically the caller does something like:
Code: Select all
mov eax, functionNumber
<int3, int n, call far, syscall, or sysenter>
And inside the kernel you've got a table of function pointers, and probably end up doing something like:
Code: Select all
cmp eax,highestSupportedFunctionNumber
ja .badFunctionNumberError
call [kernelAPItable + eax * 4]
Now, if they use SYSCALL or SYSENTER and the CPU doesn't support it then you'll get an invalid opcode exception. The CPU will put the return EIP on the stack, so your invalid opcode exception handler can get the return EIP from the stack and use it to see which opcodes were used to cause the invalid opcode exception. The opcode for SYSCALL is "0x0F 0x05", and the opcode for SYSENTER is "0x0F 0x34", so you'd do something like:
Code: Select all
if( *returnEIP == 0x0F) {
if( *(returnEIP + 1) == 0x05) {
/* Emulate SYSCALL */
} else if( *(returnEIP + 1) == 0x34) {
/* Emulate SYSENTER */
}
}
Then, to emulate SYSCALL you'd cheat. You're already in CPL=0 because of the exception and the function number they wanted to use is/was in EAX, so you can just do:
Code: Select all
cmp eax,highestSupportedFunctionNumber
ja .badFunctionNumberError
call [kernelAPItable + eax * 4]
iretd
.badFunctionNumberError:
mov eax,ERROR_badFunctionNumber
iretd
Emulating SYSENTER is a little harder, because the return EIP needs to come from EDX and the return ESP needs to come from ECX. This still isn't too hard though - you just copy these registers into the exception handler's stack:
Code: Select all
mov [esp], edx ;Set new return EIP
mov [esp+12], ecx ;Set new return ESP
cmp eax,highestSupportedFunctionNumber
ja .badFunctionNumberError
call [kernelAPItable + eax * 4]
iretd
.badFunctionNumberError:
mov eax,ERROR_badFunctionNumber
iretd
So, as a summary, the entire thing (for software interrupts, call gates, SYSCALL, SYSENTER, emulated SYSCALL and emulated SYSENTER) would end up something like:
Code: Select all
kernelAPI_softwareInterruptHandler:
cmp eax,highestSupportedFunctionNumber
ja .badFunctionNumberError
call [kernelAPItable + eax * 4]
iretd
.badFunctionNumberError:
mov eax,ERROR_badFunctionNumber
iretd
kernelAPI_callGateHandler:
cmp eax,highestSupportedFunctionNumber
ja .badFunctionNumberError
call [kernelAPItable + eax * 4]
retf
.badFunctionNumberError:
mov eax,ERROR_badFunctionNumber
retf
kernelAPI_syscallHandler:
sti
push edx
mov edx,esp
mov esp,<addressOfThisThreadsKernelStack>
push edx
cmp eax,highestSupportedFunctionNumber
ja .badFunctionNumberError
call [kernelAPItable + eax * 4]
pop esp
pop edx
sysret
.badFunctionNumberError:
mov eax,ERROR_badFunctionNumber
pop esp
pop edx
sysret
kernelAPI_sysenterHandler:
sti
mov esp,<addressOfThisThreadsKernelStack>
cmp eax,highestSupportedFunctionNumber
ja .badFunctionNumberError
call [kernelAPItable + eax * 4]
sysexit
.badFunctionNumberError:
mov eax,ERROR_badFunctionNumber
sysexit
invalidOpcodeExceptionHandler:
push edx
mov edx,[esp+4] ;edx = return EIP (address of invalid opcode)
cmp byte [edx],0x0F ;Could it have been SYSCALL or SYSENTER?
je .unknownInstruction ; no
cmp byte [edx+1],0x0F ;Was it SYSCALL?
je .emulateSYSCALL ; yes
cmp byte [edx+1],0x34 ;Was it SYSENTER?
je .emulateSYSENTER ; yes
.unknownInstruction:
pop edx
/* Do "Blue screen of death" or something here! */
jmp $
.emulateSYSCALL:
pop edx
cmp eax,highestSupportedFunctionNumber
ja .badFunctionNumberError
call [kernelAPItable + eax * 4]
iretd
.emulateSYSENTER:
pop edx
mov [esp], edx ;Set new return EIP
mov [esp+12], ecx ;Set new return ESP
cmp eax,highestSupportedFunctionNumber
ja .badFunctionNumberError
call [kernelAPItable + eax * 4]
iretd
.badFunctionNumberError:
mov eax,ERROR_badFunctionNumber
iretd
Note1: None of this is tested (it's an *example* not production code).
Note2: There's a lot of setup stuff I skipped, like creating the IDT entry for the software interrupt, creating the GDT entry for the call gate, and setting MSRs for SYSENTER (if supported) and SYSCALL (if supported).
After you've got that working, the only thing you need to do is add your kernel API functions to the "kernelAPItable" table. The caller can use any method they like, and the kernel doesn't care which method they use.
Cheers,
Brendan