Commit caa334712f

kcbanner <kcbanner@gmail.com>
2023-06-28 05:53:05
linux: rework getcontext to closer match the specification (saved IP/SP match the state after it would return) debug: fixup ucontext_t check
1 parent 89ef004
Changed files (3)
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")),
-        : "memory", "ecx"
+          [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"
     );
+}
 
-    // 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);
+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"
+    );
 }
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"
     );
+}
 
-    // 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);
+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"
+    );
 }
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;