Do push the preserved registers in the interrupt handler?
Posted: Thu Jan 03, 2019 3:27 pm
This is the summarized pseudocode related to the assembler code I use in my x86_64 SMP microkernel to handle interrupts:
It is 'optimized' in the sense that it only pushes scratch registers to the stack, as the 'preserved' ones get preserved. Then it calls C/C++ code, and at the end saves/loads them if we have to switch thread (determined in C code). It preserves the control flow as if it were a function call.
But then I learned, in this thread: viewtopic.php?f=15&t=33393 that a proper way of handling, in kernel mode, the reads and writes to and from userspace, is to do a basic check on the pointer and then to perform the reads or writes and let the processor issue exceptions (page faults) if the memory is not readable/writable. As opossed to doing an exhaustive checking, which would mean to parse the page tables and to do some expensive locking.
I realized that to support exceptions (page faults) in kernel mode while accessing user memory, there is need to save or push to the stack also the preserved registers, at least in syscalls, because they will be needed in case the SIGSEGV signal gets catched (the thread should get the same state as before). This seems so because, after the syscall places its data on the kernel stack, and the page fault exception (nested) places its data too, we cannot return from the exception (it would repeat itself) to restore the preserved registers. Then if we do not have saved the preserved registers, they could have been modified (and saved in the stack, but, at unknown places), so there would be no easy way of getting them. If the process was to be terminated, that is not an issue, but if the process catches the signal, it is.
This would mean that I will need to push all registers, at least in syscalls, and that I no longer can preserve the control flow as before: in some cases I have to discard two or more kernel stack frames at once, instead of let it shrink completely, in reverse order as it grew.
So I am writing this to ask whether I reasoned correctly or, on the contrary, there is a better way of doing things.
Code: Select all
push scratch registers to the stack
make space for thread save and load pointers in the stack
call c_code // handles interrupts in C (passing %rsp as the first argument) // sets thread_save_pointer and thread_load_pointer if needed
if thread_save_pointer // if there is need to save state to thread_save_pointer
copy saved stack header from the stack to thread_save_pointer
copy saved scratch registers from the stack to thread_save_pointer
copy current preserved registers to thread_save_pointer (the registers should have the same value as before the call, because of the calling convention)
if thread_load_pointer // if there is need to load state from thread_load_pointer
discard old values from the stack
push stack header from thread_load_pointer to the stack
restore scratch registers from thread_load_pointer
restore preserved registers from thread_load_pointer
iretq
// it reaches here if there is no need to load
discard load and save pointers from the stack
pop scratch registers from the stack
discard interrupt number and error code from the stack
iretq
But then I learned, in this thread: viewtopic.php?f=15&t=33393 that a proper way of handling, in kernel mode, the reads and writes to and from userspace, is to do a basic check on the pointer and then to perform the reads or writes and let the processor issue exceptions (page faults) if the memory is not readable/writable. As opossed to doing an exhaustive checking, which would mean to parse the page tables and to do some expensive locking.
I realized that to support exceptions (page faults) in kernel mode while accessing user memory, there is need to save or push to the stack also the preserved registers, at least in syscalls, because they will be needed in case the SIGSEGV signal gets catched (the thread should get the same state as before). This seems so because, after the syscall places its data on the kernel stack, and the page fault exception (nested) places its data too, we cannot return from the exception (it would repeat itself) to restore the preserved registers. Then if we do not have saved the preserved registers, they could have been modified (and saved in the stack, but, at unknown places), so there would be no easy way of getting them. If the process was to be terminated, that is not an issue, but if the process catches the signal, it is.
This would mean that I will need to push all registers, at least in syscalls, and that I no longer can preserve the control flow as before: in some cases I have to discard two or more kernel stack frames at once, instead of let it shrink completely, in reverse order as it grew.
So I am writing this to ask whether I reasoned correctly or, on the contrary, there is a better way of doing things.