|
| 1 | +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] |
| 2 | +pub(crate) mod go_1_24 { |
| 3 | + use std::arch::naked_asm; |
| 4 | + |
| 5 | + use crate::{hooks::HookManager, macros::hook_symbol}; |
| 6 | + |
| 7 | + pub(crate) fn hook(hook_manager: &mut HookManager, module_name: &str) { |
| 8 | + hook_symbol!( |
| 9 | + hook_manager, |
| 10 | + module_name, |
| 11 | + "internal/runtime/syscall.Syscall6", |
| 12 | + internal_runtime_syscall_syscall6_detour |
| 13 | + ); |
| 14 | + } |
| 15 | + |
| 16 | + /// Detour of `internal/runtime/syscall.Syscall6`. |
| 17 | + /// |
| 18 | + /// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) |
| 19 | + /// https://github.com/golang/go/blob/go1.24.11/src/internal/runtime/syscall/asm_linux_amd64.s#L27 |
| 20 | + /// |
| 21 | + /// Function arguments are mapped as the following: |
| 22 | + /// rax = num |
| 23 | + /// rbx = a1 |
| 24 | + /// rcx = a2 |
| 25 | + /// rdi = a3 |
| 26 | + /// rsi = a4 |
| 27 | + /// r8 = a5 |
| 28 | + /// r9 = a6 |
| 29 | + /// r14 = g |
| 30 | + #[unsafe(naked)] |
| 31 | + unsafe extern "C" fn internal_runtime_syscall_syscall6_detour() { |
| 32 | + naked_asm!( |
| 33 | + // If the syscall is SYS_EXIT or SYS_EXIT_GROUP, |
| 34 | + // skip our logic and just execute it. |
| 35 | + "cmp rax,60", // SYS_EXIT |
| 36 | + "je 2f", |
| 37 | + "cmp rax,231", // SYS_EXIT_GROUP |
| 38 | + "je 2f", |
| 39 | + "push rbp", |
| 40 | + "mov rbp,rsp", |
| 41 | + // Reservce space and store args. |
| 42 | + "sub rsp,0x40", |
| 43 | + "mov [rsp+0x38],r9", |
| 44 | + "mov [rsp+0x30],r8", |
| 45 | + "mov [rsp+0x28],rsi", |
| 46 | + "mov [rsp+0x20],rdi", |
| 47 | + "mov [rsp+0x18],rcx", |
| 48 | + "mov [rsp+0x10],rbx", |
| 49 | + "mov [rsp],rax", |
| 50 | + // function args starting at rdi. |
| 51 | + "mov rdi,rsp", |
| 52 | + "call {c_abi_wrapper_on_systemstack}", |
| 53 | + // Check failure |
| 54 | + "cmp rax,-0xfff", |
| 55 | + "jbe 1f", |
| 56 | + // Syscall failed. |
| 57 | + // Save errno in rcx. |
| 58 | + "neg rax", |
| 59 | + "mov rcx,rax", |
| 60 | + // Fill -1 in rax. |
| 61 | + "mov rax,-0x1", |
| 62 | + // Clear result register. |
| 63 | + "mov rbx,0x0", |
| 64 | + // Drop space reserved for locals. |
| 65 | + "add rsp,0x40", |
| 66 | + // Restore rbp and return. |
| 67 | + "pop rbp", |
| 68 | + "ret", |
| 69 | + // Syscall did not fail. Result is in rax. Clear other result registers. |
| 70 | + "1:", |
| 71 | + "mov rbx,0x0", |
| 72 | + "mov rcx,0x0", |
| 73 | + // Drop space for storing args locally. |
| 74 | + "add rsp,0x40", |
| 75 | + // Restore rbp and return. |
| 76 | + "pop rbp", |
| 77 | + "ret", |
| 78 | + // Move the first syscall argument to the correct register (rdx), |
| 79 | + // and execute the syscall. |
| 80 | + "2:", |
| 81 | + "mov rdx,rdi", |
| 82 | + "syscall", |
| 83 | + |
| 84 | + c_abi_wrapper_on_systemstack = sym c_abi_wrapper_on_systemstack, |
| 85 | + ); |
| 86 | + } |
| 87 | + |
| 88 | + /// Calls [`c_abi_wrapper`] on systemstack. |
| 89 | + /// |
| 90 | + /// Implemented based on [`runtime.systemstack`](https://github.com/golang/go/blob/go1.24.11/src/runtime/asm_amd64.s#L483) |
| 91 | + #[unsafe(naked)] |
| 92 | + unsafe extern "C" fn c_abi_wrapper_on_systemstack() { |
| 93 | + naked_asm!( |
| 94 | + // This is required, as we call `gosave_systemstack_switch` later. |
| 95 | + // Not having any local variables in this function is required as well. |
| 96 | + "push rbp", |
| 97 | + "mov rbp,rsp", |
| 98 | + // We assume that r14 stores the address of g. |
| 99 | + // Load address of current m to rbx. |
| 100 | + // https://github.com/golang/go/blob/go1.24.11/src/runtime/runtime2.go#L410 |
| 101 | + "mov rbx,[r14+0x30]", |
| 102 | + // Check if g is m->gsignal. If so, do not switch stack. |
| 103 | + // https://github.com/golang/go/blob/go1.24.11/src/runtime/runtime2.go#L536 |
| 104 | + "cmp r14,[rbx+0x50]", |
| 105 | + "je 1f", |
| 106 | + // Load address of m->g0 to rdx. |
| 107 | + // Check if g is g0. If so, do not switch stack. |
| 108 | + "mov rdx,[rbx]", |
| 109 | + "cmp r14,rdx", |
| 110 | + "je 1f", |
| 111 | + // We expect g is m->curg now. If not, abort with `ud2`. |
| 112 | + // https://github.com/golang/go/blob/go1.24.11/src/runtime/runtime2.go#L541 |
| 113 | + "cmp r14,[rbx+0xc0]", |
| 114 | + "jne 2f", |
| 115 | + // Switch to system stack. |
| 116 | + "call {gosave_systemstack_switch}", |
| 117 | + // rdx holds the address of m->g0. Store it also in TLS and r14. |
| 118 | + "mov fs:0xfffffffffffffff8,rdx", |
| 119 | + "mov r14,rdx", |
| 120 | + // Fill rsp with g0's g->sched->sp. After this, we are on system stack. |
| 121 | + "mov rsp,[rdx+0x38]", |
| 122 | + // We assume rdi still has the original syscall args stored on user g stack. |
| 123 | + "call {c_abi_wrapper}", |
| 124 | + // Switch back to g stack. |
| 125 | + // https://github.com/golang/go/blob/go1.24.11/src/runtime/asm_amd64.s#L516-L526 |
| 126 | + // We assume r14 still has g0. Store g0's m in rbx. |
| 127 | + "mov rbx,[r14+0x30]", |
| 128 | + // Store m->curg in rsi. |
| 129 | + "mov rsi,[rbx+0xc0]", |
| 130 | + // Store user g in TLS |
| 131 | + "mov fs:0xfffffffffffffff8,rsi", |
| 132 | + // Store user g in r14 |
| 133 | + "mov r14,rsi", |
| 134 | + // Fill rsp with g->sched->sp |
| 135 | + "mov rsp,[rsi+0x38]", |
| 136 | + // Fill rbp with g->sched->bp |
| 137 | + // https://github.com/golang/go/blob/go1.24.11/src/runtime/runtime2.go#L317 |
| 138 | + "mov rbp,[rsi+0x68]", |
| 139 | + "mov QWORD PTR [rsi+0x38],0x0", |
| 140 | + "mov QWORD PTR [rsi+0x68],0x0", |
| 141 | + // Restore rbp and return. |
| 142 | + "pop rbp", |
| 143 | + "ret", |
| 144 | + // Already on system stack. Tail call `c_abi_wrapper`. |
| 145 | + // https://github.com/golang/go/blob/go1.24.11/src/runtime/asm_amd64.s#L528-L537 |
| 146 | + "1:", |
| 147 | + "pop rbp", |
| 148 | + "jmp {c_abi_wrapper}", |
| 149 | + // Abort the program. |
| 150 | + "2:", |
| 151 | + "ud2", |
| 152 | + gosave_systemstack_switch = sym gosave_systemstack_switch, |
| 153 | + c_abi_wrapper = sym c_abi_wrapper, |
| 154 | + ); |
| 155 | + } |
| 156 | + |
| 157 | + /// Exact copy of the `go_1_25::c_abi_wrapper` function. |
| 158 | + /// |
| 159 | + /// Move function args from stack to registers so we can call |
| 160 | + /// [`c_abi_syscall6_handler`](crate::go::c_abi_syscall6_handler). |
| 161 | + /// |
| 162 | + /// We expect rdi stores the address of the start of function args on the stack. |
| 163 | + /// |
| 164 | + /// C ABI: fn(rdi, rsi, rdx, rcx, r8, r9, [stack]) |
| 165 | + #[unsafe(naked)] |
| 166 | + unsafe extern "C" fn c_abi_wrapper() { |
| 167 | + naked_asm!( |
| 168 | + "push rbp", |
| 169 | + "mov rbp,rsp", |
| 170 | + "sub rsp,0x8", |
| 171 | + "and rsp,-0x10", |
| 172 | + "mov rax,[rdi]", |
| 173 | + "mov rsi,[rdi+0x10]", |
| 174 | + "mov rdx,[rdi+0x18]", |
| 175 | + "mov rcx,[rdi+0x20]", |
| 176 | + "mov r8,[rdi+0x28]", |
| 177 | + "mov r9,[rdi+0x30]", |
| 178 | + "mov r10,[rdi+0x38]", |
| 179 | + "mov [rsp],r10", |
| 180 | + "mov rdi,rax", |
| 181 | + "call c_abi_syscall6_handler", |
| 182 | + "mov rsp, rbp", |
| 183 | + "pop rbp", |
| 184 | + "ret" |
| 185 | + ); |
| 186 | + } |
| 187 | + |
| 188 | + /// Implemented based on [`gosave_systemstack_switch`](https://github.com/golang/go/blob/go1.24.11/src/runtime/asm_amd64.s#L823) |
| 189 | + /// |
| 190 | + /// Smashes r9. |
| 191 | + #[unsafe(naked)] |
| 192 | + unsafe extern "C" fn gosave_systemstack_switch() { |
| 193 | + naked_asm!( |
| 194 | + // FIXME: In Go's implementation, it loads the address of `runtime.systemstack_switch` + 8 bytes. |
| 195 | + // [`runtime.systemstack_switch`](https://github.com/golang/go/blob/go1.24.11/src/runtime/asm_amd64.s#L475) |
| 196 | + // is a dummy marker function that shall never be called. |
| 197 | + // Go runtime uses it to determine if a g is curently switched to systemstack. |
| 198 | + // |
| 199 | + // In case of dlopen() multiple c-shared go libs, I don't know how to do it yet :) |
| 200 | + "lea r9, [rip + {runtime_abort}]", |
| 201 | + // Store r9 in g->sched->pc. |
| 202 | + "mov QWORD PTR [r14+0x40],r9", |
| 203 | + "lea r9, [rsp+0x8]", |
| 204 | + // Store r9 in g->sched->sp. |
| 205 | + "mov QWORD PTR [r14+0x38],r9", |
| 206 | + // Store r9 in g->sched->ret. |
| 207 | + "mov QWORD PTR [r14+0x58],0x0", |
| 208 | + // Store r9 in g->sched->bp. |
| 209 | + "mov QWORD PTR [r14+0x68],rbp", |
| 210 | + // Store g->sched->ctxt in r9. |
| 211 | + "mov r9, QWORD PTR [r14+0x50]", |
| 212 | + // If g->sched->ctxt == 0 abort runtime, otherwise return. |
| 213 | + "test r9, r9", |
| 214 | + "jz 1f", |
| 215 | + "call {runtime_abort}", |
| 216 | + "1:", |
| 217 | + "ret", |
| 218 | + |
| 219 | + runtime_abort = sym runtime_abort, |
| 220 | + ); |
| 221 | + } |
| 222 | + |
| 223 | + /// Implemented based on [`runtime.abort`](https://github.com/golang/go/blob/fed3b0a298464457c58d1150bdb3942f22bd6220/src/runtime/asm_amd64.s#L1237) |
| 224 | + /// |
| 225 | + /// This function crashes the runtime. `int3` is recognized by debuggers. |
| 226 | + /// The rest of the function is an infinite trap loop. |
| 227 | + #[unsafe(naked)] |
| 228 | + unsafe extern "C" fn runtime_abort() { |
| 229 | + naked_asm!("int3", "1:", "jmp 1b",); |
| 230 | + } |
| 231 | +} |
0 commit comments