Commit 412cd789bf

kcbanner <kcbanner@gmail.com>
2023-07-03 20:31:29
debug: fixup base address calculations for macho dwarf: fixup x86 register mapping logic dwarf: change the register context update to update in-place instead of copying debug: always print the unwind error type
1 parent b85f840
Changed files (5)
lib/std/c/darwin/x86_64.zig
@@ -31,6 +31,29 @@ pub const thread_state = extern struct {
     gs: u64,
 };
 
+const stmm_reg = [16]u8;
+const xmm_reg = [16]u8;
+pub const float_state = extern struct {
+    reserved: [2]c_int,
+    fcw: u16,
+    fsw: u16,
+    ftw: u8,
+    rsrv1: u8,
+    fop: u16,
+    ip: u32,
+    cs: u16,
+    rsrv2: u16,
+    dp: u32,
+    ds: u16,
+    rsrv3: u16,
+    mxcsr: u32,
+    mxcsrmask: u32,
+    stmm: [8]stmm_reg,
+    xmm: [16]xmm_reg,
+    rsrv4: [96]u8,
+    reserved1: c_int,
+};
+
 pub const THREAD_STATE = 4;
 pub const THREAD_STATE_COUNT: c.mach_msg_type_number_t = @sizeOf(thread_state) / @sizeOf(c_int);
 
lib/std/c/darwin.zig
@@ -148,11 +148,13 @@ pub const ucontext_t = extern struct {
     link: ?*ucontext_t,
     mcsize: u64,
     mcontext: *mcontext_t,
+    __mcontext_data: mcontext_t,
 };
 
 pub const mcontext_t = extern struct {
     es: arch_bits.exception_state,
     ss: arch_bits.thread_state,
+    fs: arch_bits.float_state,
 };
 
 extern "c" fn __error() *c_int;
lib/std/dwarf/abi.zig
@@ -68,38 +68,41 @@ pub fn regBytes(ucontext_ptr: anytype, reg_number: u8, reg_ctx: ?RegisterContext
     var m = &ucontext_ptr.mcontext;
 
     return switch (builtin.cpu.arch) {
-        .x86 => switch (reg_number) {
-            0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EAX]),
-            1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ECX]),
-            2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDX]),
-            3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBX]),
-            4...5 => if (reg_ctx) |r| bytes: {
-                if (reg_number == 4) {
-                    break :bytes if (r.eh_frame and r.is_macho)
-                        mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP])
-                    else
-                        mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP]);
-                } else {
-                    break :bytes if (r.eh_frame and r.is_macho)
-                        mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP])
-                    else
-                        mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP]);
-                }
-            } else error.RegisterContextRequired,
-            6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESI]),
-            7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDI]),
-            8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EIP]),
-            9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EFL]),
-            10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.CS]),
-            11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.SS]),
-            12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.DS]),
-            13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ES]),
-            14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.FS]),
-            15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.GS]),
-            16...23 => error.InvalidRegister, // TODO: Support loading ST0-ST7 from mcontext.fpregs
-            // TODO: Map TRAPNO, ERR, UESP
-            32...39 => error.InvalidRegister, // TODO: Support loading XMM0-XMM7 from mcontext.fpregs
-            else => error.InvalidRegister,
+        .x86 => switch (builtin.os.tag) {
+            .linux, .netbsd, .solaris => switch (reg_number) {
+                0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EAX]),
+                1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ECX]),
+                2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDX]),
+                3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBX]),
+                4...5 => if (reg_ctx) |r| bytes: {
+                    if (reg_number == 4) {
+                        break :bytes if (r.eh_frame and r.is_macho)
+                            mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP])
+                        else
+                            mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP]);
+                    } else {
+                        break :bytes if (r.eh_frame and r.is_macho)
+                            mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP])
+                        else
+                            mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP]);
+                    }
+                } else error.RegisterContextRequired,
+                6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESI]),
+                7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDI]),
+                8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EIP]),
+                9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EFL]),
+                10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.CS]),
+                11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.SS]),
+                12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.DS]),
+                13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ES]),
+                14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.FS]),
+                15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.GS]),
+                16...23 => error.InvalidRegister, // TODO: Support loading ST0-ST7 from mcontext.fpregs
+                // TODO: Map TRAPNO, ERR, UESP
+                32...39 => error.InvalidRegister, // TODO: Support loading XMM0-XMM7 from mcontext.fpregs
+                else => error.InvalidRegister,
+            },
+            else => error.UnimplementedOs,
         },
         .x86_64 => switch (builtin.os.tag) {
             .linux, .netbsd, .solaris => switch (reg_number) {
lib/std/debug.zig
@@ -444,7 +444,10 @@ pub inline fn getContext(context: *StackTraceContext) bool {
         return true;
     }
 
-    return have_getcontext and os.system.getcontext(context) == 0;
+    const result = have_getcontext and os.system.getcontext(context) == 0;
+    if (native_os == .macos) assert(context.mcsize == @sizeOf(std.c.mcontext_t));
+
+    return result;
 }
 
 pub const UnwindError = if (have_ucontext)
@@ -553,6 +556,7 @@ pub const StackIterator = struct {
         if (native_os == .freestanding) return true;
 
         const aligned_address = address & ~@as(usize, @intCast((mem.page_size - 1)));
+        if (aligned_address == 0) return false;
         const aligned_memory = @as([*]align(mem.page_size) u8, @ptrFromInt(aligned_address))[0..mem.page_size];
 
         if (native_os != .windows) {
@@ -815,11 +819,7 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz
 pub fn printUnwindError(debug_info: *DebugInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void {
     const module_name = debug_info.getModuleNameForAddress(address) orelse "???";
     try tty_config.setColor(out_stream, .dim);
-    if (err != error.MissingDebugInfo) {
-        try out_stream.print("Unwind information for {s} was not available ({}), trace may be incomplete\n\n", .{ module_name, err });
-    } else {
-        try out_stream.print("Unwind information for {s} was not available, trace may be incomplete\n\n", .{module_name});
-    }
+    try out_stream.print("Unwind information for {s} was not available ({}), trace may be incomplete\n\n", .{ module_name, err });
     try tty_config.setColor(out_stream, .reset);
 }
 
@@ -1309,6 +1309,7 @@ fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugIn
 
     return ModuleDebugInfo{
         .base_address = undefined,
+        .vmaddr_slide = undefined,
         .mapped_memory = mapped_mem,
         .ofiles = ModuleDebugInfo.OFileTable.init(allocator),
         .symbols = symbols,
@@ -1514,11 +1515,10 @@ pub const DebugInfo = struct {
 
         var i: u32 = 0;
         while (i < image_count) : (i += 1) {
-            const base_address = std.c._dyld_get_image_vmaddr_slide(i);
-
-            if (address < base_address) continue;
-
             const header = std.c._dyld_get_image_header(i) orelse continue;
+            const base_address = @intFromPtr(header);
+            if (address < base_address) continue;
+            const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
 
             var it = macho.LoadCommandIterator{
                 .ncmds = header.ncmds,
@@ -1527,14 +1527,16 @@ pub const DebugInfo = struct {
                     @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
                 )[0..header.sizeofcmds]),
             };
+
             while (it.next()) |cmd| switch (cmd.cmd()) {
                 .SEGMENT_64 => {
                     const segment_cmd = cmd.cast(macho.segment_command_64).?;
-                    const rebased_address = address - base_address;
+                    if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
+
+                    const original_address = address - vmaddr_slide;
                     const seg_start = segment_cmd.vmaddr;
                     const seg_end = seg_start + segment_cmd.vmsize;
-
-                    if (rebased_address >= seg_start and rebased_address < seg_end) {
+                    if (original_address >= seg_start and original_address < seg_end) {
                         if (self.address_map.get(base_address)) |obj_di| {
                             return obj_di;
                         }
@@ -1551,6 +1553,7 @@ pub const DebugInfo = struct {
                         };
                         obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
                         obj_di.base_address = base_address;
+                        obj_di.vmaddr_slide = vmaddr_slide;
 
                         try self.address_map.putNoClobber(base_address, obj_di);
 
@@ -1808,6 +1811,7 @@ pub const DebugInfo = struct {
 pub const ModuleDebugInfo = switch (native_os) {
     .macos, .ios, .watchos, .tvos => struct {
         base_address: usize,
+        vmaddr_slide: usize,
         mapped_memory: []align(mem.page_size) const u8,
         symbols: []const MachoSymbol,
         strings: [:0]const u8,
@@ -1972,7 +1976,7 @@ pub const ModuleDebugInfo = switch (native_os) {
         } {
             nosuspend {
                 // Translate the VA into an address into this object
-                const relocated_address = address - self.base_address;
+                const relocated_address = address - self.vmaddr_slide;
 
                 // Find the .o file where this symbol is defined
                 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{
lib/std/dwarf.zig
@@ -1705,28 +1705,53 @@ pub const DwarfInfo = struct {
 
         if (!context.isValidMemory(context.cfa.?)) return error.InvalidCFA;
 
-        // Update the context with the previous frame's values
-        var next_ucontext = context.ucontext;
+        // Buffering the modifications is done because copying the ucontext is not portable,
+        // some implementations (ie. darwin) use internal pointers to the mcontext.
+        var arena = std.heap.ArenaAllocator.init(context.allocator);
+        defer arena.deinit();
+        const update_allocator = arena.allocator();
+
+        const RegisterUpdate = struct {
+            // Backed by ucontext
+            old_value: []u8,
+            // Backed by arena
+            new_value: []const u8,
+            prev: ?*@This(),
+        };
 
+        var update_tail: ?*RegisterUpdate = null;
         var has_next_ip = false;
         for (context.vm.rowColumns(row.*)) |column| {
             if (column.register) |register| {
-                const dest = try abi.regBytes(&next_ucontext, register, context.reg_ctx);
                 if (register == cie.return_address_register) {
                     has_next_ip = column.rule != .undefined;
                 }
 
+                const old_value = try abi.regBytes(&context.ucontext, register, context.reg_ctx);
+                const new_value = try update_allocator.alloc(u8, old_value.len);
+
+                const prev = update_tail;
+                update_tail = try update_allocator.create(RegisterUpdate);
+                update_tail.?.* = .{
+                    .old_value = old_value,
+                    .new_value = new_value,
+                    .prev = prev,
+                };
+
                 try column.resolveValue(
                     context,
                     compile_unit,
                     &context.ucontext,
                     context.reg_ctx,
-                    dest,
+                    new_value,
                 );
             }
         }
 
-        context.ucontext = next_ucontext;
+        while (update_tail) |tail| {
+            @memcpy(tail.old_value, tail.new_value);
+            update_tail = tail.prev;
+        }
 
         if (has_next_ip) {
             context.pc = mem.readIntSliceNative(usize, try abi.regBytes(&context.ucontext, comptime abi.ipRegNum(), context.reg_ctx));