From caa334712fc8f540349e54fa3008aa3fdef64e13 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Tue, 27 Jun 2023 23:53:05 -0400 Subject: [PATCH] linux: rework getcontext to closer match the specification (saved IP/SP match the state after it would return) debug: fixup ucontext_t check --- lib/std/debug.zig | 2 +- lib/std/os/linux/x86.zig | 85 +++++++++++++++++++++----------- lib/std/os/linux/x86_64.zig | 97 +++++++++++++++++++++++-------------- 3 files changed, 119 insertions(+), 65 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 2c3f93b55e..7b74be24af 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -136,7 +136,7 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { pub const StackTraceContext = blk: { if (native_os == .windows) { break :blk std.os.windows.CONTEXT; - } else if (@hasDecl(os.system, "ucontext_t")) { + } else if (StackIterator.supports_context) { break :blk os.ucontext_t; } else { break :blk void; diff --git a/lib/std/os/linux/x86.zig b/lib/std/os/linux/x86.zig index 81a3bac92b..e5d75c1831 100644 --- a/lib/std/os/linux/x86.zig +++ b/lib/std/os/linux/x86.zig @@ -394,28 +394,51 @@ fn gpRegisterOffset(comptime reg_index: comptime_int) usize { return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index; } -pub inline fn getcontext(context: *ucontext_t) usize { +noinline fn getContextReturnAddress() usize { + return @returnAddress(); +} + +pub fn getContextInternal() callconv(.Naked) void { asm volatile ( - \\ movl %%edi, (%[edi_offset])(%[context]) - \\ movl %%esi, (%[esi_offset])(%[context]) - \\ movl %%ebp, (%[ebp_offset])(%[context]) - \\ movl %%esp, (%[esp_offset])(%[context]) - \\ movl %%ebx, (%[ebx_offset])(%[context]) - \\ movl %%edx, (%[edx_offset])(%[context]) - \\ movl %%ecx, (%[ecx_offset])(%[context]) - \\ movl %%eax, (%[eax_offset])(%[context]) + \\ movl $0, (%[flags_offset])(%%edx) + \\ movl $0, (%[link_offset])(%%edx) + \\ movl %%edi, (%[edi_offset])(%%edx) + \\ movl %%esi, (%[esi_offset])(%%edx) + \\ movl %%ebp, (%[ebp_offset])(%%edx) + \\ movl %%ebx, (%[ebx_offset])(%%edx) + \\ movl %%edx, (%[edx_offset])(%%edx) + \\ movl %%ecx, (%[ecx_offset])(%%edx) + \\ movl %%eax, (%[eax_offset])(%%edx) + \\ movl (%%esp), %%ecx + \\ movl %%ecx, (%[eip_offset])(%%edx) + \\ leal 4(%%esp), %%ecx + \\ movl %%ecx, (%[esp_offset])(%%edx) \\ xorl %%ecx, %%ecx \\ movw %%fs, %%cx - \\ movl %%ecx, (%[fs_offset])(%[context]) - \\ leal (%[regspace_offset])(%[context]), %%ecx - \\ movl %%ecx, (%[fpregs_offset])(%[context]) + \\ movl %%ecx, (%[fs_offset])(%%edx) + \\ leal (%[regspace_offset])(%%edx), %%ecx + \\ movl %%ecx, (%[fpregs_offset])(%%edx) \\ fnstenv (%%ecx) \\ fldenv (%%ecx) - \\ call getcontext_read_eip - \\ getcontext_read_eip: pop %%ecx - \\ movl %%ecx, (%[eip_offset])(%[context]) + \\ pushl %%ebx + \\ pushl %%esi + \\ xorl %%ebx, %%ebx + \\ movl %[sigaltstack], %%eax + \\ leal (%[stack_offset])(%%edx), %%ecx + \\ int $0x80 + \\ cmpl $0, %%eax + \\ jne return + \\ movl %[sigprocmask], %%eax + \\ xorl %%ecx, %%ecx + \\ leal (%[sigmask_offset])(%%edx), %%edx + \\ movl %[sigset_size], %%esi + \\ int $0x80 + \\ return: + \\ popl %%esi + \\ popl %%ebx : - : [context] "{edi}" (context), + : [flags_offset] "p" (@offsetOf(ucontext_t, "flags")), + [link_offset] "p" (@offsetOf(ucontext_t, "link")), [edi_offset] "p" (comptime gpRegisterOffset(REG.EDI)), [esi_offset] "p" (comptime gpRegisterOffset(REG.ESI)), [ebp_offset] "p" (comptime gpRegisterOffset(REG.EBP)), @@ -428,18 +451,24 @@ pub inline fn getcontext(context: *ucontext_t) usize { [fs_offset] "p" (comptime gpRegisterOffset(REG.FS)), [fpregs_offset] "p" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")), [regspace_offset] "p" (@offsetOf(ucontext_t, "regspace")), + [sigaltstack] "i" (@intFromEnum(linux.SYS.sigaltstack)), + [stack_offset] "p" (@offsetOf(ucontext_t, "stack")), + [sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)), + [sigmask_offset] "p" (@offsetOf(ucontext_t, "sigmask")), + [sigset_size] "i" (linux.NSIG / 8), + : "memory", "eax", "ecx", "edx" + ); +} + +pub inline fn getcontext(context: *ucontext_t) usize { + // This method is used so that getContextInternal can control + // its prologue in order to read ESP from a constant offset. + // The unused &getContextInternal input is required so the function is included in the binary. + return asm volatile ( + \\ call os.linux.x86.getContextInternal + : [ret] "={eax}" (-> usize), + : [context] "{edx}" (context), + [getContextInternal] "X" (&getContextInternal), : "memory", "ecx" ); - - // TODO: Read CS/SS registers? - // TODO: Store mxcsr state, need an actual definition of fpstate for that - - // TODO: `flags` isn't present in the getcontext man page, figure out what to write here - context.flags = 0; - context.link = null; - - const altstack_result = linux.sigaltstack(null, &context.stack); - if (altstack_result != 0) return altstack_result; - - return linux.sigprocmask(0, null, &context.sigmask); } diff --git a/lib/std/os/linux/x86_64.zig b/lib/std/os/linux/x86_64.zig index 786a95c7a5..e5febce14d 100644 --- a/lib/std/os/linux/x86_64.zig +++ b/lib/std/os/linux/x86_64.zig @@ -400,35 +400,53 @@ fn gpRegisterOffset(comptime reg_index: comptime_int) usize { return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index; } -pub inline fn getcontext(context: *ucontext_t) usize { +fn getContextInternal() callconv(.Naked) void { + // TODO: Read GS/FS registers? asm volatile ( - \\ movq %%r8, (%[r8_offset])(%[context]) - \\ movq %%r9, (%[r9_offset])(%[context]) - \\ movq %%r10, (%[r10_offset])(%[context]) - \\ movq %%r11, (%[r11_offset])(%[context]) - \\ movq %%r12, (%[r12_offset])(%[context]) - \\ movq %%r13, (%[r13_offset])(%[context]) - \\ movq %%r14, (%[r14_offset])(%[context]) - \\ movq %%r15, (%[r15_offset])(%[context]) - \\ movq %%rdi, (%[rdi_offset])(%[context]) - \\ movq %%rsi, (%[rsi_offset])(%[context]) - \\ movq %%rbp, (%[rbp_offset])(%[context]) - \\ movq %%rbx, (%[rbx_offset])(%[context]) - \\ movq %%rdx, (%[rdx_offset])(%[context]) - \\ movq %%rax, (%[rax_offset])(%[context]) - \\ movq %%rcx, (%[rcx_offset])(%[context]) - \\ movq %%rsp, (%[rsp_offset])(%[context]) - \\ leaq (%%rip), %%rcx - \\ movq %%rcx, (%[rip_offset])(%[context]) + \\ movq $0, (%[flags_offset])(%%rdi) + \\ movq $0, (%[link_offset])(%%rdi) + \\ movq %%r8, (%[r8_offset])(%%rdi) + \\ movq %%r9, (%[r9_offset])(%%rdi) + \\ movq %%r10, (%[r10_offset])(%%rdi) + \\ movq %%r11, (%[r11_offset])(%%rdi) + \\ movq %%r12, (%[r12_offset])(%%rdi) + \\ movq %%r13, (%[r13_offset])(%%rdi) + \\ movq %%r14, (%[r14_offset])(%%rdi) + \\ movq %%r15, (%[r15_offset])(%%rdi) + \\ movq %%rdi, (%[rdi_offset])(%%rdi) + \\ movq %%rsi, (%[rsi_offset])(%%rdi) + \\ movq %%rbp, (%[rbp_offset])(%%rdi) + \\ movq %%rbx, (%[rbx_offset])(%%rdi) + \\ movq %%rdx, (%[rdx_offset])(%%rdi) + \\ movq %%rax, (%[rax_offset])(%%rdi) + \\ movq %%rcx, (%[rcx_offset])(%%rdi) + \\ movq (%%rsp), %%rcx + \\ movq %%rcx, (%[rip_offset])(%%rdi) + \\ leaq 8(%%rsp), %%rcx + \\ movq %%rcx, (%[rsp_offset])(%%rdi) \\ pushfq - \\ popq (%[efl_offset])(%[context]) - \\ leaq (%[fpmem_offset])(%[context]), %%rcx - \\ movq %%rcx, (%[fpstate_offset])(%[context]) + \\ popq (%[efl_offset])(%%rdi) + \\ leaq (%[fpmem_offset])(%%rdi), %%rcx + \\ movq %%rcx, (%[fpstate_offset])(%%rdi) \\ fnstenv (%%rcx) \\ fldenv (%%rcx) - \\ stmxcsr (%[mxcsr_offset])(%[context]) + \\ stmxcsr (%[mxcsr_offset])(%%rdi) + \\ leaq (%[stack_offset])(%%rdi), %%rsi + \\ movq %%rdi, %%r8 + \\ xorq %%rdi, %%rdi + \\ movq %[sigaltstack], %%rax + \\ syscall + \\ cmpq $0, %%rax + \\ jne return + \\ movq %[sigprocmask], %%rax + \\ xorq %%rsi, %%rsi + \\ leaq (%[sigmask_offset])(%%r8), %%rdx + \\ movq %[sigset_size], %%r10 + \\ syscall + \\ return: : - : [context] "{rdi}" (context), + : [flags_offset] "p" (@offsetOf(ucontext_t, "flags")), + [link_offset] "p" (@offsetOf(ucontext_t, "link")), [r8_offset] "p" (comptime gpRegisterOffset(REG.R8)), [r9_offset] "p" (comptime gpRegisterOffset(REG.R9)), [r10_offset] "p" (comptime gpRegisterOffset(REG.R10)), @@ -450,17 +468,24 @@ pub inline fn getcontext(context: *ucontext_t) usize { [fpstate_offset] "p" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")), [fpmem_offset] "p" (@offsetOf(ucontext_t, "fpregs_mem")), [mxcsr_offset] "p" (@offsetOf(ucontext_t, "fpregs_mem") + @offsetOf(fpstate, "mxcsr")), - : "memory", "rcx" + [sigaltstack] "i" (@intFromEnum(linux.SYS.sigaltstack)), + [stack_offset] "p" (@offsetOf(ucontext_t, "stack")), + [sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)), + [sigmask_offset] "p" (@offsetOf(ucontext_t, "sigmask")), + [sigset_size] "i" (linux.NSIG / 8), + : "memory", "rcx", "rdx", "rdi", "rsi", "r8", "r10", "r11" + ); +} + +pub inline fn getcontext(context: *ucontext_t) usize { + // This method is used so that getContextInternal can control + // its prologue in order to read RSP from a constant offset + // The unused &getContextInternal input is required so the function is included in the binary. + return asm volatile ( + \\ call os.linux.x86_64.getContextInternal + : [ret] "={rax}" (-> usize), + : [context] "{rdi}" (context), + [getContextInternal] "X" (&getContextInternal), + : "memory", "rcx", "rdx", "rdi", "rsi", "r8", "r10", "r11" ); - - // TODO: Read GS/FS registers? - - // TODO: `flags` isn't present in the getcontext man page, figure out what to write here - context.flags = 0; - context.link = null; - - const altstack_result = linux.sigaltstack(null, &context.stack); - if (altstack_result != 0) return altstack_result; - - return linux.sigprocmask(0, null, &context.sigmask); }