Howto: Implementation of new system service calls (III)

Windows Research Kernel @ HPI

Basic 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_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

Listing 1: /base/ntos/ke/i386/systable.asm

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:

        HANDLE ThreadHandle,
        THREADINFOCLASS ThreadInformationClass,
        PVOID ThreadInformation,
        ULONG ThreadInformationLength)
        __asm {
                mov eax, 0x00EE

                mov edx, 0x7FFE0300
                call dword ptr [edx]

Listing 2: Syscall wrapper function for NtSetInformationThread

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.


Comments are closed.