Howto: Implementation of new system service calls (III)
Windows Research Kernel @ HPIBasic information about the implementation of new system service calls in the Windows Research Kernel can be found in the first and second part of this small series.
In this post, some minor aspects which were forgotten in the first articles are described:
- Parameter handling - how does the kernel know about the amount of parameters of a system service routine?
- CPU independent optimized syscall invocation
- how to take advantage of special CPU instructions, like
sysenter
?
(1) Parameter handling
A new system service call must be inserted into to the syscall
table in the file systable.asm
. After the system
service call table definition, the ARGTBL
starts. This
table contains the amount of bytes the kernel must copy from the
user mode stack to the kernel mode stack if a system service call is
invoked. Therefore, for a new system service call, a new entry into
the ARGTBL
must be inserted.
ARGTBL_BEGIN ARGTBL_ENTRY 24,32,44,44,64,44,64,68 .. ARGTBL_ENTRY 24,8,8,8,0,16,16,4 ARGTBL_ENTRY 4,8,8,20,16,8,8,16 ARGTBL_ENTRY 20,12,4,4,36,36,24,20 ARGTBL_ENTRY 0,16,12,16,16,0,0,20 ARGTBL_END
If you compare the values in the ARGTBL
with the
last value of each TABLE_ENTRY
, it is the number of
syscall parameters multiplied with four. That is exactly the size of
the system service call parameters which are passed from the user to
the kernel mode.
(2) CPU independent optimized syscall invocation
In the last two articles, system service calls were called by
using the interrupt 0×2Eh. As already mentioned, current CPUs
provide an optimized processor instruction to trigger a transfer
from user mode to kernel mode: sysenter
for Intel CPUs
and syscall
for AMD CPUs. Each of these posibilities
for a system service call requires different kinds of parameter
passing.
Fortunately, Windows provides a generic way to invoke system service calls without needing to consider these details: In the KUSER_SHARED_DATA structure (see also this post) which is mapped into the user space, an entry points to a small piece of code which calls the kernel and executes the system service call. This piece of code is initialized during system startup by considering the actual available CPU and chosing the best option for system service call invocation.
With this information, we can implement generic system service call wrapper functions in the following way:
__declspec(noinline) __declspec(naked) NTSTATUS CallNtSetInformationThread( HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength) { __asm { mov eax, 0x00EE mov edx, 0x7FFE0300 call dword ptr [edx] ret } }
The system service call number has to be stored in
eax
. Afterwards, the address of the system call
instruction from the KUSER_SHARED_DATA
structure is
loaded into the edx
register and then called.
KUSER_SHARED_DATA
is always mapped to the user mode
address 0×7FFE0000 the ULONG SystemCall
entry has the
offset 0×300. The __declspec(naked)
declaration is
required to prevent the compiler from generating a function prolog
and epilog for the syscall wrapper function and allows to pass the
stack state to the syscall invokation code.
With this approach for system service calls, user mode libraries (such as NTDLL.DLL) can take advantage of CPU optimizations for system service calls without knowing the actual available CPU.