rust-osdev / x86_64

Library to program x86_64 hardware.
https://docs.rs/x86_64
Apache License 2.0
797 stars 132 forks source link

[Problem] Add software interrupt. #392

Closed AmberCrafter closed 2 years ago

AmberCrafter commented 2 years ago

I would like to implment the software interrupt on idt[0x80] ( likes linux's syscall ), but I've run into a calling convention problem: eax will be overwrited when extern "x86-interrupt" fn foo {} is executed. Is this the feature of the x86-interrupt? Or am I missing anything? And where can I found the doucuments about x86-interrupt?

01 02 03

AmberCrafter commented 2 years ago

RFC mention x86-interrupt was clobbered rax. Is that mean we need to use other register to pass the syscall's number? More over, should we need to consider other registers are callee-save registers or define them by myself?

Freax13 commented 2 years ago

AFAICT there are no guarantees about the contents of registers once the function has been entered. If you want to inspect/modify registers you could use the #[naked] attribute on a function and implement the calling convention yourself.

AmberCrafter commented 2 years ago

@Freax13 Thanks for your suggestion. In these days, I trace the asm code of different kind x86-inerrupt function, and found the interesting behavior:

If we use the InterruptStackFrame, the asm code like below, which will overwrite the rax x86-interrupt_calling_convention3

however, if we unuse the InterruptStackFrame, it looks like this, which will not overwrite the rax. x86-interrupt_calling_convention3(diff)

On the other hand, @xfoxfu (repo.) provides a good solution to do the type wrapping (extern "C" -> extern "x86-interrupt") which has already perplexed me for a long time.

Freax13 commented 2 years ago

however, if we unuse the InterruptStackFrame, it looks like this, which will not overwrite the rax. x86-interrupt_calling_convention3(diff)

There's no guarantee that this will always be the case. This might work for now, but can (and most certainly will) break at any time in the future.

On the other hand, @xfoxfu (repo.) provides a good solution to do the type wrapping (extern "C" -> extern "x86-interrupt") which has already perplexed me for a long time.

The thing that makes this work is their wrap! macro which generates a naked function that pushes the registers onto the stack and prepares the registers for calling a function with the C abi before calling the wrapped function.

I think they could use the x86-interrupt calling convention for their generated function and then they wouldn't have to transmute the function when setting the entry.

AmberCrafter commented 2 years ago

however, if we unuse the InterruptStackFrame, it looks like this, which will not overwrite the rax. x86-interrupt_calling_convention3(diff)

There's no guarantee that this will always be the case. This might work for now, but can (and most certainly will) break at any time in the future.

@Freax13 Your right! I can't ensure these behaviors.

On the other hand, @xfoxfu (repo.) provides a good solution to do the type wrapping (extern "C" -> extern "x86-interrupt") which has already perplexed me for a long time.

The thing that makes this work is their wrap! macro which generates a naked function that pushes the registers onto the stack and prepares the registers for calling a function with the C abi before calling the wrapped function.

I think they could use the x86-interrupt calling convention for their generated function and then they wouldn't have to transmute the function when setting the entry.

I think you mean like this

// idt setting
idt[0x80].set_handler_fn(syscall_handler_naked_wrap);

// handler function
#[naked]
pub extern "x86-interrupt" fn syscall_handler_naked_wrap(stack_frame: InterruptStackFrame) {
    unsafe {
        core::arch::asm!(
            "
                push rbp
                push rax
                push rbx
                push rcx
                push rdx
                push rsi
                push rdi
                push r8
                push r9
                push r10
                push r11
                push r12
                push r13
                push r14
                push r15
                mov rsi, rsp    // second arg: register list
                mov rdi, rsp
                add rdi, 15*8   // first arg: interrupt frame
                call {}
                pop r15
                pop r14
                pop r13
                pop r12 
                pop r11
                pop r10
                pop r9
                pop r8
                pop rdi
                pop rsi
                pop rdx
                pop rcx
                pop rbx
                pop rax
                pop rbp
                iretq
            ",
            sym syscall_handler_naked,
            options(noreturn)
        );
    }
}

pub extern "C" fn syscall_handler_naked(sf: &mut InterruptStackFrame, regs: &mut Registers) {
    // something i want to do...
    serial_println!(
        "
            rax: {:?}\n
            rdi: {:?}\n
            rsi: {:?}\n
            rdx: {:?}
        ",
        regs.rax,
        regs.rdi,
        regs.rsi,
        regs.rdx
    );

    serial_println!("{:?}", sf);
    serial_println!("syscall finished!");
}

and it work for me. Hurray!!