trapframe/arch/x86_64/
fncall.rs

1//! Switch context by function call within the same privilege level.
2//!
3//! # Assumption
4//!
5//! This module suppose you are running kernel on Linux or macOS with glibc,
6//! and your user program is based on musl libc.
7//!
8//! Because we will store values in their pthread structure.
9
10use super::UserContext;
11use core::arch::global_asm;
12
13extern "sysv64" {
14    /// The syscall entry of function call.
15    ///
16    /// # Usage
17    ///
18    /// Replace `syscall` instruction by a `call` instruction.
19    ///
20    /// ```asm
21    /// syscall
22    /// call syscall_fn_entry
23    /// ```
24    pub fn syscall_fn_entry();
25
26    fn syscall_fn_return(regs: &mut UserContext);
27}
28
29impl UserContext {
30    /// Go to user context by function return, within the same privilege level.
31    ///
32    /// User program should call `syscall_fn_entry()` to return back.
33    /// Trap reason and error code will always be set to 0x100 and 0.
34    pub fn run_fncall(&mut self) {
35        unsafe {
36            syscall_fn_return(self);
37        }
38        self.trap_num = 0x100;
39        self.error_code = 0;
40    }
41}
42
43// User: (musl)
44// - fs:0  (pthread.self)       = user fsbase
45// - fs:48 (pthread.canary2)    = kernel fsbase
46//
47// Kernel: (glibc)
48// - fs:0  (pthread.self)       = kernel fsbase
49// - fs:64 (pthread.???)        = kernel stack
50// - fs:72 (pthread.???)        = init user fsbase
51//
52#[cfg(target_os = "linux")]
53global_asm!(
54    r#"
55.macro SWITCH_TO_KERNEL_STACK
56    mov rsp, fs:48          # rsp = kernel fsbase
57    mov rsp, [rsp + 64]     # rsp = kernel stack
58.endm
59.macro SAVE_KERNEL_STACK
60    mov fs:64, rsp
61.endm
62.macro PUSH_USER_FSBASE
63    push fs:0
64.endm
65.macro SWITCH_TO_KERNEL_FSBASE
66    mov eax, 158            # SYS_arch_prctl
67    mov edi, 0x1002         # SET_FS
68    mov rsi, fs:48          # rsi = kernel fsbase
69    syscall
70.endm
71.macro POP_USER_FSBASE
72    mov rsi, [rsp + 18 * 8] # rsi = user fsbase
73    mov rdx, fs:0           # rdx = kernel fsbase
74    test rsi, rsi
75    jnz 1f                  # if not 0, goto set
760:  lea rsi, [rdx + 72]     # rsi = init user fsbase
77    mov [rsi], rsi          # user_fs:0 = user fsbase
781:  mov eax, 158            # SYS_arch_prctl
79    mov edi, 0x1002         # SET_FS
80    syscall                 # set fsbase
81    mov fs:48, rdx          # user_fs:48 = kernel fsbase
82.endm
83
84.global syscall_fn_entry
85.global syscall_fn_return
86"#
87);
88
89// User: (musl)
90// - gs:0   (pthread.self)      = user gsbase
91// - gs:48  (pthread.canary2)   = kernel gsbase
92//
93// Kernel: (darwin)
94// - gs:0   (pthread.tsd[self]) = kernel gsbase - 224
95// - gs:48  (pthread.tsd[6])    = kernel stack
96// - gs:240 (pthread.tsd[30])   = init user fsbase
97//
98// Ref:
99// - Set gsbase:
100//   - https://gist.github.com/aras-p/5389747
101// - Get gsbase:
102//   - https://github.com/DynamoRIO/dynamorio/issues/1568#issuecomment-239819506
103//   - https://github.com/apple/darwin-libpthread/blob/03c4628c8940cca6fd6a82957f683af804f62e7f/src/internal.h#L241
104#[cfg(target_os = "macos")]
105global_asm!(
106    r#"
107.macro SWITCH_TO_KERNEL_STACK
108    mov rsp, gs:48          # rsp = kernel gsbase
109    mov rsp, [rsp + 48]     # rsp = kernel stack
110.endm
111.macro SAVE_KERNEL_STACK
112    mov gs:48, rsp
113.endm
114.macro PUSH_USER_FSBASE
115    push gs:0
116.endm
117.macro SWITCH_TO_KERNEL_FSBASE
118    mov rdi, gs:48          # rdi = kernel gsbase
119    mov eax, 0x3000003
120    syscall                 # set gsbase
121.endm
122.macro POP_USER_FSBASE
123    mov rdi, [rsp + 18 * 8] # rdi = user gsbase
124    mov rsi, gs:0
125    add rsi, 224            # rsi = kernel gsbase
126    test rdi, rdi
127    jnz 1f                  # if not 0, goto set
1280:  lea rdi, [rsi + 30*8]   # rdi = init user gsbase
129                            #     = pthread.tsd[30] (kernel gsbase + 30 * 8)
130    mov [rdi], rdi          # user_gs:0 = user gsbase
1311:  mov eax, 0x3000003
132    syscall                 # set gsbase
133    mov gs:48, rsi          # user_gs:48 = kernel gsbase
134.endm
135
136.global _syscall_fn_entry
137.global syscall_fn_entry
138.global _syscall_fn_return
139.set _syscall_fn_entry, syscall_fn_entry
140.set _syscall_fn_return, syscall_fn_return
141"#
142);
143
144global_asm!(
145    r#"
146syscall_fn_entry:
147    # save rsp
148    lea r11, [rsp + 8]      # save rsp to r11 (clobber)
149
150    SWITCH_TO_KERNEL_STACK
151    pop rsp
152    lea rsp, [rsp + 20*8]   # rsp = top of trap frame
153
154    # push trap frame (struct GeneralRegs)
155    push 0                  # ignore gs_base
156    PUSH_USER_FSBASE
157    pushfq                  # push rflags
158    push [r11 - 8]          # push rip
159    push r15
160    push r14
161    push r13
162    push r12
163    push r11
164    push r10
165    push r9
166    push r8
167    push r11                # push rsp
168    push rbp
169    push rdi
170    push rsi
171    push rdx
172    push rcx
173    push rbx
174    push rax
175
176    # restore callee-saved registers
177    SWITCH_TO_KERNEL_STACK
178    pop rbx
179    pop rbx
180    pop rbp
181    pop r12
182    pop r13
183    pop r14
184    pop r15
185
186    SWITCH_TO_KERNEL_FSBASE
187
188    # go back to Rust
189    ret
190
191    # extern "sysv64" fn syscall_fn_return(&mut UserContext)
192syscall_fn_return:
193    # save callee-saved registers
194    push r15
195    push r14
196    push r13
197    push r12
198    push rbp
199    push rbx
200
201    push rdi
202    SAVE_KERNEL_STACK
203    mov rsp, rdi
204
205    POP_USER_FSBASE
206
207    # pop trap frame (struct GeneralRegs)
208    pop rax
209    pop rbx
210    pop rcx
211    pop rdx
212    pop rsi
213    pop rdi
214    pop rbp
215    pop r8                  # skip rsp
216    pop r8
217    pop r9
218    pop r10
219    pop r11
220    pop r12
221    pop r13
222    pop r14
223    pop r15
224    pop r11                 # r11 = rip. FIXME: don't overwrite r11!
225    popfq                   # pop rflags
226    mov rsp, [rsp - 8*11]   # restore rsp
227    jmp r11                 # restore rip
228"#
229);
230
231#[cfg(test)]
232mod tests {
233    use crate::*;
234    use core::arch::global_asm;
235
236    #[cfg(target_os = "macos")]
237    global_asm!(".set _dump_registers, dump_registers");
238
239    // Mock user program to dump registers at stack.
240    global_asm!(
241        r#"
242dump_registers:
243    push r15
244    push r14
245    push r13
246    push r12
247    push r11
248    push r10
249    push r9
250    push r8
251    push rsp
252    push rbp
253    push rdi
254    push rsi
255    push rdx
256    push rcx
257    push rbx
258    push rax
259
260    add rax, 10
261    add rbx, 10
262    add rcx, 10
263    add rdx, 10
264    add rsi, 10
265    add rdi, 10
266    add rbp, 10
267    add r8, 10
268    add r9, 10
269    add r10, 10
270    add r11, 10
271    add r12, 10
272    add r13, 10
273    add r14, 10
274    add r15, 10
275
276    call syscall_fn_entry
277"#
278    );
279
280    #[test]
281    fn run_fncall() {
282        extern "sysv64" {
283            fn dump_registers();
284        }
285        let mut stack = [0u8; 0x1000];
286        let mut cx = UserContext {
287            general: GeneralRegs {
288                rax: 0,
289                rbx: 1,
290                rcx: 2,
291                rdx: 3,
292                rsi: 4,
293                rdi: 5,
294                rbp: 6,
295                rsp: stack.as_mut_ptr() as usize + 0x1000,
296                r8: 8,
297                r9: 9,
298                r10: 10,
299                r11: 11,
300                r12: 12,
301                r13: 13,
302                r14: 14,
303                r15: 15,
304                rip: dump_registers as usize,
305                rflags: 0,
306                fsbase: 0, // don't set to non-zero garbage value
307                gsbase: 0,
308            },
309            trap_num: 0,
310            error_code: 0,
311        };
312        cx.run_fncall();
313        // check restored registers
314        let general = unsafe { *(cx.general.rsp as *const GeneralRegs) };
315        assert_eq!(
316            general,
317            GeneralRegs {
318                rax: 0,
319                rbx: 1,
320                rcx: 2,
321                rdx: 3,
322                rsi: 4,
323                rdi: 5,
324                rbp: 6,
325                // skip rsp
326                r8: 8,
327                r9: 9,
328                r10: 10,
329                // skip r11
330                r12: 12,
331                r13: 13,
332                r14: 14,
333                r15: 15,
334                ..general
335            }
336        );
337        // check saved registers
338        assert_eq!(
339            cx.general,
340            GeneralRegs {
341                rax: 10,
342                rbx: 11,
343                rcx: 12,
344                rdx: 13,
345                rsi: 14,
346                rdi: 15,
347                rbp: 16,
348                // skip rsp
349                r8: 18,
350                r9: 19,
351                r10: 20,
352                // skip r11
353                r12: 22,
354                r13: 23,
355                r14: 24,
356                r15: 25,
357                ..cx.general
358            }
359        );
360        assert_eq!(cx.trap_num, 0x100);
361        assert_eq!(cx.error_code, 0);
362    }
363}