I need to use Rust code in a naked function, because the function handles an x86_64 OS context switch on a timer interrupt, and RSP should be untouched at the beginning of the function body, to save everything before moving on. I have tried to use #[naked]
for this, but it is really inflexible, because I can only write assembly code in it.
If I use a stable calling convention (like extern "C"
), the function makes some room for locals on the stack by subtracting whatever it's needed from RSP. This is not OK because if I want to save the pushed registers I need to know the stack pointer address of the last one, but now I am unable to, because I do not know how much does Rust subtract from RSP.
My naked function would still work with local variables if I subtract a known value (even though larger than needed) from RSP AFTER saving the stack pointer, but obviously Rust steps in my way by not letting me use anything besides ASM if I don't want a prologue.
I hear you asking, why couldn't I just push the registers after the prologue? The answer is that I need to take the saved RIP, CS, RFLAGS, RSP and SS registers too, pushed by the CPU when the timer interrupt happens, but before the prologue. I have seen that a couple of years ago Rust allowed actual code in a naked function, but now it does not, and I couldn't find a fitting replacement yet. I really think RFC #2972 made naked functions useless in most cases, because in my opinion if the developer really pays attention to what code they are writing and how, inspecting the generated assembly too, and if they make sure that the naked function only gets called from known places, it wouldn't "likely depend on undefined behavior".
What could match my requirements?
Solved it! I made the interrupt handler a naked function, that does the context save and passes the stack address to an extern "C"
function through the first parameter.
#[naked]
#[no_mangle]
pub unsafe fn timer_handler_naked_scheduler() {
asm!("push r15; push r14; push r13; push r12; push r11; push r10; push r9;\
push r8; push rdi; push rsi; push rdx; push rcx; push rbx; push rax; push rbp;\
mov rdi, rsp; call scheduler_part_2;", options(noreturn));
}
#[no_mangle]
pub unsafe extern "C" fn scheduler_part_2(context: *const TaskContext) {
serial_println!("{:x?}", *context);
pic::end_of_interrupt(interrupts::HardwareInterrupt::Timer);
task::restore_context(&*context);
}