RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / trap.rs
6254 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! Trap frames: the user's registers, saved on the kernel stack.
//!
//! Every way into the kernel from ring 3 -- a `syscall`, a page fault or
//! other exception, the timer interrupt -- now saves *all* of the user's
//! registers in one layout, `TrapFrame`, at the top of the thread's kernel
//! stack, and every way back goes through one exit path (`trap_return`)
//! that reloads them and `iretq`s. Linux calls this `struct pt_regs`.
//!
//! Why bother (stage 13 used `sysretq` and the ready-made
//! `extern "x86-interrupt"` handlers)? Because the new process features
//! need to *read and change* the user's registers:
//!   * fork copies them for the child (with rax = 0, "you're the child"),
//!   * execve replaces them (new rip, new rsp, everything else zero),
//!   * a signal saves them on the user stack and points rip at the handler;
//!     rt_sigreturn puts them back,
//!   * and signals must be checked on *every* return to user mode, also
//!     after a timer interrupt (that's how Ctrl+C stops a program that
//!     never makes a syscall, like `spin`).
//!
//! Layout on the kernel stack (lowest address first; `rsp` points at r15
//! while the Rust handler runs):
//!
//!   r15 r14 r13 r12 r11 r10 r9 r8 rbp rdi rsi rdx rcx rbx rax   (we push)
//!   vector  error                                               (we push)
//!   rip cs rflags rsp ss                     (the CPU pushes, or we do)
//!
//! For a syscall `vector` is TRAP_SYSCALL and `error` holds the syscall
//! number (Linux's "orig_rax": rax gets the result, but a restarted call
//! needs the number again).

use core::arch::global_asm;

/// `vector` value of a frame built by the syscall entry.
pub const TRAP_SYSCALL: u64 = 0x100;

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct TrapFrame {
    pub r15: u64,
    pub r14: u64,
    pub r13: u64,
    pub r12: u64,
    pub r11: u64,
    pub r10: u64,
    pub r9: u64,
    pub r8: u64,
    pub rbp: u64,
    pub rdi: u64,
    pub rsi: u64,
    pub rdx: u64,
    pub rcx: u64,
    pub rbx: u64,
    pub rax: u64,
    pub vector: u64,
    pub error: u64,
    pub rip: u64,
    pub cs: u64,
    pub rflags: u64,
    pub rsp: u64,
    pub ss: u64,
}

impl TrapFrame {
    /// Registers for starting a program: everything zero except the entry
    /// point, the stack, and RFLAGS = interrupts on (0x200, plus the
    /// always-one bit 1).
    pub fn new_user(rip: u64, rsp: u64) -> TrapFrame {
        let s = crate::gdt::selectors();
        TrapFrame {
            rip,
            rsp,
            rflags: 0x202,
            cs: s.user_code.0 as u64,
            ss: s.user_data.0 as u64,
            ..Default::default()
        }
    }

    /// Did this trap come from ring 3? (The low two bits of CS are the
    /// privilege level the CPU was running at.)
    pub fn from_user(&self) -> bool {
        self.cs & 3 == 3
    }

    pub fn is_syscall(&self) -> bool {
        self.vector == TRAP_SYSCALL
    }
}

unsafe extern "C" {
    /// Pops a TrapFrame (rsp must point at it) and `iretq`s.
    pub fn trap_return();
}

/// Leave the kernel for good: load `frame` into the registers and jump to
/// user mode. Used by a new process's first instructions (spawned or
/// forked). The frame is copied onto our own stack first; the stack below
/// it doesn't matter any more.
pub fn return_to_user(frame: TrapFrame) -> ! {
    unsafe {
        core::arch::asm!(
            "mov rsp, {frame}",
            "jmp {ret}",
            frame = in(reg) &frame as *const TrapFrame,
            ret = sym trap_return,
            options(noreturn)
        )
    }
}

// The common exit. Interrupts off: from here on we're restoring user
// registers and must not be interrupted half-way (the CPU turns them back
// on itself, because the saved RFLAGS has IF set).
global_asm!(
    ".global trap_return",
    "trap_return:",
    "    cli",
    "    pop r15",
    "    pop r14",
    "    pop r13",
    "    pop r12",
    "    pop r11",
    "    pop r10",
    "    pop r9",
    "    pop r8",
    "    pop rbp",
    "    pop rdi",
    "    pop rsi",
    "    pop rdx",
    "    pop rcx",
    "    pop rbx",
    "    pop rax",
    "    add rsp, 16", // skip vector and error
    "    iretq",
);

/// An interrupt/exception entry stub. The CPU has pushed ss, rsp, rflags,
/// cs, rip (and for some exceptions an error code). We push a fake error
/// code if needed, the vector number and all general registers, then call
/// `interrupts::trap_handler(&mut TrapFrame)` and leave through
/// `trap_return`. 22 slots of 8 bytes: the CPU aligned rsp to 16 before
/// its pushes, so it's still 16-aligned for the call.
macro_rules! trap_stub {
    ($name:literal, $vector:literal, $push_error:literal) => {
        global_asm!(
            concat!(".global ", $name),
            concat!($name, ":"),
            $push_error,
            concat!("    push ", $vector),
            "    push rax",
            "    push rbx",
            "    push rcx",
            "    push rdx",
            "    push rsi",
            "    push rdi",
            "    push rbp",
            "    push r8",
            "    push r9",
            "    push r10",
            "    push r11",
            "    push r12",
            "    push r13",
            "    push r14",
            "    push r15",
            "    mov rdi, rsp",
            "    cld", // Rust expects the direction flag clear
            "    call {handler}",
            "    jmp {ret}",
            handler = sym crate::interrupts::trap_handler,
            ret = sym trap_return,
        );
    };
}

// Exceptions that a user program can cause (and get a signal for), plus
// the local APIC timer (so a running program can be stopped by a signal).
trap_stub!("trap_divide_error", "0", "push 0"); // SIGFPE
trap_stub!("trap_invalid_opcode", "6", "push 0"); // SIGILL
trap_stub!("trap_general_protection", "13", ""); // SIGSEGV (CPU pushes an error code)
trap_stub!("trap_page_fault", "14", ""); // SIGSEGV or demand paging
trap_stub!("trap_apic_timer", "0x40", "push 0");

unsafe extern "C" {
    pub fn trap_divide_error();
    pub fn trap_invalid_opcode();
    pub fn trap_general_protection();
    pub fn trap_page_fault();
    pub fn trap_apic_timer();
}