Files
RedBear-OS/src/arch/x86_64/interrupt/syscall.rs
T
2020-06-22 13:11:32 +02:00

135 lines
4.1 KiB
Rust

use crate::arch::macros::InterruptStack;
use crate::arch::{gdt, pti};
use crate::syscall::flag::{PTRACE_FLAG_IGNORE, PTRACE_STOP_PRE_SYSCALL, PTRACE_STOP_POST_SYSCALL};
use crate::{ptrace, syscall};
use x86::msr;
pub unsafe fn init() {
msr::wrmsr(msr::IA32_STAR, ((gdt::GDT_KERNEL_CODE as u64) << 3) << 32);
msr::wrmsr(msr::IA32_LSTAR, syscall_instruction as u64);
msr::wrmsr(msr::IA32_FMASK, 0x0300); // Clear trap flag and interrupt enable
msr::wrmsr(msr::IA32_KERNEL_GSBASE, &gdt::TSS as *const _ as u64);
let efer = msr::rdmsr(msr::IA32_EFER);
msr::wrmsr(msr::IA32_EFER, efer | 1);
}
// Not a function pointer because it somehow messes up the returning
// from clone() (via clone_ret()). Not sure what the problem is.
macro_rules! with_interrupt_stack {
(unsafe fn $wrapped:ident($stack:ident) -> usize $code:block) => {
#[inline(never)]
unsafe fn $wrapped(stack: *mut InterruptStack) {
let _guard = ptrace::set_process_regs(stack);
let allowed = ptrace::breakpoint_callback(PTRACE_STOP_PRE_SYSCALL, None)
.and_then(|_| ptrace::next_breakpoint().map(|f| !f.contains(PTRACE_FLAG_IGNORE)));
if allowed.unwrap_or(true) {
// If syscall not ignored
let $stack = &mut *stack;
$stack.scratch.rax = $code;
}
ptrace::breakpoint_callback(PTRACE_STOP_POST_SYSCALL, None);
}
}
}
#[naked]
pub unsafe extern fn syscall_instruction() {
with_interrupt_stack! {
unsafe fn inner(stack) -> usize {
let rbp;
asm!("" : "={rbp}"(rbp) : : : "intel", "volatile");
let scratch = &stack.scratch;
syscall::syscall(scratch.rax, scratch.rdi, scratch.rsi, scratch.rdx, scratch.r10, scratch.r8, rbp, stack)
}
}
// Yes, this is magic. No, you don't need to understand
asm!("
swapgs // Set gs segment to TSS
mov gs:[28], rsp // Save userspace rsp
mov rsp, gs:[4] // Load kernel rsp
push 5 * 8 + 3 // Push userspace data segment
push qword ptr gs:[28] // Push userspace rsp
mov qword ptr gs:[28], 0 // Clear userspace rsp
push r11 // Push rflags
push 4 * 8 + 3 // Push userspace code segment
push rcx // Push userspace return pointer
swapgs // Restore gs
"
:
:
:
: "intel", "volatile");
// Push scratch registers
interrupt_push!();
// Get reference to stack variables
let rsp: usize;
asm!("" : "={rsp}"(rsp) : : : "intel", "volatile");
// Map kernel
pti::map();
inner(rsp as *mut InterruptStack);
// Unmap kernel
pti::unmap();
// Interrupt return
interrupt_pop!();
iret!()
}
#[naked]
pub unsafe extern fn syscall() {
with_interrupt_stack! {
unsafe fn inner(stack) -> usize {
let rbp;
asm!("" : "={rbp}"(rbp) : : : "intel", "volatile");
let scratch = &stack.scratch;
syscall::syscall(scratch.rax, stack.preserved.rbx, scratch.rcx, scratch.rdx, scratch.rsi, scratch.rdi, rbp, stack)
}
}
// Push scratch registers
interrupt_push!();
// Get reference to stack variables
let rsp: usize;
asm!("" : "={rsp}"(rsp) : : : "intel", "volatile");
// Map kernel
pti::map();
inner(rsp as *mut InterruptStack);
// Unmap kernel
pti::unmap();
// Interrupt return
interrupt_pop!();
iret!();
}
#[naked]
pub unsafe extern "C" fn clone_ret() {
// The C x86_64 ABI specifies that rbp is pushed to save the old
// call frame. Popping rbp means we're using the parent's call
// frame and thus will not only return from this function but also
// from the function above this one.
// When this is called, the stack should have been
// interrupt->inner->syscall->clone
// then changed to
// interrupt->inner->clone_ret->clone
// so this will return from "inner".
asm!("pop rbp" : : : : "intel", "volatile");
}