| author | |
| committer | |
| log | b64535e3c8770db9bf314fc14d1b2b276b864f72 |
| tree | 4a6f90029d8feff983889a133326fbe2a4e3465d |
| parent | 7adb15892eada307b43a6a7844d3e51720f8992d |
| parent | 1120546f72405ac263dce7414eb71ca4e6c96fc8 |
| signature |
The Great `std.debug` Refactor90 files changed, 8072 insertions(+), 7886 deletions(-)
build.zig+2-1| ... | ... | @@ -563,7 +563,8 @@ pub fn build(b: *std.Build) !void { |
| 563 | 563 | .skip_release = skip_release, |
| 564 | 564 | })); |
| 565 | 565 | test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, enable_ios_sdk, enable_symlinks_windows)); |
| 566 | test_step.dependOn(tests.addStackTraceTests(b, test_filters, optimization_modes)); | |
| 566 | test_step.dependOn(tests.addStackTraceTests(b, test_filters, skip_non_native)); | |
| 567 | test_step.dependOn(tests.addErrorTraceTests(b, test_filters, optimization_modes, skip_non_native)); | |
| 567 | 568 | test_step.dependOn(tests.addCliTests(b)); |
| 568 | 569 | if (tests.addDebuggerTests(b, .{ |
| 569 | 570 | .test_filters = test_filters, |
lib/compiler/test_runner.zig+4-4| ... | ... | @@ -140,7 +140,7 @@ fn mainServer() !void { |
| 140 | 140 | else => { |
| 141 | 141 | fail = true; |
| 142 | 142 | if (@errorReturnTrace()) |trace| { |
| 143 | std.debug.dumpStackTrace(trace.*); | |
| 143 | std.debug.dumpStackTrace(trace); | |
| 144 | 144 | } |
| 145 | 145 | }, |
| 146 | 146 | }; |
| ... | ... | @@ -182,7 +182,7 @@ fn mainServer() !void { |
| 182 | 182 | error.SkipZigTest => return, |
| 183 | 183 | else => { |
| 184 | 184 | if (@errorReturnTrace()) |trace| { |
| 185 | std.debug.dumpStackTrace(trace.*); | |
| 185 | std.debug.dumpStackTrace(trace); | |
| 186 | 186 | } |
| 187 | 187 | std.debug.print("failed with error.{t}\n", .{err}); |
| 188 | 188 | std.process.exit(1); |
| ... | ... | @@ -261,7 +261,7 @@ fn mainTerminal() void { |
| 261 | 261 | std.debug.print("FAIL ({t})\n", .{err}); |
| 262 | 262 | } |
| 263 | 263 | if (@errorReturnTrace()) |trace| { |
| 264 | std.debug.dumpStackTrace(trace.*); | |
| 264 | std.debug.dumpStackTrace(trace); | |
| 265 | 265 | } |
| 266 | 266 | test_node.end(); |
| 267 | 267 | }, |
| ... | ... | @@ -398,7 +398,7 @@ pub fn fuzz( |
| 398 | 398 | error.SkipZigTest => return, |
| 399 | 399 | else => { |
| 400 | 400 | std.debug.lockStdErr(); |
| 401 | if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace.*); | |
| 401 | if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace); | |
| 402 | 402 | std.debug.print("failed with error.{t}\n", .{err}); |
| 403 | 403 | std.process.exit(1); |
| 404 | 404 | }, |
lib/std/Build.zig+1-1| ... | ... | @@ -2195,7 +2195,7 @@ fn dependencyInner( |
| 2195 | 2195 | sub_builder.runBuild(bz) catch @panic("unhandled error"); |
| 2196 | 2196 | |
| 2197 | 2197 | if (sub_builder.validateUserInputDidItFail()) { |
| 2198 | std.debug.dumpCurrentStackTrace(@returnAddress()); | |
| 2198 | std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() }); | |
| 2199 | 2199 | } |
| 2200 | 2200 | } |
| 2201 | 2201 |
lib/std/Build/Step.zig+5-33| ... | ... | @@ -60,7 +60,7 @@ test_results: TestResults, |
| 60 | 60 | |
| 61 | 61 | /// The return address associated with creation of this step that can be useful |
| 62 | 62 | /// to print along with debugging messages. |
| 63 | debug_stack_trace: []usize, | |
| 63 | debug_stack_trace: std.builtin.StackTrace, | |
| 64 | 64 | |
| 65 | 65 | pub const TestResults = struct { |
| 66 | 66 | fail_count: u32 = 0, |
| ... | ... | @@ -220,16 +220,9 @@ pub fn init(options: StepOptions) Step { |
| 220 | 220 | .state = .precheck_unstarted, |
| 221 | 221 | .max_rss = options.max_rss, |
| 222 | 222 | .debug_stack_trace = blk: { |
| 223 | if (!std.debug.sys_can_stack_trace) break :blk &.{}; | |
| 224 | const addresses = arena.alloc(usize, options.owner.debug_stack_frames_count) catch @panic("OOM"); | |
| 225 | @memset(addresses, 0); | |
| 223 | const addr_buf = arena.alloc(usize, options.owner.debug_stack_frames_count) catch @panic("OOM"); | |
| 226 | 224 | const first_ret_addr = options.first_ret_addr orelse @returnAddress(); |
| 227 | var stack_trace = std.builtin.StackTrace{ | |
| 228 | .instruction_addresses = addresses, | |
| 229 | .index = 0, | |
| 230 | }; | |
| 231 | std.debug.captureStackTrace(first_ret_addr, &stack_trace); | |
| 232 | break :blk addresses; | |
| 225 | break :blk std.debug.captureCurrentStackTrace(.{ .first_address = first_ret_addr }, addr_buf); | |
| 233 | 226 | }, |
| 234 | 227 | .result_error_msgs = .{}, |
| 235 | 228 | .result_error_bundle = std.zig.ErrorBundle.empty, |
| ... | ... | @@ -282,18 +275,6 @@ pub fn dependOn(step: *Step, other: *Step) void { |
| 282 | 275 | step.dependencies.append(other) catch @panic("OOM"); |
| 283 | 276 | } |
| 284 | 277 | |
| 285 | pub fn getStackTrace(s: *Step) ?std.builtin.StackTrace { | |
| 286 | var len: usize = 0; | |
| 287 | while (len < s.debug_stack_trace.len and s.debug_stack_trace[len] != 0) { | |
| 288 | len += 1; | |
| 289 | } | |
| 290 | ||
| 291 | return if (len == 0) null else .{ | |
| 292 | .instruction_addresses = s.debug_stack_trace, | |
| 293 | .index = len, | |
| 294 | }; | |
| 295 | } | |
| 296 | ||
| 297 | 278 | fn makeNoOp(step: *Step, options: MakeOptions) anyerror!void { |
| 298 | 279 | _ = options; |
| 299 | 280 | |
| ... | ... | @@ -315,18 +296,9 @@ pub fn cast(step: *Step, comptime T: type) ?*T { |
| 315 | 296 | |
| 316 | 297 | /// For debugging purposes, prints identifying information about this Step. |
| 317 | 298 | pub fn dump(step: *Step, w: *std.Io.Writer, tty_config: std.Io.tty.Config) void { |
| 318 | const debug_info = std.debug.getSelfDebugInfo() catch |err| { | |
| 319 | w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{ | |
| 320 | @errorName(err), | |
| 321 | }) catch {}; | |
| 322 | return; | |
| 323 | }; | |
| 324 | if (step.getStackTrace()) |stack_trace| { | |
| 299 | if (step.debug_stack_trace.instruction_addresses.len > 0) { | |
| 325 | 300 | w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {}; |
| 326 | std.debug.writeStackTrace(stack_trace, w, debug_info, tty_config) catch |err| { | |
| 327 | w.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch {}; | |
| 328 | return; | |
| 329 | }; | |
| 301 | std.debug.writeStackTrace(&step.debug_stack_trace, w, tty_config) catch {}; | |
| 330 | 302 | } else { |
| 331 | 303 | const field = "debug_stack_frames_count"; |
| 332 | 304 | comptime assert(@hasField(Build, field)); |
lib/std/Build/Step/CheckObject.zig+16-22| ... | ... | @@ -1097,16 +1097,10 @@ const MachODumper = struct { |
| 1097 | 1097 | |
| 1098 | 1098 | for (ctx.symtab.items) |sym| { |
| 1099 | 1099 | const sym_name = ctx.getString(sym.n_strx); |
| 1100 | if (sym.stab()) { | |
| 1101 | const tt = switch (sym.n_type) { | |
| 1102 | macho.N_SO => "SO", | |
| 1103 | macho.N_OSO => "OSO", | |
| 1104 | macho.N_BNSYM => "BNSYM", | |
| 1105 | macho.N_ENSYM => "ENSYM", | |
| 1106 | macho.N_FUN => "FUN", | |
| 1107 | macho.N_GSYM => "GSYM", | |
| 1108 | macho.N_STSYM => "STSYM", | |
| 1109 | else => "UNKNOWN STAB", | |
| 1100 | if (sym.n_type.bits.is_stab != 0) { | |
| 1101 | const tt = switch (sym.n_type.stab) { | |
| 1102 | _ => "UNKNOWN STAB", | |
| 1103 | else => @tagName(sym.n_type.stab), | |
| 1110 | 1104 | }; |
| 1111 | 1105 | try writer.print("{x}", .{sym.n_value}); |
| 1112 | 1106 | if (sym.n_sect > 0) { |
| ... | ... | @@ -1114,27 +1108,27 @@ const MachODumper = struct { |
| 1114 | 1108 | try writer.print(" ({s},{s})", .{ sect.segName(), sect.sectName() }); |
| 1115 | 1109 | } |
| 1116 | 1110 | try writer.print(" {s} (stab) {s}\n", .{ tt, sym_name }); |
| 1117 | } else if (sym.sect()) { | |
| 1111 | } else if (sym.n_type.bits.type == .sect) { | |
| 1118 | 1112 | const sect = ctx.sections.items[sym.n_sect - 1]; |
| 1119 | 1113 | try writer.print("{x} ({s},{s})", .{ |
| 1120 | 1114 | sym.n_value, |
| 1121 | 1115 | sect.segName(), |
| 1122 | 1116 | sect.sectName(), |
| 1123 | 1117 | }); |
| 1124 | if (sym.n_desc & macho.REFERENCED_DYNAMICALLY != 0) try writer.writeAll(" [referenced dynamically]"); | |
| 1125 | if (sym.weakDef()) try writer.writeAll(" weak"); | |
| 1126 | if (sym.weakRef()) try writer.writeAll(" weakref"); | |
| 1127 | if (sym.ext()) { | |
| 1128 | if (sym.pext()) try writer.writeAll(" private"); | |
| 1118 | if (sym.n_desc.referenced_dynamically) try writer.writeAll(" [referenced dynamically]"); | |
| 1119 | if (sym.n_desc.weak_def_or_ref_to_weak) try writer.writeAll(" weak"); | |
| 1120 | if (sym.n_desc.weak_ref) try writer.writeAll(" weakref"); | |
| 1121 | if (sym.n_type.bits.ext) { | |
| 1122 | if (sym.n_type.bits.pext) try writer.writeAll(" private"); | |
| 1129 | 1123 | try writer.writeAll(" external"); |
| 1130 | } else if (sym.pext()) try writer.writeAll(" (was private external)"); | |
| 1124 | } else if (sym.n_type.bits.pext) try writer.writeAll(" (was private external)"); | |
| 1131 | 1125 | try writer.print(" {s}\n", .{sym_name}); |
| 1132 | 1126 | } else if (sym.tentative()) { |
| 1133 | const alignment = (sym.n_desc >> 8) & 0x0F; | |
| 1127 | const alignment = (@as(u16, @bitCast(sym.n_desc)) >> 8) & 0x0F; | |
| 1134 | 1128 | try writer.print(" 0x{x:0>16} (common) (alignment 2^{d})", .{ sym.n_value, alignment }); |
| 1135 | if (sym.ext()) try writer.writeAll(" external"); | |
| 1129 | if (sym.n_type.bits.ext) try writer.writeAll(" external"); | |
| 1136 | 1130 | try writer.print(" {s}\n", .{sym_name}); |
| 1137 | } else if (sym.undf()) { | |
| 1131 | } else if (sym.n_type.bits.type == .undf) { | |
| 1138 | 1132 | const ordinal = @divFloor(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER); |
| 1139 | 1133 | const import_name = blk: { |
| 1140 | 1134 | if (ordinal <= 0) { |
| ... | ... | @@ -1153,8 +1147,8 @@ const MachODumper = struct { |
| 1153 | 1147 | break :blk basename[0..ext]; |
| 1154 | 1148 | }; |
| 1155 | 1149 | try writer.writeAll("(undefined)"); |
| 1156 | if (sym.weakRef()) try writer.writeAll(" weakref"); | |
| 1157 | if (sym.ext()) try writer.writeAll(" external"); | |
| 1150 | if (sym.n_desc.weak_ref) try writer.writeAll(" weakref"); | |
| 1151 | if (sym.n_type.bits.ext) try writer.writeAll(" external"); | |
| 1158 | 1152 | try writer.print(" {s} (from {s})\n", .{ |
| 1159 | 1153 | sym_name, |
| 1160 | 1154 | import_name, |
lib/std/Thread.zig+1-1| ... | ... | @@ -530,7 +530,7 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) { |
| 530 | 530 | @call(.auto, f, args) catch |err| { |
| 531 | 531 | std.debug.print("error: {s}\n", .{@errorName(err)}); |
| 532 | 532 | if (@errorReturnTrace()) |trace| { |
| 533 | std.debug.dumpStackTrace(trace.*); | |
| 533 | std.debug.dumpStackTrace(trace); | |
| 534 | 534 | } |
| 535 | 535 | }; |
| 536 | 536 |
lib/std/builtin.zig+5-8| ... | ... | @@ -38,20 +38,17 @@ pub const StackTrace = struct { |
| 38 | 38 | index: usize, |
| 39 | 39 | instruction_addresses: []usize, |
| 40 | 40 | |
| 41 | pub fn format(self: StackTrace, writer: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 41 | pub fn format(st: *const StackTrace, writer: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 42 | 42 | // TODO: re-evaluate whether to use format() methods at all. |
| 43 | 43 | // Until then, avoid an error when using GeneralPurposeAllocator with WebAssembly |
| 44 | 44 | // where it tries to call detectTTYConfig here. |
| 45 | 45 | if (builtin.os.tag == .freestanding) return; |
| 46 | 46 | |
| 47 | const debug_info = std.debug.getSelfDebugInfo() catch |err| { | |
| 48 | return writer.print("\nUnable to print stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); | |
| 49 | }; | |
| 50 | const tty_config = std.Io.tty.detectConfig(std.fs.File.stderr()); | |
| 47 | // TODO: why on earth are we using stderr's ttyconfig? | |
| 48 | // If we want colored output, we should just make a formatter out of `writeStackTrace`. | |
| 49 | const tty_config = std.Io.tty.detectConfig(.stderr()); | |
| 51 | 50 | try writer.writeAll("\n"); |
| 52 | std.debug.writeStackTrace(self, writer, debug_info, tty_config) catch |err| { | |
| 53 | try writer.print("Unable to print stack trace: {s}\n", .{@errorName(err)}); | |
| 54 | }; | |
| 51 | try std.debug.writeStackTrace(st, writer, tty_config); | |
| 55 | 52 | } |
| 56 | 53 | }; |
| 57 | 54 |
lib/std/c.zig+3-207| ... | ... | @@ -7035,205 +7035,6 @@ pub const timezone = switch (native_os) { |
| 7035 | 7035 | else => void, |
| 7036 | 7036 | }; |
| 7037 | 7037 | |
| 7038 | pub const ucontext_t = switch (native_os) { | |
| 7039 | .linux => linux.ucontext_t, // std.os.linux.ucontext_t is currently glibc-compatible, but it should probably not be. | |
| 7040 | .emscripten => emscripten.ucontext_t, | |
| 7041 | .macos, .ios, .tvos, .watchos, .visionos => extern struct { | |
| 7042 | onstack: c_int, | |
| 7043 | sigmask: sigset_t, | |
| 7044 | stack: stack_t, | |
| 7045 | link: ?*ucontext_t, | |
| 7046 | mcsize: u64, | |
| 7047 | mcontext: *mcontext_t, | |
| 7048 | __mcontext_data: mcontext_t, | |
| 7049 | }, | |
| 7050 | .freebsd => extern struct { | |
| 7051 | sigmask: sigset_t, | |
| 7052 | mcontext: mcontext_t, | |
| 7053 | link: ?*ucontext_t, | |
| 7054 | stack: stack_t, | |
| 7055 | flags: c_int, | |
| 7056 | __spare__: [4]c_int, | |
| 7057 | }, | |
| 7058 | .solaris, .illumos => extern struct { | |
| 7059 | flags: u64, | |
| 7060 | link: ?*ucontext_t, | |
| 7061 | sigmask: sigset_t, | |
| 7062 | stack: stack_t, | |
| 7063 | mcontext: mcontext_t, | |
| 7064 | brand_data: [3]?*anyopaque, | |
| 7065 | filler: [2]i64, | |
| 7066 | }, | |
| 7067 | .netbsd => extern struct { | |
| 7068 | flags: u32, | |
| 7069 | link: ?*ucontext_t, | |
| 7070 | sigmask: sigset_t, | |
| 7071 | stack: stack_t, | |
| 7072 | mcontext: mcontext_t, | |
| 7073 | __pad: [ | |
| 7074 | switch (builtin.cpu.arch) { | |
| 7075 | .x86 => 4, | |
| 7076 | .mips, .mipsel, .mips64, .mips64el => 14, | |
| 7077 | .arm, .armeb, .thumb, .thumbeb => 1, | |
| 7078 | .sparc, .sparc64 => if (@sizeOf(usize) == 4) 43 else 8, | |
| 7079 | else => 0, | |
| 7080 | } | |
| 7081 | ]u32, | |
| 7082 | }, | |
| 7083 | .dragonfly => extern struct { | |
| 7084 | sigmask: sigset_t, | |
| 7085 | mcontext: mcontext_t, | |
| 7086 | link: ?*ucontext_t, | |
| 7087 | stack: stack_t, | |
| 7088 | cofunc: ?*fn (?*ucontext_t, ?*anyopaque) void, | |
| 7089 | arg: ?*void, | |
| 7090 | _spare: [4]c_int, | |
| 7091 | }, | |
| 7092 | // https://github.com/SerenityOS/serenity/blob/87eac0e424cff4a1f941fb704b9362a08654c24d/Kernel/API/POSIX/ucontext.h#L19-L24 | |
| 7093 | .haiku, .serenity => extern struct { | |
| 7094 | link: ?*ucontext_t, | |
| 7095 | sigmask: sigset_t, | |
| 7096 | stack: stack_t, | |
| 7097 | mcontext: mcontext_t, | |
| 7098 | }, | |
| 7099 | .openbsd => openbsd.ucontext_t, | |
| 7100 | else => void, | |
| 7101 | }; | |
| 7102 | pub const mcontext_t = switch (native_os) { | |
| 7103 | .linux => linux.mcontext_t, | |
| 7104 | .emscripten => emscripten.mcontext_t, | |
| 7105 | .macos, .ios, .tvos, .watchos, .visionos => darwin.mcontext_t, | |
| 7106 | .freebsd => switch (builtin.cpu.arch) { | |
| 7107 | .x86_64 => extern struct { | |
| 7108 | onstack: u64, | |
| 7109 | rdi: u64, | |
| 7110 | rsi: u64, | |
| 7111 | rdx: u64, | |
| 7112 | rcx: u64, | |
| 7113 | r8: u64, | |
| 7114 | r9: u64, | |
| 7115 | rax: u64, | |
| 7116 | rbx: u64, | |
| 7117 | rbp: u64, | |
| 7118 | r10: u64, | |
| 7119 | r11: u64, | |
| 7120 | r12: u64, | |
| 7121 | r13: u64, | |
| 7122 | r14: u64, | |
| 7123 | r15: u64, | |
| 7124 | trapno: u32, | |
| 7125 | fs: u16, | |
| 7126 | gs: u16, | |
| 7127 | addr: u64, | |
| 7128 | flags: u32, | |
| 7129 | es: u16, | |
| 7130 | ds: u16, | |
| 7131 | err: u64, | |
| 7132 | rip: u64, | |
| 7133 | cs: u64, | |
| 7134 | rflags: u64, | |
| 7135 | rsp: u64, | |
| 7136 | ss: u64, | |
| 7137 | len: u64, | |
| 7138 | fpformat: u64, | |
| 7139 | ownedfp: u64, | |
| 7140 | fpstate: [64]u64 align(16), | |
| 7141 | fsbase: u64, | |
| 7142 | gsbase: u64, | |
| 7143 | xfpustate: u64, | |
| 7144 | xfpustate_len: u64, | |
| 7145 | spare: [4]u64, | |
| 7146 | }, | |
| 7147 | .aarch64 => extern struct { | |
| 7148 | gpregs: extern struct { | |
| 7149 | x: [30]u64, | |
| 7150 | lr: u64, | |
| 7151 | sp: u64, | |
| 7152 | elr: u64, | |
| 7153 | spsr: u32, | |
| 7154 | _pad: u32, | |
| 7155 | }, | |
| 7156 | fpregs: extern struct { | |
| 7157 | q: [32]u128, | |
| 7158 | sr: u32, | |
| 7159 | cr: u32, | |
| 7160 | flags: u32, | |
| 7161 | _pad: u32, | |
| 7162 | }, | |
| 7163 | flags: u32, | |
| 7164 | _pad: u32, | |
| 7165 | _spare: [8]u64, | |
| 7166 | }, | |
| 7167 | else => struct {}, | |
| 7168 | }, | |
| 7169 | .solaris, .illumos => extern struct { | |
| 7170 | gregs: [28]u64, | |
| 7171 | fpregs: solaris.fpregset_t, | |
| 7172 | }, | |
| 7173 | .netbsd => switch (builtin.cpu.arch) { | |
| 7174 | .aarch64, .aarch64_be => extern struct { | |
| 7175 | gregs: [35]u64, | |
| 7176 | fregs: [528]u8 align(16), | |
| 7177 | spare: [8]u64, | |
| 7178 | }, | |
| 7179 | .x86 => extern struct { | |
| 7180 | gregs: [19]u32, | |
| 7181 | fpregs: [161]u32, | |
| 7182 | mc_tlsbase: u32, | |
| 7183 | }, | |
| 7184 | .x86_64 => extern struct { | |
| 7185 | gregs: [26]u64, | |
| 7186 | mc_tlsbase: u64, | |
| 7187 | fpregs: [512]u8 align(8), | |
| 7188 | }, | |
| 7189 | else => struct {}, | |
| 7190 | }, | |
| 7191 | .dragonfly => dragonfly.mcontext_t, | |
| 7192 | .haiku => haiku.mcontext_t, | |
| 7193 | .serenity => switch (native_arch) { | |
| 7194 | // https://github.com/SerenityOS/serenity/blob/200e91cd7f1ec5453799a2720d4dc114a59cc289/Kernel/Arch/aarch64/mcontext.h#L15-L19 | |
| 7195 | .aarch64 => extern struct { | |
| 7196 | x: [31]u64, | |
| 7197 | sp: u64, | |
| 7198 | pc: u64, | |
| 7199 | }, | |
| 7200 | // https://github.com/SerenityOS/serenity/blob/66f8d0f031ef25c409dbb4fecaa454800fecae0f/Kernel/Arch/riscv64/mcontext.h#L15-L18 | |
| 7201 | .riscv64 => extern struct { | |
| 7202 | x: [31]u64, | |
| 7203 | pc: u64, | |
| 7204 | }, | |
| 7205 | // https://github.com/SerenityOS/serenity/blob/7b9ea3efdec9f86a1042893e8107d0b23aad8727/Kernel/Arch/x86_64/mcontext.h#L15-L40 | |
| 7206 | .x86_64 => extern struct { | |
| 7207 | rax: u64, | |
| 7208 | rcx: u64, | |
| 7209 | rdx: u64, | |
| 7210 | rbx: u64, | |
| 7211 | rsp: u64, | |
| 7212 | rbp: u64, | |
| 7213 | rsi: u64, | |
| 7214 | rdi: u64, | |
| 7215 | rip: u64, | |
| 7216 | r8: u64, | |
| 7217 | r9: u64, | |
| 7218 | r10: u64, | |
| 7219 | r11: u64, | |
| 7220 | r12: u64, | |
| 7221 | r13: u64, | |
| 7222 | r14: u64, | |
| 7223 | r15: u64, | |
| 7224 | rflags: u64, | |
| 7225 | cs: u32, | |
| 7226 | ss: u32, | |
| 7227 | ds: u32, | |
| 7228 | es: u32, | |
| 7229 | fs: u32, | |
| 7230 | gs: u32, | |
| 7231 | }, | |
| 7232 | else => struct {}, | |
| 7233 | }, | |
| 7234 | else => void, | |
| 7235 | }; | |
| 7236 | ||
| 7237 | 7038 | pub const user_desc = switch (native_os) { |
| 7238 | 7039 | .linux => linux.user_desc, |
| 7239 | 7040 | else => void, |
| ... | ... | @@ -11193,6 +10994,9 @@ pub extern "c" fn dlclose(handle: *anyopaque) c_int; |
| 11193 | 10994 | pub extern "c" fn dlsym(handle: ?*anyopaque, symbol: [*:0]const u8) ?*anyopaque; |
| 11194 | 10995 | pub extern "c" fn dlerror() ?[*:0]u8; |
| 11195 | 10996 | |
| 10997 | pub const dladdr = if (native_os.isDarwin()) darwin.dladdr else {}; | |
| 10998 | pub const dl_info = if (native_os.isDarwin()) darwin.dl_info else {}; | |
| 10999 | ||
| 11196 | 11000 | pub extern "c" fn sync() void; |
| 11197 | 11001 | pub extern "c" fn syncfs(fd: c_int) c_int; |
| 11198 | 11002 | pub extern "c" fn fsync(fd: c_int) c_int; |
| ... | ... | @@ -11238,13 +11042,6 @@ pub const LC = enum(c_int) { |
| 11238 | 11042 | |
| 11239 | 11043 | pub extern "c" fn setlocale(category: LC, locale: ?[*:0]const u8) ?[*:0]const u8; |
| 11240 | 11044 | |
| 11241 | pub const getcontext = if (builtin.target.abi.isAndroid() or builtin.target.os.tag == .openbsd or builtin.target.os.tag == .haiku) | |
| 11242 | {} // libc does not implement getcontext | |
| 11243 | else if (native_os == .linux and builtin.target.abi.isMusl()) | |
| 11244 | linux.getcontext | |
| 11245 | else | |
| 11246 | private.getcontext; | |
| 11247 | ||
| 11248 | 11045 | pub const max_align_t = if (native_abi == .msvc or native_abi == .itanium) |
| 11249 | 11046 | f64 |
| 11250 | 11047 | else if (native_os.isDarwin()) |
| ... | ... | @@ -11668,7 +11465,6 @@ const private = struct { |
| 11668 | 11465 | extern "c" fn shm_open(name: [*:0]const u8, flag: c_int, mode: mode_t) c_int; |
| 11669 | 11466 | |
| 11670 | 11467 | extern "c" fn pthread_setname_np(thread: pthread_t, name: [*:0]const u8) c_int; |
| 11671 | extern "c" fn getcontext(ucp: *ucontext_t) c_int; | |
| 11672 | 11468 | |
| 11673 | 11469 | extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize; |
| 11674 | 11470 | extern "c" fn getentropy(buffer: [*]u8, size: usize) c_int; |
lib/std/c/darwin.zig+8-101| ... | ... | @@ -348,113 +348,20 @@ pub const VM = struct { |
| 348 | 348 | |
| 349 | 349 | pub const exception_type_t = c_int; |
| 350 | 350 | |
| 351 | pub const mcontext_t = switch (native_arch) { | |
| 352 | .aarch64 => extern struct { | |
| 353 | es: exception_state, | |
| 354 | ss: thread_state, | |
| 355 | ns: neon_state, | |
| 356 | }, | |
| 357 | .x86_64 => extern struct { | |
| 358 | es: exception_state, | |
| 359 | ss: thread_state, | |
| 360 | fs: float_state, | |
| 361 | }, | |
| 362 | else => @compileError("unsupported arch"), | |
| 363 | }; | |
| 364 | ||
| 365 | pub const exception_state = switch (native_arch) { | |
| 366 | .aarch64 => extern struct { | |
| 367 | far: u64, // Virtual Fault Address | |
| 368 | esr: u32, // Exception syndrome | |
| 369 | exception: u32, // Number of arm exception taken | |
| 370 | }, | |
| 371 | .x86_64 => extern struct { | |
| 372 | trapno: u16, | |
| 373 | cpu: u16, | |
| 374 | err: u32, | |
| 375 | faultvaddr: u64, | |
| 376 | }, | |
| 377 | else => @compileError("unsupported arch"), | |
| 378 | }; | |
| 379 | ||
| 380 | pub const thread_state = switch (native_arch) { | |
| 381 | .aarch64 => extern struct { | |
| 382 | /// General purpose registers | |
| 383 | regs: [29]u64, | |
| 384 | /// Frame pointer x29 | |
| 385 | fp: u64, | |
| 386 | /// Link register x30 | |
| 387 | lr: u64, | |
| 388 | /// Stack pointer x31 | |
| 389 | sp: u64, | |
| 390 | /// Program counter | |
| 391 | pc: u64, | |
| 392 | /// Current program status register | |
| 393 | cpsr: u32, | |
| 394 | __pad: u32, | |
| 395 | }, | |
| 396 | .x86_64 => extern struct { | |
| 397 | rax: u64, | |
| 398 | rbx: u64, | |
| 399 | rcx: u64, | |
| 400 | rdx: u64, | |
| 401 | rdi: u64, | |
| 402 | rsi: u64, | |
| 403 | rbp: u64, | |
| 404 | rsp: u64, | |
| 405 | r8: u64, | |
| 406 | r9: u64, | |
| 407 | r10: u64, | |
| 408 | r11: u64, | |
| 409 | r12: u64, | |
| 410 | r13: u64, | |
| 411 | r14: u64, | |
| 412 | r15: u64, | |
| 413 | rip: u64, | |
| 414 | rflags: u64, | |
| 415 | cs: u64, | |
| 416 | fs: u64, | |
| 417 | gs: u64, | |
| 418 | }, | |
| 419 | else => @compileError("unsupported arch"), | |
| 420 | }; | |
| 421 | ||
| 422 | pub const neon_state = extern struct { | |
| 423 | q: [32]u128, | |
| 424 | fpsr: u32, | |
| 425 | fpcr: u32, | |
| 426 | }; | |
| 427 | ||
| 428 | pub const float_state = extern struct { | |
| 429 | reserved: [2]c_int, | |
| 430 | fcw: u16, | |
| 431 | fsw: u16, | |
| 432 | ftw: u8, | |
| 433 | rsrv1: u8, | |
| 434 | fop: u16, | |
| 435 | ip: u32, | |
| 436 | cs: u16, | |
| 437 | rsrv2: u16, | |
| 438 | dp: u32, | |
| 439 | ds: u16, | |
| 440 | rsrv3: u16, | |
| 441 | mxcsr: u32, | |
| 442 | mxcsrmask: u32, | |
| 443 | stmm: [8]stmm_reg, | |
| 444 | xmm: [16]xmm_reg, | |
| 445 | rsrv4: [96]u8, | |
| 446 | reserved1: c_int, | |
| 447 | }; | |
| 448 | ||
| 449 | pub const stmm_reg = [16]u8; | |
| 450 | pub const xmm_reg = [16]u8; | |
| 451 | ||
| 452 | 351 | pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32; |
| 453 | 352 | pub extern "c" fn _NSGetExecutablePath(buf: [*:0]u8, bufsize: *u32) c_int; |
| 454 | 353 | pub extern "c" fn _dyld_image_count() u32; |
| 455 | 354 | pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header; |
| 456 | 355 | pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize; |
| 457 | 356 | pub extern "c" fn _dyld_get_image_name(image_index: u32) [*:0]const u8; |
| 357 | pub extern "c" fn dladdr(addr: *const anyopaque, info: *dl_info) c_int; | |
| 358 | ||
| 359 | pub const dl_info = extern struct { | |
| 360 | fname: [*:0]const u8, | |
| 361 | fbase: *anyopaque, | |
| 362 | sname: ?[*:0]const u8, | |
| 363 | saddr: ?*anyopaque, | |
| 364 | }; | |
| 458 | 365 | |
| 459 | 366 | pub const COPYFILE = packed struct(u32) { |
| 460 | 367 | ACL: bool = false, |
lib/std/c/dragonfly.zig-40| ... | ... | @@ -13,46 +13,6 @@ pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: caddr_t, data: c_int) |
| 13 | 13 | pub extern "c" fn umtx_sleep(ptr: *const volatile c_int, value: c_int, timeout: c_int) c_int; |
| 14 | 14 | pub extern "c" fn umtx_wakeup(ptr: *const volatile c_int, count: c_int) c_int; |
| 15 | 15 | |
| 16 | pub const mcontext_t = extern struct { | |
| 17 | onstack: register_t, // XXX - sigcontext compat. | |
| 18 | rdi: register_t, | |
| 19 | rsi: register_t, | |
| 20 | rdx: register_t, | |
| 21 | rcx: register_t, | |
| 22 | r8: register_t, | |
| 23 | r9: register_t, | |
| 24 | rax: register_t, | |
| 25 | rbx: register_t, | |
| 26 | rbp: register_t, | |
| 27 | r10: register_t, | |
| 28 | r11: register_t, | |
| 29 | r12: register_t, | |
| 30 | r13: register_t, | |
| 31 | r14: register_t, | |
| 32 | r15: register_t, | |
| 33 | xflags: register_t, | |
| 34 | trapno: register_t, | |
| 35 | addr: register_t, | |
| 36 | flags: register_t, | |
| 37 | err: register_t, | |
| 38 | rip: register_t, | |
| 39 | cs: register_t, | |
| 40 | rflags: register_t, | |
| 41 | rsp: register_t, // machine state | |
| 42 | ss: register_t, | |
| 43 | ||
| 44 | len: c_uint, // sizeof(mcontext_t) | |
| 45 | fpformat: c_uint, | |
| 46 | ownedfp: c_uint, | |
| 47 | reserved: c_uint, | |
| 48 | unused: [8]c_uint, | |
| 49 | ||
| 50 | // NOTE! 64-byte aligned as of here. Also must match savefpu structure. | |
| 51 | fpregs: [256]c_int align(64), | |
| 52 | }; | |
| 53 | ||
| 54 | pub const register_t = isize; | |
| 55 | ||
| 56 | 16 | pub const E = enum(u16) { |
| 57 | 17 | /// No error occurred. |
| 58 | 18 | SUCCESS = 0, |
lib/std/c/haiku.zig-263| ... | ... | @@ -273,269 +273,6 @@ pub const E = enum(i32) { |
| 273 | 273 | |
| 274 | 274 | pub const status_t = i32; |
| 275 | 275 | |
| 276 | pub const mcontext_t = switch (builtin.cpu.arch) { | |
| 277 | .arm, .thumb => extern struct { | |
| 278 | r0: u32, | |
| 279 | r1: u32, | |
| 280 | r2: u32, | |
| 281 | r3: u32, | |
| 282 | r4: u32, | |
| 283 | r5: u32, | |
| 284 | r6: u32, | |
| 285 | r7: u32, | |
| 286 | r8: u32, | |
| 287 | r9: u32, | |
| 288 | r10: u32, | |
| 289 | r11: u32, | |
| 290 | r12: u32, | |
| 291 | r13: u32, | |
| 292 | r14: u32, | |
| 293 | r15: u32, | |
| 294 | cpsr: u32, | |
| 295 | }, | |
| 296 | .aarch64 => extern struct { | |
| 297 | x: [10]u64, | |
| 298 | lr: u64, | |
| 299 | sp: u64, | |
| 300 | elr: u64, | |
| 301 | spsr: u64, | |
| 302 | fp_q: [32]u128, | |
| 303 | fpsr: u32, | |
| 304 | fpcr: u32, | |
| 305 | }, | |
| 306 | .m68k => extern struct { | |
| 307 | pc: u32, | |
| 308 | d0: u32, | |
| 309 | d1: u32, | |
| 310 | d2: u32, | |
| 311 | d3: u32, | |
| 312 | d4: u32, | |
| 313 | d5: u32, | |
| 314 | d6: u32, | |
| 315 | d7: u32, | |
| 316 | a0: u32, | |
| 317 | a1: u32, | |
| 318 | a2: u32, | |
| 319 | a3: u32, | |
| 320 | a4: u32, | |
| 321 | a5: u32, | |
| 322 | a6: u32, | |
| 323 | a7: u32, | |
| 324 | ccr: u8, | |
| 325 | f0: f64, | |
| 326 | f1: f64, | |
| 327 | f2: f64, | |
| 328 | f3: f64, | |
| 329 | f4: f64, | |
| 330 | f5: f64, | |
| 331 | f6: f64, | |
| 332 | f7: f64, | |
| 333 | f8: f64, | |
| 334 | f9: f64, | |
| 335 | f10: f64, | |
| 336 | f11: f64, | |
| 337 | f12: f64, | |
| 338 | f13: f64, | |
| 339 | }, | |
| 340 | .mipsel => extern struct { | |
| 341 | r0: u32, | |
| 342 | }, | |
| 343 | .powerpc => extern struct { | |
| 344 | pc: u32, | |
| 345 | r0: u32, | |
| 346 | r1: u32, | |
| 347 | r2: u32, | |
| 348 | r3: u32, | |
| 349 | r4: u32, | |
| 350 | r5: u32, | |
| 351 | r6: u32, | |
| 352 | r7: u32, | |
| 353 | r8: u32, | |
| 354 | r9: u32, | |
| 355 | r10: u32, | |
| 356 | r11: u32, | |
| 357 | r12: u32, | |
| 358 | f0: f64, | |
| 359 | f1: f64, | |
| 360 | f2: f64, | |
| 361 | f3: f64, | |
| 362 | f4: f64, | |
| 363 | f5: f64, | |
| 364 | f6: f64, | |
| 365 | f7: f64, | |
| 366 | f8: f64, | |
| 367 | f9: f64, | |
| 368 | f10: f64, | |
| 369 | f11: f64, | |
| 370 | f12: f64, | |
| 371 | f13: f64, | |
| 372 | reserved: u32, | |
| 373 | fpscr: u32, | |
| 374 | ctr: u32, | |
| 375 | xer: u32, | |
| 376 | cr: u32, | |
| 377 | msr: u32, | |
| 378 | lr: u32, | |
| 379 | }, | |
| 380 | .riscv64 => extern struct { | |
| 381 | x: [31]u64, | |
| 382 | pc: u64, | |
| 383 | f: [32]f64, | |
| 384 | fcsr: u64, | |
| 385 | }, | |
| 386 | .sparc64 => extern struct { | |
| 387 | g1: u64, | |
| 388 | g2: u64, | |
| 389 | g3: u64, | |
| 390 | g4: u64, | |
| 391 | g5: u64, | |
| 392 | g6: u64, | |
| 393 | g7: u64, | |
| 394 | o0: u64, | |
| 395 | o1: u64, | |
| 396 | o2: u64, | |
| 397 | o3: u64, | |
| 398 | o4: u64, | |
| 399 | o5: u64, | |
| 400 | sp: u64, | |
| 401 | o7: u64, | |
| 402 | l0: u64, | |
| 403 | l1: u64, | |
| 404 | l2: u64, | |
| 405 | l3: u64, | |
| 406 | l4: u64, | |
| 407 | l5: u64, | |
| 408 | l6: u64, | |
| 409 | l7: u64, | |
| 410 | i0: u64, | |
| 411 | i1: u64, | |
| 412 | i2: u64, | |
| 413 | i3: u64, | |
| 414 | i4: u64, | |
| 415 | i5: u64, | |
| 416 | fp: u64, | |
| 417 | i7: u64, | |
| 418 | }, | |
| 419 | .x86 => extern struct { | |
| 420 | pub const old_extended_regs = extern struct { | |
| 421 | control: u16, | |
| 422 | reserved1: u16, | |
| 423 | status: u16, | |
| 424 | reserved2: u16, | |
| 425 | tag: u16, | |
| 426 | reserved3: u16, | |
| 427 | eip: u32, | |
| 428 | cs: u16, | |
| 429 | opcode: u16, | |
| 430 | datap: u32, | |
| 431 | ds: u16, | |
| 432 | reserved4: u16, | |
| 433 | fp_mmx: [8][10]u8, | |
| 434 | }; | |
| 435 | ||
| 436 | pub const fp_register = extern struct { value: [10]u8, reserved: [6]u8 }; | |
| 437 | ||
| 438 | pub const xmm_register = extern struct { value: [16]u8 }; | |
| 439 | ||
| 440 | pub const new_extended_regs = extern struct { | |
| 441 | control: u16, | |
| 442 | status: u16, | |
| 443 | tag: u16, | |
| 444 | opcode: u16, | |
| 445 | eip: u32, | |
| 446 | cs: u16, | |
| 447 | reserved1: u16, | |
| 448 | datap: u32, | |
| 449 | ds: u16, | |
| 450 | reserved2: u16, | |
| 451 | mxcsr: u32, | |
| 452 | reserved3: u32, | |
| 453 | fp_mmx: [8]fp_register, | |
| 454 | xmmx: [8]xmm_register, | |
| 455 | reserved4: [224]u8, | |
| 456 | }; | |
| 457 | ||
| 458 | pub const extended_regs = extern struct { | |
| 459 | state: extern union { | |
| 460 | old_format: old_extended_regs, | |
| 461 | new_format: new_extended_regs, | |
| 462 | }, | |
| 463 | format: u32, | |
| 464 | }; | |
| 465 | ||
| 466 | eip: u32, | |
| 467 | eflags: u32, | |
| 468 | eax: u32, | |
| 469 | ecx: u32, | |
| 470 | edx: u32, | |
| 471 | esp: u32, | |
| 472 | ebp: u32, | |
| 473 | reserved: u32, | |
| 474 | xregs: extended_regs, | |
| 475 | edi: u32, | |
| 476 | esi: u32, | |
| 477 | ebx: u32, | |
| 478 | }, | |
| 479 | .x86_64 => extern struct { | |
| 480 | pub const fp_register = extern struct { | |
| 481 | value: [10]u8, | |
| 482 | reserved: [6]u8, | |
| 483 | }; | |
| 484 | ||
| 485 | pub const xmm_register = extern struct { | |
| 486 | value: [16]u8, | |
| 487 | }; | |
| 488 | ||
| 489 | pub const fpu_state = extern struct { | |
| 490 | control: u16, | |
| 491 | status: u16, | |
| 492 | tag: u16, | |
| 493 | opcode: u16, | |
| 494 | rip: u64, | |
| 495 | rdp: u64, | |
| 496 | mxcsr: u32, | |
| 497 | mscsr_mask: u32, | |
| 498 | ||
| 499 | fp_mmx: [8]fp_register, | |
| 500 | xmm: [16]xmm_register, | |
| 501 | reserved: [96]u8, | |
| 502 | }; | |
| 503 | ||
| 504 | pub const xstate_hdr = extern struct { | |
| 505 | bv: u64, | |
| 506 | xcomp_bv: u64, | |
| 507 | reserved: [48]u8, | |
| 508 | }; | |
| 509 | ||
| 510 | pub const savefpu = extern struct { | |
| 511 | fxsave: fpu_state, | |
| 512 | xstate: xstate_hdr, | |
| 513 | ymm: [16]xmm_register, | |
| 514 | }; | |
| 515 | ||
| 516 | rax: u64, | |
| 517 | rbx: u64, | |
| 518 | rcx: u64, | |
| 519 | rdx: u64, | |
| 520 | rdi: u64, | |
| 521 | rsi: u64, | |
| 522 | rbp: u64, | |
| 523 | r8: u64, | |
| 524 | r9: u64, | |
| 525 | r10: u64, | |
| 526 | r11: u64, | |
| 527 | r12: u64, | |
| 528 | r13: u64, | |
| 529 | r14: u64, | |
| 530 | r15: u64, | |
| 531 | rsp: u64, | |
| 532 | rip: u64, | |
| 533 | rflags: u64, | |
| 534 | fpu: savefpu, | |
| 535 | }, | |
| 536 | else => void, | |
| 537 | }; | |
| 538 | ||
| 539 | 276 | pub const DirEnt = extern struct { |
| 540 | 277 | /// device |
| 541 | 278 | dev: dev_t, |
lib/std/c/openbsd.zig-47| ... | ... | @@ -144,53 +144,6 @@ pub const TCIO = enum(u32) { |
| 144 | 144 | ION = 4, |
| 145 | 145 | }; |
| 146 | 146 | |
| 147 | pub const ucontext_t = switch (builtin.cpu.arch) { | |
| 148 | .x86_64 => extern struct { | |
| 149 | sc_rdi: c_long, | |
| 150 | sc_rsi: c_long, | |
| 151 | sc_rdx: c_long, | |
| 152 | sc_rcx: c_long, | |
| 153 | sc_r8: c_long, | |
| 154 | sc_r9: c_long, | |
| 155 | sc_r10: c_long, | |
| 156 | sc_r11: c_long, | |
| 157 | sc_r12: c_long, | |
| 158 | sc_r13: c_long, | |
| 159 | sc_r14: c_long, | |
| 160 | sc_r15: c_long, | |
| 161 | sc_rbp: c_long, | |
| 162 | sc_rbx: c_long, | |
| 163 | sc_rax: c_long, | |
| 164 | sc_gs: c_long, | |
| 165 | sc_fs: c_long, | |
| 166 | sc_es: c_long, | |
| 167 | sc_ds: c_long, | |
| 168 | sc_trapno: c_long, | |
| 169 | sc_err: c_long, | |
| 170 | sc_rip: c_long, | |
| 171 | sc_cs: c_long, | |
| 172 | sc_rflags: c_long, | |
| 173 | sc_rsp: c_long, | |
| 174 | sc_ss: c_long, | |
| 175 | ||
| 176 | sc_fpstate: *anyopaque, // struct fxsave64 * | |
| 177 | __sc_unused: c_int, | |
| 178 | sc_mask: c_int, | |
| 179 | sc_cookie: c_long, | |
| 180 | }, | |
| 181 | .aarch64 => extern struct { | |
| 182 | __sc_unused: c_int, | |
| 183 | sc_mask: c_int, | |
| 184 | sc_sp: c_ulong, | |
| 185 | sc_lr: c_ulong, | |
| 186 | sc_elr: c_ulong, | |
| 187 | sc_spsr: c_ulong, | |
| 188 | sc_x: [30]c_ulong, | |
| 189 | sc_cookie: c_long, | |
| 190 | }, | |
| 191 | else => @compileError("missing ucontext_t type definition"), | |
| 192 | }; | |
| 193 | ||
| 194 | 147 | pub const E = enum(u16) { |
| 195 | 148 | /// No error occurred. |
| 196 | 149 | SUCCESS = 0, |
lib/std/coff.zig+10-9| ... | ... | @@ -1083,26 +1083,27 @@ pub const Coff = struct { |
| 1083 | 1083 | age: u32 = undefined, |
| 1084 | 1084 | |
| 1085 | 1085 | // The lifetime of `data` must be longer than the lifetime of the returned Coff |
| 1086 | pub fn init(data: []const u8, is_loaded: bool) !Coff { | |
| 1086 | pub fn init(data: []const u8, is_loaded: bool) error{ EndOfStream, MissingPEHeader }!Coff { | |
| 1087 | 1087 | const pe_pointer_offset = 0x3C; |
| 1088 | 1088 | const pe_magic = "PE\x00\x00"; |
| 1089 | 1089 | |
| 1090 | var reader: std.Io.Reader = .fixed(data); | |
| 1091 | reader.seek = pe_pointer_offset; | |
| 1092 | const coff_header_offset = try reader.takeInt(u32, .little); | |
| 1093 | reader.seek = coff_header_offset; | |
| 1094 | const is_image = mem.eql(u8, pe_magic, try reader.takeArray(4)); | |
| 1090 | if (data.len < pe_pointer_offset + 4) return error.EndOfStream; | |
| 1091 | const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little); | |
| 1092 | if (data.len < header_offset + 4) return error.EndOfStream; | |
| 1093 | const is_image = mem.eql(u8, data[header_offset..][0..4], pe_magic); | |
| 1095 | 1094 | |
| 1096 | var coff = @This(){ | |
| 1095 | const coff: Coff = .{ | |
| 1097 | 1096 | .data = data, |
| 1098 | 1097 | .is_image = is_image, |
| 1099 | 1098 | .is_loaded = is_loaded, |
| 1100 | .coff_header_offset = coff_header_offset, | |
| 1099 | .coff_header_offset = o: { | |
| 1100 | if (is_image) break :o header_offset + 4; | |
| 1101 | break :o header_offset; | |
| 1102 | }, | |
| 1101 | 1103 | }; |
| 1102 | 1104 | |
| 1103 | 1105 | // Do some basic validation upfront |
| 1104 | 1106 | if (is_image) { |
| 1105 | coff.coff_header_offset = coff.coff_header_offset + 4; | |
| 1106 | 1107 | const coff_header = coff.getCoffHeader(); |
| 1107 | 1108 | if (coff_header.size_of_optional_header == 0) return error.MissingPEHeader; |
| 1108 | 1109 | } |
lib/std/debug.zig+662-799| ... | ... | @@ -1,24 +1,102 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | 1 | const std = @import("std.zig"); |
| 3 | 2 | const math = std.math; |
| 4 | 3 | const mem = std.mem; |
| 5 | 4 | const posix = std.posix; |
| 6 | 5 | const fs = std.fs; |
| 7 | 6 | const testing = std.testing; |
| 8 | const root = @import("root"); | |
| 7 | const Allocator = mem.Allocator; | |
| 9 | 8 | const File = std.fs.File; |
| 10 | 9 | const windows = std.os.windows; |
| 11 | const native_arch = builtin.cpu.arch; | |
| 12 | const native_os = builtin.os.tag; | |
| 13 | const native_endian = native_arch.endian(); | |
| 14 | 10 | const Writer = std.Io.Writer; |
| 15 | 11 | const tty = std.Io.tty; |
| 16 | 12 | |
| 13 | const builtin = @import("builtin"); | |
| 14 | const native_arch = builtin.cpu.arch; | |
| 15 | const native_os = builtin.os.tag; | |
| 16 | ||
| 17 | const root = @import("root"); | |
| 18 | ||
| 17 | 19 | pub const Dwarf = @import("debug/Dwarf.zig"); |
| 18 | 20 | pub const Pdb = @import("debug/Pdb.zig"); |
| 19 | pub const SelfInfo = @import("debug/SelfInfo.zig"); | |
| 21 | pub const ElfFile = @import("debug/ElfFile.zig"); | |
| 20 | 22 | pub const Info = @import("debug/Info.zig"); |
| 21 | 23 | pub const Coverage = @import("debug/Coverage.zig"); |
| 24 | pub const cpu_context = @import("debug/cpu_context.zig"); | |
| 25 | ||
| 26 | /// This type abstracts the target-specific implementation of accessing this process' own debug | |
| 27 | /// information behind a generic interface which supports looking up source locations associated | |
| 28 | /// with addresses, as well as unwinding the stack where a safe mechanism to do so exists. | |
| 29 | /// | |
| 30 | /// The Zig Standard Library provides default implementations of `SelfInfo` for common targets, but | |
| 31 | /// the implementation can be overriden by exposing `root.debug.SelfInfo`. Setting `SelfInfo` to | |
| 32 | /// `void` indicates that the `SelfInfo` API is not supported. | |
| 33 | /// | |
| 34 | /// This type must expose the following declarations: | |
| 35 | /// | |
| 36 | /// ``` | |
| 37 | /// pub const init: SelfInfo; | |
| 38 | /// pub fn deinit(si: *SelfInfo, gpa: Allocator) void; | |
| 39 | /// | |
| 40 | /// /// Returns the symbol and source location of the instruction at `address`. | |
| 41 | /// pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError!Symbol; | |
| 42 | /// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`. | |
| 43 | /// pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError![]const u8; | |
| 44 | /// | |
| 45 | /// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available. | |
| 46 | /// pub const can_unwind: bool; | |
| 47 | /// /// Only required if `can_unwind == true`. | |
| 48 | /// pub const UnwindContext = struct { | |
| 49 | /// /// An address representing the instruction pointer in the last frame. | |
| 50 | /// pc: usize, | |
| 51 | /// | |
| 52 | /// pub fn init(ctx: *cpu_context.Native, gpa: Allocator) Allocator.Error!UnwindContext; | |
| 53 | /// pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void; | |
| 54 | /// /// Returns the frame pointer associated with the last unwound stack frame. | |
| 55 | /// /// If the frame pointer is unknown, 0 may be returned instead. | |
| 56 | /// pub fn getFp(uc: *UnwindContext) usize; | |
| 57 | /// }; | |
| 58 | /// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's | |
| 59 | /// /// return address, or 0 if the end of the stack has been reached. | |
| 60 | /// pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) SelfInfoError!usize; | |
| 61 | /// ``` | |
| 62 | pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo")) | |
| 63 | root.debug.SelfInfo | |
| 64 | else switch (native_os) { | |
| 65 | .linux, | |
| 66 | .netbsd, | |
| 67 | .freebsd, | |
| 68 | .dragonfly, | |
| 69 | .openbsd, | |
| 70 | .solaris, | |
| 71 | .illumos, | |
| 72 | => @import("debug/SelfInfo/Elf.zig"), | |
| 73 | ||
| 74 | .macos, | |
| 75 | .ios, | |
| 76 | .watchos, | |
| 77 | .tvos, | |
| 78 | .visionos, | |
| 79 | => @import("debug/SelfInfo/Darwin.zig"), | |
| 80 | ||
| 81 | .uefi, | |
| 82 | .windows, | |
| 83 | => @import("debug/SelfInfo/Windows.zig"), | |
| 84 | ||
| 85 | else => void, | |
| 86 | }; | |
| 87 | ||
| 88 | pub const SelfInfoError = error{ | |
| 89 | /// The required debug info is invalid or corrupted. | |
| 90 | InvalidDebugInfo, | |
| 91 | /// The required debug info could not be found. | |
| 92 | MissingDebugInfo, | |
| 93 | /// The required debug info was found, and may be valid, but is not supported by this implementation. | |
| 94 | UnsupportedDebugInfo, | |
| 95 | /// The required debug info could not be read from disk due to some IO error. | |
| 96 | ReadFailed, | |
| 97 | OutOfMemory, | |
| 98 | Unexpected, | |
| 99 | }; | |
| 22 | 100 | |
| 23 | 101 | pub const simple_panic = @import("debug/simple_panic.zig"); |
| 24 | 102 | pub const no_panic = @import("debug/no_panic.zig"); |
| ... | ... | @@ -153,9 +231,14 @@ pub const SourceLocation = struct { |
| 153 | 231 | }; |
| 154 | 232 | |
| 155 | 233 | pub const Symbol = struct { |
| 156 | name: []const u8 = "???", | |
| 157 | compile_unit_name: []const u8 = "???", | |
| 158 | source_location: ?SourceLocation = null, | |
| 234 | name: ?[]const u8, | |
| 235 | compile_unit_name: ?[]const u8, | |
| 236 | source_location: ?SourceLocation, | |
| 237 | pub const unknown: Symbol = .{ | |
| 238 | .name = null, | |
| 239 | .compile_unit_name = null, | |
| 240 | .source_location = null, | |
| 241 | }; | |
| 159 | 242 | }; |
| 160 | 243 | |
| 161 | 244 | /// Deprecated because it returns the optimization mode of the standard |
| ... | ... | @@ -166,18 +249,12 @@ pub const runtime_safety = switch (builtin.mode) { |
| 166 | 249 | .ReleaseFast, .ReleaseSmall => false, |
| 167 | 250 | }; |
| 168 | 251 | |
| 252 | /// Whether we can unwind the stack on this target, allowing capturing and/or printing the current | |
| 253 | /// stack trace. It is still legal to call `captureCurrentStackTrace`, `writeCurrentStackTrace`, and | |
| 254 | /// `dumpCurrentStackTrace` if this is `false`; it will just print an error / capture an empty | |
| 255 | /// trace due to missing functionality. This value is just intended as a heuristic to avoid | |
| 256 | /// pointless work e.g. capturing always-empty stack traces. | |
| 169 | 257 | pub const sys_can_stack_trace = switch (builtin.cpu.arch) { |
| 170 | // Observed to go into an infinite loop. | |
| 171 | // TODO: Make this work. | |
| 172 | .loongarch32, | |
| 173 | .loongarch64, | |
| 174 | .mips, | |
| 175 | .mipsel, | |
| 176 | .mips64, | |
| 177 | .mips64el, | |
| 178 | .s390x, | |
| 179 | => false, | |
| 180 | ||
| 181 | 258 | // `@returnAddress()` in LLVM 10 gives |
| 182 | 259 | // "Non-Emscripten WebAssembly hasn't implemented __builtin_return_address". |
| 183 | 260 | // On Emscripten, Zig only supports `@returnAddress()` in debug builds |
| ... | ... | @@ -186,7 +263,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { |
| 186 | 263 | .wasm64, |
| 187 | 264 | => native_os == .emscripten and builtin.mode == .Debug, |
| 188 | 265 | |
| 189 | // `@returnAddress()` is unsupported in LLVM 13. | |
| 266 | // `@returnAddress()` is unsupported in LLVM 21. | |
| 190 | 267 | .bpfel, |
| 191 | 268 | .bpfeb, |
| 192 | 269 | => false, |
| ... | ... | @@ -209,8 +286,12 @@ pub fn unlockStdErr() void { |
| 209 | 286 | /// |
| 210 | 287 | /// During the lock, any `std.Progress` information is cleared from the terminal. |
| 211 | 288 | /// |
| 212 | /// Returns a `Writer` with empty buffer, meaning that it is | |
| 213 | /// in fact unbuffered and does not need to be flushed. | |
| 289 | /// The lock is recursive, so it is valid for the same thread to call `lockStderrWriter` multiple | |
| 290 | /// times. The primary motivation is that this allows the panic handler to safely dump the stack | |
| 291 | /// trace and panic message even if the mutex was held at the panic site. | |
| 292 | /// | |
| 293 | /// The returned `Writer` does not need to be manually flushed: flushing is performed automatically | |
| 294 | /// when the matching `unlockStderrWriter` call occurs. | |
| 214 | 295 | pub fn lockStderrWriter(buffer: []u8) *Writer { |
| 215 | 296 | return std.Progress.lockStderrWriter(buffer); |
| 216 | 297 | } |
| ... | ... | @@ -231,16 +312,13 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { |
| 231 | 312 | nosuspend bw.print(fmt, args) catch return; |
| 232 | 313 | } |
| 233 | 314 | |
| 234 | /// TODO multithreaded awareness | |
| 235 | var self_debug_info: ?SelfInfo = null; | |
| 236 | ||
| 237 | pub fn getSelfDebugInfo() !*SelfInfo { | |
| 238 | if (self_debug_info) |*info| { | |
| 239 | return info; | |
| 240 | } else { | |
| 241 | self_debug_info = try SelfInfo.open(getDebugInfoAllocator()); | |
| 242 | return &self_debug_info.?; | |
| 243 | } | |
| 315 | /// Marked `inline` to propagate a comptime-known error to callers. | |
| 316 | pub inline fn getSelfDebugInfo() !*SelfInfo { | |
| 317 | if (SelfInfo == void) return error.UnsupportedTarget; | |
| 318 | const S = struct { | |
| 319 | var self_info: SelfInfo = .init; | |
| 320 | }; | |
| 321 | return &S.self_info; | |
| 244 | 322 | } |
| 245 | 323 | |
| 246 | 324 | /// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned. |
| ... | ... | @@ -321,226 +399,8 @@ test dumpHexFallible { |
| 321 | 399 | try std.testing.expectEqualStrings(expected, aw.written()); |
| 322 | 400 | } |
| 323 | 401 | |
| 324 | /// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned. | |
| 325 | pub fn dumpCurrentStackTrace(start_addr: ?usize) void { | |
| 326 | const stderr = lockStderrWriter(&.{}); | |
| 327 | defer unlockStderrWriter(); | |
| 328 | nosuspend dumpCurrentStackTraceToWriter(start_addr, stderr) catch return; | |
| 329 | } | |
| 330 | ||
| 331 | /// Prints the current stack trace to the provided writer. | |
| 332 | pub fn dumpCurrentStackTraceToWriter(start_addr: ?usize, writer: *Writer) !void { | |
| 333 | if (builtin.target.cpu.arch.isWasm()) { | |
| 334 | if (native_os == .wasi) { | |
| 335 | try writer.writeAll("Unable to dump stack trace: not implemented for Wasm\n"); | |
| 336 | } | |
| 337 | return; | |
| 338 | } | |
| 339 | if (builtin.strip_debug_info) { | |
| 340 | try writer.writeAll("Unable to dump stack trace: debug info stripped\n"); | |
| 341 | return; | |
| 342 | } | |
| 343 | const debug_info = getSelfDebugInfo() catch |err| { | |
| 344 | try writer.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); | |
| 345 | return; | |
| 346 | }; | |
| 347 | writeCurrentStackTrace(writer, debug_info, tty.detectConfig(.stderr()), start_addr) catch |err| { | |
| 348 | try writer.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}); | |
| 349 | return; | |
| 350 | }; | |
| 351 | } | |
| 352 | ||
| 353 | pub const have_ucontext = posix.ucontext_t != void; | |
| 354 | ||
| 355 | /// Platform-specific thread state. This contains register state, and on some platforms | |
| 356 | /// information about the stack. This is not safe to trivially copy, because some platforms | |
| 357 | /// use internal pointers within this structure. To make a copy, use `copyContext`. | |
| 358 | pub const ThreadContext = blk: { | |
| 359 | if (native_os == .windows) { | |
| 360 | break :blk windows.CONTEXT; | |
| 361 | } else if (have_ucontext) { | |
| 362 | break :blk posix.ucontext_t; | |
| 363 | } else { | |
| 364 | break :blk void; | |
| 365 | } | |
| 366 | }; | |
| 367 | ||
| 368 | /// Copies one context to another, updating any internal pointers | |
| 369 | pub fn copyContext(source: *const ThreadContext, dest: *ThreadContext) void { | |
| 370 | if (!have_ucontext) return {}; | |
| 371 | dest.* = source.*; | |
| 372 | relocateContext(dest); | |
| 373 | } | |
| 374 | ||
| 375 | /// Updates any internal pointers in the context to reflect its current location | |
| 376 | pub fn relocateContext(context: *ThreadContext) void { | |
| 377 | return switch (native_os) { | |
| 378 | .macos => { | |
| 379 | context.mcontext = &context.__mcontext_data; | |
| 380 | }, | |
| 381 | else => {}, | |
| 382 | }; | |
| 383 | } | |
| 384 | ||
| 385 | pub const have_getcontext = @TypeOf(posix.system.getcontext) != void; | |
| 386 | ||
| 387 | /// Capture the current context. The register values in the context will reflect the | |
| 388 | /// state after the platform `getcontext` function returns. | |
| 389 | /// | |
| 390 | /// It is valid to call this if the platform doesn't have context capturing support, | |
| 391 | /// in that case false will be returned. | |
| 392 | pub inline fn getContext(context: *ThreadContext) bool { | |
| 393 | if (native_os == .windows) { | |
| 394 | context.* = std.mem.zeroes(windows.CONTEXT); | |
| 395 | windows.ntdll.RtlCaptureContext(context); | |
| 396 | return true; | |
| 397 | } | |
| 398 | ||
| 399 | const result = have_getcontext and posix.system.getcontext(context) == 0; | |
| 400 | if (native_os == .macos) { | |
| 401 | assert(context.mcsize == @sizeOf(std.c.mcontext_t)); | |
| 402 | ||
| 403 | // On aarch64-macos, the system getcontext doesn't write anything into the pc | |
| 404 | // register slot, it only writes lr. This makes the context consistent with | |
| 405 | // other aarch64 getcontext implementations which write the current lr | |
| 406 | // (where getcontext will return to) into both the lr and pc slot of the context. | |
| 407 | if (native_arch == .aarch64) context.mcontext.ss.pc = context.mcontext.ss.lr; | |
| 408 | } | |
| 409 | ||
| 410 | return result; | |
| 411 | } | |
| 412 | ||
| 413 | /// Tries to print the stack trace starting from the supplied base pointer to stderr, | |
| 414 | /// unbuffered, and ignores any error returned. | |
| 415 | /// TODO multithreaded awareness | |
| 416 | pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void { | |
| 417 | nosuspend { | |
| 418 | if (builtin.target.cpu.arch.isWasm()) { | |
| 419 | if (native_os == .wasi) { | |
| 420 | stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return; | |
| 421 | } | |
| 422 | return; | |
| 423 | } | |
| 424 | if (builtin.strip_debug_info) { | |
| 425 | stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; | |
| 426 | return; | |
| 427 | } | |
| 428 | const debug_info = getSelfDebugInfo() catch |err| { | |
| 429 | stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; | |
| 430 | return; | |
| 431 | }; | |
| 432 | const tty_config = tty.detectConfig(.stderr()); | |
| 433 | if (native_os == .windows) { | |
| 434 | // On x86_64 and aarch64, the stack will be unwound using RtlVirtualUnwind using the context | |
| 435 | // provided by the exception handler. On x86, RtlVirtualUnwind doesn't exist. Instead, a new backtrace | |
| 436 | // will be captured and frames prior to the exception will be filtered. | |
| 437 | // The caveat is that RtlCaptureStackBackTrace does not include the KiUserExceptionDispatcher frame, | |
| 438 | // which is where the IP in `context` points to, so it can't be used as start_addr. | |
| 439 | // Instead, start_addr is recovered from the stack. | |
| 440 | const start_addr = if (builtin.cpu.arch == .x86) @as(*const usize, @ptrFromInt(context.getRegs().bp + 4)).* else null; | |
| 441 | writeStackTraceWindows(stderr, debug_info, tty_config, context, start_addr) catch return; | |
| 442 | return; | |
| 443 | } | |
| 444 | ||
| 445 | var it = StackIterator.initWithContext(null, debug_info, context, @frameAddress()) catch return; | |
| 446 | defer it.deinit(); | |
| 447 | ||
| 448 | // DWARF unwinding on aarch64-macos is not complete so we need to get pc address from mcontext | |
| 449 | const pc_addr = if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64) | |
| 450 | context.mcontext.ss.pc | |
| 451 | else | |
| 452 | it.unwind_state.?.dwarf_context.pc; | |
| 453 | printSourceAtAddress(debug_info, stderr, pc_addr, tty_config) catch return; | |
| 454 | ||
| 455 | while (it.next()) |return_address| { | |
| 456 | printLastUnwindError(&it, debug_info, stderr, tty_config); | |
| 457 | ||
| 458 | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, | |
| 459 | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid | |
| 460 | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this | |
| 461 | // condition on the subsequent iteration and return `null` thus terminating the loop. | |
| 462 | // same behaviour for x86-windows-msvc | |
| 463 | const address = if (return_address == 0) return_address else return_address - 1; | |
| 464 | printSourceAtAddress(debug_info, stderr, address, tty_config) catch return; | |
| 465 | } else printLastUnwindError(&it, debug_info, stderr, tty_config); | |
| 466 | } | |
| 467 | } | |
| 468 | ||
| 469 | /// Returns a slice with the same pointer as addresses, with a potentially smaller len. | |
| 470 | /// On Windows, when first_address is not null, we ask for at least 32 stack frames, | |
| 471 | /// and then try to find the first address. If addresses.len is more than 32, we | |
| 472 | /// capture that many stack frames exactly, and then look for the first address, | |
| 473 | /// chopping off the irrelevant frames and shifting so that the returned addresses pointer | |
| 474 | /// equals the passed in addresses pointer. | |
| 475 | pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackTrace) void { | |
| 476 | if (native_os == .windows) { | |
| 477 | const addrs = stack_trace.instruction_addresses; | |
| 478 | const first_addr = first_address orelse { | |
| 479 | stack_trace.index = walkStackWindows(addrs[0..], null); | |
| 480 | return; | |
| 481 | }; | |
| 482 | var addr_buf_stack: [32]usize = undefined; | |
| 483 | const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs; | |
| 484 | const n = walkStackWindows(addr_buf[0..], null); | |
| 485 | const first_index = for (addr_buf[0..n], 0..) |addr, i| { | |
| 486 | if (addr == first_addr) { | |
| 487 | break i; | |
| 488 | } | |
| 489 | } else { | |
| 490 | stack_trace.index = 0; | |
| 491 | return; | |
| 492 | }; | |
| 493 | const end_index = @min(first_index + addrs.len, n); | |
| 494 | const slice = addr_buf[first_index..end_index]; | |
| 495 | // We use a for loop here because slice and addrs may alias. | |
| 496 | for (slice, 0..) |addr, i| { | |
| 497 | addrs[i] = addr; | |
| 498 | } | |
| 499 | stack_trace.index = slice.len; | |
| 500 | } else { | |
| 501 | // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required). | |
| 502 | // A new path for loading SelfInfo needs to be created which will only attempt to parse in-memory sections, because | |
| 503 | // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding. | |
| 504 | var it = StackIterator.init(first_address, @frameAddress()); | |
| 505 | defer it.deinit(); | |
| 506 | for (stack_trace.instruction_addresses, 0..) |*addr, i| { | |
| 507 | addr.* = it.next() orelse { | |
| 508 | stack_trace.index = i; | |
| 509 | return; | |
| 510 | }; | |
| 511 | } | |
| 512 | stack_trace.index = stack_trace.instruction_addresses.len; | |
| 513 | } | |
| 514 | } | |
| 515 | ||
| 516 | /// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned. | |
| 517 | /// TODO multithreaded awareness | |
| 518 | pub fn dumpStackTrace(stack_trace: std.builtin.StackTrace) void { | |
| 519 | nosuspend { | |
| 520 | if (builtin.target.cpu.arch.isWasm()) { | |
| 521 | if (native_os == .wasi) { | |
| 522 | const stderr = lockStderrWriter(&.{}); | |
| 523 | defer unlockStderrWriter(); | |
| 524 | stderr.writeAll("Unable to dump stack trace: not implemented for Wasm\n") catch return; | |
| 525 | } | |
| 526 | return; | |
| 527 | } | |
| 528 | const stderr = lockStderrWriter(&.{}); | |
| 529 | defer unlockStderrWriter(); | |
| 530 | if (builtin.strip_debug_info) { | |
| 531 | stderr.writeAll("Unable to dump stack trace: debug info stripped\n") catch return; | |
| 532 | return; | |
| 533 | } | |
| 534 | const debug_info = getSelfDebugInfo() catch |err| { | |
| 535 | stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; | |
| 536 | return; | |
| 537 | }; | |
| 538 | writeStackTrace(stack_trace, stderr, debug_info, tty.detectConfig(.stderr())) catch |err| { | |
| 539 | stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; | |
| 540 | return; | |
| 541 | }; | |
| 542 | } | |
| 543 | } | |
| 402 | /// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic. | |
| 403 | pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *const cpu_context.Native; | |
| 544 | 404 | |
| 545 | 405 | /// Invokes detectable illegal behavior when `ok` is `false`. |
| 546 | 406 | /// |
| ... | ... | @@ -579,8 +439,8 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn { |
| 579 | 439 | panicExtra(@returnAddress(), format, args); |
| 580 | 440 | } |
| 581 | 441 | |
| 582 | /// Equivalent to `@panic` but with a formatted message, and with an explicitly | |
| 583 | /// provided return address. | |
| 442 | /// Equivalent to `@panic` but with a formatted message and an explicitly provided return address | |
| 443 | /// which will be the first address in the stack trace. | |
| 584 | 444 | pub fn panicExtra( |
| 585 | 445 | ret_addr: ?usize, |
| 586 | 446 | comptime format: []const u8, |
| ... | ... | @@ -610,6 +470,24 @@ var panicking = std.atomic.Value(u8).init(0); |
| 610 | 470 | /// This is used to catch and handle panics triggered by the panic handler. |
| 611 | 471 | threadlocal var panic_stage: usize = 0; |
| 612 | 472 | |
| 473 | /// For backends that cannot handle the language features depended on by the | |
| 474 | /// default panic handler, we will use a simpler implementation. | |
| 475 | const use_trap_panic = switch (builtin.zig_backend) { | |
| 476 | .stage2_aarch64, | |
| 477 | .stage2_arm, | |
| 478 | .stage2_powerpc, | |
| 479 | .stage2_riscv64, | |
| 480 | .stage2_spirv, | |
| 481 | .stage2_wasm, | |
| 482 | .stage2_x86, | |
| 483 | => true, | |
| 484 | .stage2_x86_64 => switch (builtin.target.ofmt) { | |
| 485 | .elf, .macho => false, | |
| 486 | else => true, | |
| 487 | }, | |
| 488 | else => false, | |
| 489 | }; | |
| 490 | ||
| 613 | 491 | /// Dumps a stack trace to standard error, then aborts. |
| 614 | 492 | pub fn defaultPanic( |
| 615 | 493 | msg: []const u8, |
| ... | ... | @@ -617,8 +495,8 @@ pub fn defaultPanic( |
| 617 | 495 | ) noreturn { |
| 618 | 496 | @branchHint(.cold); |
| 619 | 497 | |
| 620 | // For backends that cannot handle the language features depended on by the | |
| 621 | // default panic handler, we have a simpler panic handler: | |
| 498 | if (use_trap_panic) @trap(); | |
| 499 | ||
| 622 | 500 | switch (builtin.zig_backend) { |
| 623 | 501 | .stage2_aarch64, |
| 624 | 502 | .stage2_arm, |
| ... | ... | @@ -683,41 +561,48 @@ pub fn defaultPanic( |
| 683 | 561 | resetSegfaultHandler(); |
| 684 | 562 | } |
| 685 | 563 | |
| 686 | // Note there is similar logic in handleSegfaultPosix and handleSegfaultWindowsExtra. | |
| 687 | nosuspend switch (panic_stage) { | |
| 564 | // There is very similar logic to the following in `handleSegfault`. | |
| 565 | switch (panic_stage) { | |
| 688 | 566 | 0 => { |
| 689 | 567 | panic_stage = 1; |
| 690 | ||
| 691 | 568 | _ = panicking.fetchAdd(1, .seq_cst); |
| 692 | 569 | |
| 693 | { | |
| 570 | trace: { | |
| 571 | const tty_config = tty.detectConfig(.stderr()); | |
| 572 | ||
| 694 | 573 | const stderr = lockStderrWriter(&.{}); |
| 695 | 574 | defer unlockStderrWriter(); |
| 696 | 575 | |
| 697 | 576 | if (builtin.single_threaded) { |
| 698 | stderr.print("panic: ", .{}) catch posix.abort(); | |
| 577 | stderr.print("panic: ", .{}) catch break :trace; | |
| 699 | 578 | } else { |
| 700 | 579 | const current_thread_id = std.Thread.getCurrentId(); |
| 701 | stderr.print("thread {} panic: ", .{current_thread_id}) catch posix.abort(); | |
| 580 | stderr.print("thread {} panic: ", .{current_thread_id}) catch break :trace; | |
| 702 | 581 | } |
| 703 | stderr.print("{s}\n", .{msg}) catch posix.abort(); | |
| 582 | stderr.print("{s}\n", .{msg}) catch break :trace; | |
| 704 | 583 | |
| 705 | if (@errorReturnTrace()) |t| dumpStackTrace(t.*); | |
| 706 | dumpCurrentStackTraceToWriter(first_trace_addr orelse @returnAddress(), stderr) catch {}; | |
| 584 | if (@errorReturnTrace()) |t| if (t.index > 0) { | |
| 585 | stderr.writeAll("error return context:\n") catch break :trace; | |
| 586 | writeStackTrace(t, stderr, tty_config) catch break :trace; | |
| 587 | stderr.writeAll("\nstack trace:\n") catch break :trace; | |
| 588 | }; | |
| 589 | writeCurrentStackTrace(.{ | |
| 590 | .first_address = first_trace_addr orelse @returnAddress(), | |
| 591 | .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! | |
| 592 | }, stderr, tty_config) catch break :trace; | |
| 707 | 593 | } |
| 708 | 594 | |
| 709 | 595 | waitForOtherThreadToFinishPanicking(); |
| 710 | 596 | }, |
| 711 | 597 | 1 => { |
| 712 | 598 | panic_stage = 2; |
| 713 | ||
| 714 | 599 | // A panic happened while trying to print a previous panic message. |
| 715 | 600 | // We're still holding the mutex but that's fine as we're going to |
| 716 | 601 | // call abort(). |
| 717 | 602 | fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {}; |
| 718 | 603 | }, |
| 719 | 604 | else => {}, // Panicked while printing the recursive panic message. |
| 720 | }; | |
| 605 | } | |
| 721 | 606 | |
| 722 | 607 | posix.abort(); |
| 723 | 608 | } |
| ... | ... | @@ -736,391 +621,417 @@ fn waitForOtherThreadToFinishPanicking() void { |
| 736 | 621 | } |
| 737 | 622 | } |
| 738 | 623 | |
| 739 | pub fn writeStackTrace( | |
| 740 | stack_trace: std.builtin.StackTrace, | |
| 741 | writer: *Writer, | |
| 742 | debug_info: *SelfInfo, | |
| 743 | tty_config: tty.Config, | |
| 744 | ) !void { | |
| 745 | if (builtin.strip_debug_info) return error.MissingDebugInfo; | |
| 746 | var frame_index: usize = 0; | |
| 747 | var frames_left: usize = @min(stack_trace.index, stack_trace.instruction_addresses.len); | |
| 748 | ||
| 749 | while (frames_left != 0) : ({ | |
| 750 | frames_left -= 1; | |
| 751 | frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len; | |
| 752 | }) { | |
| 753 | const return_address = stack_trace.instruction_addresses[frame_index]; | |
| 754 | try printSourceAtAddress(debug_info, writer, return_address - 1, tty_config); | |
| 755 | } | |
| 624 | pub const StackUnwindOptions = struct { | |
| 625 | /// If not `null`, we will ignore all frames up until this return address. This is typically | |
| 626 | /// used to omit intermediate handling code (for instance, a panic handler and its machinery) | |
| 627 | /// from stack traces. | |
| 628 | first_address: ?usize = null, | |
| 629 | /// If not `null`, we will unwind from this `cpu_context.Native` instead of the current top of | |
| 630 | /// the stack. The main use case here is printing stack traces from signal handlers, where the | |
| 631 | /// kernel provides a `*const cpu_context.Native` of the state before the signal. | |
| 632 | context: ?CpuContextPtr = null, | |
| 633 | /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort. | |
| 634 | /// If `false`, only known-safe mechanisms will be attempted. | |
| 635 | allow_unsafe_unwind: bool = false, | |
| 636 | }; | |
| 756 | 637 | |
| 757 | if (stack_trace.index > stack_trace.instruction_addresses.len) { | |
| 758 | const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len; | |
| 638 | /// Capture and return the current stack trace. The returned `StackTrace` stores its addresses in | |
| 639 | /// the given buffer, so `addr_buf` must have a lifetime at least equal to the `StackTrace`. | |
| 640 | /// | |
| 641 | /// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it. | |
| 642 | pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace { | |
| 643 | const empty_trace: std.builtin.StackTrace = .{ .index = 0, .instruction_addresses = &.{} }; | |
| 644 | if (!std.options.allow_stack_tracing) return empty_trace; | |
| 645 | var it = StackIterator.init(options.context) catch return empty_trace; | |
| 646 | defer it.deinit(); | |
| 647 | if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace; | |
| 648 | var total_frames: usize = 0; | |
| 649 | var index: usize = 0; | |
| 650 | var wait_for = options.first_address; | |
| 651 | // Ideally, we would iterate the whole stack so that the `index` in the returned trace was | |
| 652 | // indicative of how many frames were skipped. However, this has a significant runtime cost | |
| 653 | // in some cases, so at least for now, we don't do that. | |
| 654 | while (index < addr_buf.len) switch (it.next()) { | |
| 655 | .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break, | |
| 656 | .end => break, | |
| 657 | .frame => |ret_addr| { | |
| 658 | if (total_frames > 10_000) { | |
| 659 | // Limit the number of frames in case of (e.g.) broken debug information which is | |
| 660 | // getting unwinding stuck in a loop. | |
| 661 | break; | |
| 662 | } | |
| 663 | total_frames += 1; | |
| 664 | if (wait_for) |target| { | |
| 665 | if (ret_addr != target) continue; | |
| 666 | wait_for = null; | |
| 667 | } | |
| 668 | addr_buf[index] = ret_addr; | |
| 669 | index += 1; | |
| 670 | }, | |
| 671 | }; | |
| 672 | return .{ | |
| 673 | .index = index, | |
| 674 | .instruction_addresses = addr_buf[0..index], | |
| 675 | }; | |
| 676 | } | |
| 677 | /// Write the current stack trace to `writer`, annotated with source locations. | |
| 678 | /// | |
| 679 | /// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing. | |
| 680 | pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void { | |
| 681 | if (!std.options.allow_stack_tracing) { | |
| 682 | tty_config.setColor(writer, .dim) catch {}; | |
| 683 | try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); | |
| 684 | tty_config.setColor(writer, .reset) catch {}; | |
| 685 | return; | |
| 686 | } | |
| 687 | const di_gpa = getDebugInfoAllocator(); | |
| 688 | const di = getSelfDebugInfo() catch |err| switch (err) { | |
| 689 | error.UnsupportedTarget => { | |
| 690 | tty_config.setColor(writer, .dim) catch {}; | |
| 691 | try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{}); | |
| 692 | tty_config.setColor(writer, .reset) catch {}; | |
| 693 | return; | |
| 694 | }, | |
| 695 | }; | |
| 696 | var it = StackIterator.init(options.context) catch |err| switch (err) { | |
| 697 | error.CannotUnwindFromContext => { | |
| 698 | tty_config.setColor(writer, .dim) catch {}; | |
| 699 | try writer.print("Cannot print stack trace: context unwind unavailable for target\n", .{}); | |
| 700 | tty_config.setColor(writer, .reset) catch {}; | |
| 701 | return; | |
| 702 | }, | |
| 703 | }; | |
| 704 | defer it.deinit(); | |
| 705 | if (!it.stratOk(options.allow_unsafe_unwind)) { | |
| 706 | tty_config.setColor(writer, .dim) catch {}; | |
| 707 | try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{}); | |
| 708 | tty_config.setColor(writer, .reset) catch {}; | |
| 709 | return; | |
| 710 | } | |
| 711 | var total_frames: usize = 0; | |
| 712 | var wait_for = options.first_address; | |
| 713 | var printed_any_frame = false; | |
| 714 | while (true) switch (it.next()) { | |
| 715 | .switch_to_fp => |unwind_error| { | |
| 716 | if (StackIterator.fp_unwind_is_safe) continue; // no need to even warn | |
| 717 | const module_name = di.getModuleName(di_gpa, unwind_error.address) catch "???"; | |
| 718 | const caption: []const u8 = switch (unwind_error.err) { | |
| 719 | error.MissingDebugInfo => "unwind info unavailable", | |
| 720 | error.InvalidDebugInfo => "unwind info invalid", | |
| 721 | error.UnsupportedDebugInfo => "unwind info unsupported", | |
| 722 | error.ReadFailed => "filesystem error", | |
| 723 | error.OutOfMemory => "out of memory", | |
| 724 | error.Unexpected => "unexpected error", | |
| 725 | }; | |
| 726 | if (it.stratOk(options.allow_unsafe_unwind)) { | |
| 727 | tty_config.setColor(writer, .dim) catch {}; | |
| 728 | try writer.print( | |
| 729 | "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n", | |
| 730 | .{ module_name, unwind_error.address, caption }, | |
| 731 | ); | |
| 732 | tty_config.setColor(writer, .reset) catch {}; | |
| 733 | } else { | |
| 734 | tty_config.setColor(writer, .dim) catch {}; | |
| 735 | try writer.print( | |
| 736 | "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n", | |
| 737 | .{ module_name, unwind_error.address, caption }, | |
| 738 | ); | |
| 739 | tty_config.setColor(writer, .reset) catch {}; | |
| 740 | return; | |
| 741 | } | |
| 742 | }, | |
| 743 | .end => break, | |
| 744 | .frame => |ret_addr| { | |
| 745 | if (total_frames > 10_000) { | |
| 746 | tty_config.setColor(writer, .dim) catch {}; | |
| 747 | try writer.print( | |
| 748 | "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n", | |
| 749 | .{total_frames}, | |
| 750 | ); | |
| 751 | tty_config.setColor(writer, .reset) catch {}; | |
| 752 | return; | |
| 753 | } | |
| 754 | total_frames += 1; | |
| 755 | if (wait_for) |target| { | |
| 756 | if (ret_addr != target) continue; | |
| 757 | wait_for = null; | |
| 758 | } | |
| 759 | // `ret_addr` is the return address, which is *after* the function call. | |
| 760 | // Subtract 1 to get an address *in* the function call for a better source location. | |
| 761 | try printSourceAtAddress(di_gpa, di, writer, ret_addr -| 1, tty_config); | |
| 762 | printed_any_frame = true; | |
| 763 | }, | |
| 764 | }; | |
| 765 | if (!printed_any_frame) return writer.writeAll("(empty stack trace)\n"); | |
| 766 | } | |
| 767 | /// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors. | |
| 768 | pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { | |
| 769 | const tty_config = tty.detectConfig(.stderr()); | |
| 770 | const stderr = lockStderrWriter(&.{}); | |
| 771 | defer unlockStderrWriter(); | |
| 772 | writeCurrentStackTrace(.{ | |
| 773 | .first_address = a: { | |
| 774 | if (options.first_address) |a| break :a a; | |
| 775 | if (options.context != null) break :a null; | |
| 776 | break :a @returnAddress(); // don't include this frame in the trace | |
| 777 | }, | |
| 778 | .context = options.context, | |
| 779 | .allow_unsafe_unwind = options.allow_unsafe_unwind, | |
| 780 | }, stderr, tty_config) catch |err| switch (err) { | |
| 781 | error.WriteFailed => {}, | |
| 782 | }; | |
| 783 | } | |
| 759 | 784 | |
| 785 | /// Write a previously captured stack trace to `writer`, annotated with source locations. | |
| 786 | pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void { | |
| 787 | if (!std.options.allow_stack_tracing) { | |
| 788 | tty_config.setColor(writer, .dim) catch {}; | |
| 789 | try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); | |
| 790 | tty_config.setColor(writer, .reset) catch {}; | |
| 791 | return; | |
| 792 | } | |
| 793 | // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if | |
| 794 | // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace. | |
| 795 | const n_frames = st.index; | |
| 796 | if (n_frames == 0) return writer.writeAll("(empty stack trace)\n"); | |
| 797 | const di_gpa = getDebugInfoAllocator(); | |
| 798 | const di = getSelfDebugInfo() catch |err| switch (err) { | |
| 799 | error.UnsupportedTarget => { | |
| 800 | tty_config.setColor(writer, .dim) catch {}; | |
| 801 | try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{}); | |
| 802 | tty_config.setColor(writer, .reset) catch {}; | |
| 803 | return; | |
| 804 | }, | |
| 805 | }; | |
| 806 | const captured_frames = @min(n_frames, st.instruction_addresses.len); | |
| 807 | for (st.instruction_addresses[0..captured_frames]) |ret_addr| { | |
| 808 | // `ret_addr` is the return address, which is *after* the function call. | |
| 809 | // Subtract 1 to get an address *in* the function call for a better source location. | |
| 810 | try printSourceAtAddress(di_gpa, di, writer, ret_addr -| 1, tty_config); | |
| 811 | } | |
| 812 | if (n_frames > captured_frames) { | |
| 760 | 813 | tty_config.setColor(writer, .bold) catch {}; |
| 761 | try writer.print("({d} additional stack frames skipped...)\n", .{dropped_frames}); | |
| 814 | try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames}); | |
| 762 | 815 | tty_config.setColor(writer, .reset) catch {}; |
| 763 | 816 | } |
| 764 | 817 | } |
| 818 | /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. | |
| 819 | pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void { | |
| 820 | const tty_config = tty.detectConfig(.stderr()); | |
| 821 | const stderr = lockStderrWriter(&.{}); | |
| 822 | defer unlockStderrWriter(); | |
| 823 | writeStackTrace(st, stderr, tty_config) catch |err| switch (err) { | |
| 824 | error.WriteFailed => {}, | |
| 825 | }; | |
| 826 | } | |
| 765 | 827 | |
| 766 | pub const UnwindError = if (have_ucontext) | |
| 767 | @typeInfo(@typeInfo(@TypeOf(StackIterator.next_unwind)).@"fn".return_type.?).error_union.error_set | |
| 768 | else | |
| 769 | void; | |
| 770 | ||
| 771 | pub const StackIterator = struct { | |
| 772 | // Skip every frame before this address is found. | |
| 773 | first_address: ?usize, | |
| 774 | // Last known value of the frame pointer register. | |
| 828 | const StackIterator = union(enum) { | |
| 829 | /// Unwinding using debug info (e.g. DWARF CFI). | |
| 830 | di: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn, | |
| 831 | /// We will first report the *current* PC of this `UnwindContext`, then we will switch to `di`. | |
| 832 | di_first: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn, | |
| 833 | /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable. | |
| 775 | 834 | fp: usize, |
| 776 | 835 | |
| 777 | // When SelfInfo and a register context is available, this iterator can unwind | |
| 778 | // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer), | |
| 779 | // using DWARF and MachO unwind info. | |
| 780 | unwind_state: if (have_ucontext) ?struct { | |
| 781 | debug_info: *SelfInfo, | |
| 782 | dwarf_context: SelfInfo.UnwindContext, | |
| 783 | last_error: ?UnwindError = null, | |
| 784 | failed: bool = false, | |
| 785 | } else void = if (have_ucontext) null else {}, | |
| 786 | ||
| 787 | pub fn init(first_address: ?usize, fp: usize) StackIterator { | |
| 788 | if (native_arch.isSPARC()) { | |
| 836 | /// It is important that this function is marked `inline` so that it can safely use | |
| 837 | /// `@frameAddress` and `cpu_context.Native.current` as the caller's stack frame and | |
| 838 | /// our own are one and the same. | |
| 839 | inline fn init(opt_context_ptr: ?CpuContextPtr) error{CannotUnwindFromContext}!StackIterator { | |
| 840 | if (builtin.cpu.arch.isSPARC()) { | |
| 789 | 841 | // Flush all the register windows on stack. |
| 790 | asm volatile (if (builtin.cpu.has(.sparc, .v9)) | |
| 791 | "flushw" | |
| 792 | else | |
| 793 | "ta 3" // ST_FLUSH_WINDOWS | |
| 794 | ::: .{ .memory = true }); | |
| 795 | } | |
| 796 | ||
| 797 | return .{ | |
| 798 | .first_address = first_address, | |
| 799 | .fp = fp, | |
| 800 | }; | |
| 801 | } | |
| 802 | ||
| 803 | pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t, fp: usize) !StackIterator { | |
| 804 | // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that | |
| 805 | // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder. | |
| 806 | if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64) | |
| 807 | return init(first_address, @truncate(context.mcontext.ss.fp)); | |
| 808 | ||
| 809 | if (SelfInfo.supports_unwinding) { | |
| 810 | var iterator = init(first_address, fp); | |
| 811 | iterator.unwind_state = .{ | |
| 812 | .debug_info = debug_info, | |
| 813 | .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context), | |
| 814 | }; | |
| 815 | return iterator; | |
| 816 | } | |
| 817 | ||
| 818 | return init(first_address, fp); | |
| 819 | } | |
| 820 | ||
| 821 | pub fn deinit(it: *StackIterator) void { | |
| 822 | if (have_ucontext and it.unwind_state != null) it.unwind_state.?.dwarf_context.deinit(); | |
| 823 | } | |
| 824 | ||
| 825 | pub fn getLastError(it: *StackIterator) ?struct { | |
| 826 | err: UnwindError, | |
| 827 | address: usize, | |
| 828 | } { | |
| 829 | if (!have_ucontext) return null; | |
| 830 | if (it.unwind_state) |*unwind_state| { | |
| 831 | if (unwind_state.last_error) |err| { | |
| 832 | unwind_state.last_error = null; | |
| 833 | return .{ | |
| 834 | .err = err, | |
| 835 | .address = unwind_state.dwarf_context.pc, | |
| 836 | }; | |
| 842 | if (builtin.cpu.has(.sparc, .v9)) { | |
| 843 | asm volatile ("flushw" ::: .{ .memory = true }); | |
| 844 | } else { | |
| 845 | asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS | |
| 837 | 846 | } |
| 838 | 847 | } |
| 839 | ||
| 840 | return null; | |
| 841 | } | |
| 842 | ||
| 843 | // Offset of the saved BP wrt the frame pointer. | |
| 844 | const fp_offset = if (native_arch.isRISCV()) | |
| 845 | // On RISC-V the frame pointer points to the top of the saved register | |
| 846 | // area, on pretty much every other architecture it points to the stack | |
| 847 | // slot where the previous frame pointer is saved. | |
| 848 | 2 * @sizeOf(usize) | |
| 849 | else if (native_arch.isSPARC()) | |
| 850 | // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS. | |
| 851 | 14 * @sizeOf(usize) | |
| 852 | else | |
| 853 | 0; | |
| 854 | ||
| 855 | const fp_bias = if (native_arch.isSPARC()) | |
| 856 | // On SPARC frame pointers are biased by a constant. | |
| 857 | 2047 | |
| 858 | else | |
| 859 | 0; | |
| 860 | ||
| 861 | // Positive offset of the saved PC wrt the frame pointer. | |
| 862 | const pc_offset = if (native_arch == .powerpc64le) | |
| 863 | 2 * @sizeOf(usize) | |
| 864 | else | |
| 865 | @sizeOf(usize); | |
| 866 | ||
| 867 | pub fn next(it: *StackIterator) ?usize { | |
| 868 | var address = it.next_internal() orelse return null; | |
| 869 | ||
| 870 | if (it.first_address) |first_address| { | |
| 871 | while (address != first_address) { | |
| 872 | address = it.next_internal() orelse return null; | |
| 873 | } | |
| 874 | it.first_address = null; | |
| 848 | if (opt_context_ptr) |context_ptr| { | |
| 849 | if (SelfInfo == void or !SelfInfo.can_unwind) return error.CannotUnwindFromContext; | |
| 850 | // Use `di_first` here so we report the PC in the context before unwinding any further. | |
| 851 | return .{ .di_first = .init(context_ptr) }; | |
| 875 | 852 | } |
| 876 | ||
| 877 | return address; | |
| 878 | } | |
| 879 | ||
| 880 | fn next_unwind(it: *StackIterator) !usize { | |
| 881 | const unwind_state = &it.unwind_state.?; | |
| 882 | const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc); | |
| 883 | switch (native_os) { | |
| 884 | .macos, .ios, .watchos, .tvos, .visionos => { | |
| 885 | // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding | |
| 886 | // via DWARF before attempting to use the compact unwind info will produce incorrect results. | |
| 887 | if (module.unwind_info) |unwind_info| { | |
| 888 | if (SelfInfo.unwindFrameMachO( | |
| 889 | unwind_state.debug_info.allocator, | |
| 890 | module.base_address, | |
| 891 | &unwind_state.dwarf_context, | |
| 892 | unwind_info, | |
| 893 | module.eh_frame, | |
| 894 | )) |return_address| { | |
| 895 | return return_address; | |
| 896 | } else |err| { | |
| 897 | if (err != error.RequiresDWARFUnwind) return err; | |
| 898 | } | |
| 899 | } else return error.MissingUnwindInfo; | |
| 900 | }, | |
| 901 | else => {}, | |
| 853 | // Workaround the C backend being unable to use inline assembly on MSVC by disabling the | |
| 854 | // call to `current`. This effectively constrains stack trace collection and dumping to FP | |
| 855 | // unwinding when building with CBE for MSVC. | |
| 856 | if (!(builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) and | |
| 857 | SelfInfo != void and | |
| 858 | SelfInfo.can_unwind and | |
| 859 | cpu_context.Native != noreturn) | |
| 860 | { | |
| 861 | // We don't need `di_first` here, because our PC is in `std.debug`; we're only interested | |
| 862 | // in our caller's frame and above. | |
| 863 | return .{ .di = .init(&.current()) }; | |
| 902 | 864 | } |
| 903 | ||
| 904 | if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| { | |
| 905 | return SelfInfo.unwindFrameDwarf( | |
| 906 | unwind_state.debug_info.allocator, | |
| 907 | di, | |
| 908 | module.base_address, | |
| 909 | &unwind_state.dwarf_context, | |
| 910 | null, | |
| 911 | ); | |
| 912 | } else return error.MissingDebugInfo; | |
| 865 | return .{ .fp = @frameAddress() }; | |
| 913 | 866 | } |
| 914 | ||
| 915 | fn next_internal(it: *StackIterator) ?usize { | |
| 916 | if (have_ucontext) { | |
| 917 | if (it.unwind_state) |*unwind_state| { | |
| 918 | if (!unwind_state.failed) { | |
| 919 | if (unwind_state.dwarf_context.pc == 0) return null; | |
| 920 | defer it.fp = unwind_state.dwarf_context.getFp() catch 0; | |
| 921 | if (it.next_unwind()) |return_address| { | |
| 922 | return return_address; | |
| 923 | } else |err| { | |
| 924 | unwind_state.last_error = err; | |
| 925 | unwind_state.failed = true; | |
| 926 | ||
| 927 | // Fall back to fp-based unwinding on the first failure. | |
| 928 | // We can't attempt it again for other modules higher in the | |
| 929 | // stack because the full register state won't have been unwound. | |
| 930 | } | |
| 931 | } | |
| 932 | } | |
| 867 | fn deinit(si: *StackIterator) void { | |
| 868 | switch (si.*) { | |
| 869 | .fp => {}, | |
| 870 | .di, .di_first => |*unwind_context| unwind_context.deinit(getDebugInfoAllocator()), | |
| 933 | 871 | } |
| 934 | ||
| 935 | if (builtin.omit_frame_pointer) return null; | |
| 936 | ||
| 937 | const fp = if (comptime native_arch.isSPARC()) | |
| 938 | // On SPARC the offset is positive. (!) | |
| 939 | math.add(usize, it.fp, fp_offset) catch return null | |
| 940 | else | |
| 941 | math.sub(usize, it.fp, fp_offset) catch return null; | |
| 942 | ||
| 943 | // Sanity check. | |
| 944 | if (fp == 0 or !mem.isAligned(fp, @alignOf(usize))) return null; | |
| 945 | const new_fp = math.add(usize, @as(*usize, @ptrFromInt(fp)).*, fp_bias) catch | |
| 946 | return null; | |
| 947 | ||
| 948 | // Sanity check: the stack grows down thus all the parent frames must be | |
| 949 | // be at addresses that are greater (or equal) than the previous one. | |
| 950 | // A zero frame pointer often signals this is the last frame, that case | |
| 951 | // is gracefully handled by the next call to next_internal. | |
| 952 | if (new_fp != 0 and new_fp < it.fp) return null; | |
| 953 | const new_pc = @as(*usize, @ptrFromInt(math.add(usize, fp, pc_offset) catch return null)).*; | |
| 954 | ||
| 955 | it.fp = new_fp; | |
| 956 | ||
| 957 | return new_pc; | |
| 958 | 872 | } |
| 959 | }; | |
| 960 | 873 | |
| 961 | pub fn writeCurrentStackTrace( | |
| 962 | writer: *Writer, | |
| 963 | debug_info: *SelfInfo, | |
| 964 | tty_config: tty.Config, | |
| 965 | start_addr: ?usize, | |
| 966 | ) !void { | |
| 967 | if (native_os == .windows) { | |
| 968 | var context: ThreadContext = undefined; | |
| 969 | assert(getContext(&context)); | |
| 970 | return writeStackTraceWindows(writer, debug_info, tty_config, &context, start_addr); | |
| 874 | /// On aarch64-macos, Apple mandate that the frame pointer is always used. | |
| 875 | /// TODO: are there any other architectures with guarantees like this? | |
| 876 | const fp_unwind_is_safe = builtin.cpu.arch == .aarch64 and builtin.os.tag.isDarwin(); | |
| 877 | ||
| 878 | /// Whether the current unwind strategy is allowed given `allow_unsafe`. | |
| 879 | fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool { | |
| 880 | return switch (it.*) { | |
| 881 | .di, .di_first => true, | |
| 882 | // If we omitted frame pointers from *this* compilation, FP unwinding would crash | |
| 883 | // immediately regardless of anything. But FPs could also be omitted from a different | |
| 884 | // linked object, so it's not guaranteed to be safe, unless the target specifically | |
| 885 | // requires it. | |
| 886 | .fp => !builtin.omit_frame_pointer and (fp_unwind_is_safe or allow_unsafe), | |
| 887 | }; | |
| 971 | 888 | } |
| 972 | var context: ThreadContext = undefined; | |
| 973 | const has_context = getContext(&context); | |
| 974 | ||
| 975 | var it = (if (has_context) blk: { | |
| 976 | break :blk StackIterator.initWithContext(start_addr, debug_info, &context, @frameAddress()) catch null; | |
| 977 | } else null) orelse StackIterator.init(start_addr, @frameAddress()); | |
| 978 | defer it.deinit(); | |
| 979 | 889 | |
| 980 | while (it.next()) |return_address| { | |
| 981 | printLastUnwindError(&it, debug_info, writer, tty_config); | |
| 982 | ||
| 983 | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, | |
| 984 | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid | |
| 985 | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this | |
| 986 | // condition on the subsequent iteration and return `null` thus terminating the loop. | |
| 987 | // same behaviour for x86-windows-msvc | |
| 988 | const address = return_address -| 1; | |
| 989 | try printSourceAtAddress(debug_info, writer, address, tty_config); | |
| 990 | } else printLastUnwindError(&it, debug_info, writer, tty_config); | |
| 991 | } | |
| 890 | const Result = union(enum) { | |
| 891 | /// A stack frame has been found; this is the corresponding return address. | |
| 892 | frame: usize, | |
| 893 | /// The end of the stack has been reached. | |
| 894 | end, | |
| 895 | /// We were using `SelfInfo.UnwindInfo`, but are now switching to FP unwinding due to this error. | |
| 896 | switch_to_fp: struct { | |
| 897 | address: usize, | |
| 898 | err: SelfInfoError, | |
| 899 | }, | |
| 900 | }; | |
| 992 | 901 | |
| 993 | pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize { | |
| 994 | if (builtin.cpu.arch == .x86) { | |
| 995 | // RtlVirtualUnwind doesn't exist on x86 | |
| 996 | return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null); | |
| 997 | } | |
| 902 | fn next(it: *StackIterator) Result { | |
| 903 | switch (it.*) { | |
| 904 | .di_first => |unwind_context| { | |
| 905 | const first_pc = unwind_context.pc; | |
| 906 | if (first_pc == 0) return .end; | |
| 907 | it.* = .{ .di = unwind_context }; | |
| 908 | // The caller expects *return* addresses, where they will subtract 1 to find the address of the call. | |
| 909 | // However, we have the actual current PC, which should not be adjusted. Compensate by adding 1. | |
| 910 | return .{ .frame = first_pc +| 1 }; | |
| 911 | }, | |
| 912 | .di => |*unwind_context| { | |
| 913 | const di = getSelfDebugInfo() catch unreachable; | |
| 914 | const di_gpa = getDebugInfoAllocator(); | |
| 915 | const ret_addr = di.unwindFrame(di_gpa, unwind_context) catch |err| { | |
| 916 | const pc = unwind_context.pc; | |
| 917 | it.* = .{ .fp = unwind_context.getFp() }; | |
| 918 | return .{ .switch_to_fp = .{ | |
| 919 | .address = pc, | |
| 920 | .err = err, | |
| 921 | } }; | |
| 922 | }; | |
| 923 | if (ret_addr <= 1) return .end; | |
| 924 | return .{ .frame = ret_addr }; | |
| 925 | }, | |
| 926 | .fp => |fp| { | |
| 927 | if (fp == 0) return .end; // we reached the "sentinel" base pointer | |
| 998 | 928 | |
| 999 | const tib = &windows.teb().NtTib; | |
| 929 | const bp_addr = applyOffset(fp, bp_offset) orelse return .end; | |
| 930 | const ra_addr = applyOffset(fp, ra_offset) orelse return .end; | |
| 1000 | 931 | |
| 1001 | var context: windows.CONTEXT = undefined; | |
| 1002 | if (existing_context) |context_ptr| { | |
| 1003 | context = context_ptr.*; | |
| 1004 | } else { | |
| 1005 | context = std.mem.zeroes(windows.CONTEXT); | |
| 1006 | windows.ntdll.RtlCaptureContext(&context); | |
| 1007 | } | |
| 932 | if (bp_addr == 0 or !mem.isAligned(bp_addr, @alignOf(usize)) or | |
| 933 | ra_addr == 0 or !mem.isAligned(ra_addr, @alignOf(usize))) | |
| 934 | { | |
| 935 | // This isn't valid, but it most likely indicates end of stack. | |
| 936 | return .end; | |
| 937 | } | |
| 1008 | 938 | |
| 1009 | var i: usize = 0; | |
| 1010 | var image_base: windows.DWORD64 = undefined; | |
| 1011 | var history_table: windows.UNWIND_HISTORY_TABLE = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE); | |
| 1012 | ||
| 1013 | while (i < addresses.len) : (i += 1) { | |
| 1014 | const current_regs = context.getRegs(); | |
| 1015 | if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &history_table)) |runtime_function| { | |
| 1016 | var handler_data: ?*anyopaque = null; | |
| 1017 | var establisher_frame: u64 = undefined; | |
| 1018 | _ = windows.ntdll.RtlVirtualUnwind( | |
| 1019 | windows.UNW_FLAG_NHANDLER, | |
| 1020 | image_base, | |
| 1021 | current_regs.ip, | |
| 1022 | runtime_function, | |
| 1023 | &context, | |
| 1024 | &handler_data, | |
| 1025 | &establisher_frame, | |
| 1026 | null, | |
| 1027 | ); | |
| 1028 | } else { | |
| 1029 | // leaf function | |
| 1030 | context.setIp(@as(*usize, @ptrFromInt(current_regs.sp)).*); | |
| 1031 | context.setSp(current_regs.sp + @sizeOf(usize)); | |
| 1032 | } | |
| 939 | const bp_ptr: *const usize = @ptrFromInt(bp_addr); | |
| 940 | const ra_ptr: *const usize = @ptrFromInt(ra_addr); | |
| 941 | const bp = applyOffset(bp_ptr.*, bp_bias) orelse return .end; | |
| 1033 | 942 | |
| 1034 | const next_regs = context.getRegs(); | |
| 1035 | if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) { | |
| 1036 | break; | |
| 1037 | } | |
| 943 | // The stack grows downards, so `bp > fp` should always hold. If it doesn't, this | |
| 944 | // frame is invalid, so we'll treat it as though it we reached end of stack. The | |
| 945 | // exception is address 0, which is a graceful end-of-stack signal, in which case | |
| 946 | // *this* return address is valid and the *next* iteration will be the last. | |
| 947 | if (bp != 0 and bp <= fp) return .end; | |
| 1038 | 948 | |
| 1039 | if (next_regs.ip == 0) { | |
| 1040 | break; | |
| 949 | it.fp = bp; | |
| 950 | const ra = stripInstructionPtrAuthCode(ra_ptr.*); | |
| 951 | if (ra <= 1) return .end; | |
| 952 | return .{ .frame = ra }; | |
| 953 | }, | |
| 1041 | 954 | } |
| 1042 | ||
| 1043 | addresses[i] = next_regs.ip; | |
| 1044 | 955 | } |
| 1045 | 956 | |
| 1046 | return i; | |
| 1047 | } | |
| 957 | /// Offset of the saved base pointer (previous frame pointer) wrt the frame pointer. | |
| 958 | const bp_offset = off: { | |
| 959 | // On RISC-V the frame pointer points to the top of the saved register | |
| 960 | // area, on pretty much every other architecture it points to the stack | |
| 961 | // slot where the previous frame pointer is saved. | |
| 962 | if (native_arch.isRISCV()) break :off -2 * @sizeOf(usize); | |
| 963 | // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS. | |
| 964 | if (native_arch.isSPARC()) break :off 14 * @sizeOf(usize); | |
| 965 | break :off 0; | |
| 966 | }; | |
| 1048 | 967 | |
| 1049 | pub fn writeStackTraceWindows( | |
| 1050 | writer: *Writer, | |
| 1051 | debug_info: *SelfInfo, | |
| 1052 | tty_config: tty.Config, | |
| 1053 | context: *const windows.CONTEXT, | |
| 1054 | start_addr: ?usize, | |
| 1055 | ) !void { | |
| 1056 | var addr_buf: [1024]usize = undefined; | |
| 1057 | const n = walkStackWindows(addr_buf[0..], context); | |
| 1058 | const addrs = addr_buf[0..n]; | |
| 1059 | const start_i: usize = if (start_addr) |saddr| blk: { | |
| 1060 | for (addrs, 0..) |addr, i| { | |
| 1061 | if (addr == saddr) break :blk i; | |
| 1062 | } | |
| 1063 | return; | |
| 1064 | } else 0; | |
| 1065 | for (addrs[start_i..]) |addr| { | |
| 1066 | try printSourceAtAddress(debug_info, writer, addr - 1, tty_config); | |
| 1067 | } | |
| 1068 | } | |
| 968 | /// Offset of the saved return address wrt the frame pointer. | |
| 969 | const ra_offset = off: { | |
| 970 | if (native_arch == .powerpc64le) break :off 2 * @sizeOf(usize); | |
| 971 | break :off @sizeOf(usize); | |
| 972 | }; | |
| 1069 | 973 | |
| 1070 | fn printUnknownSource(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void { | |
| 1071 | const module_name = debug_info.getModuleNameForAddress(address); | |
| 1072 | return printLineInfo( | |
| 1073 | writer, | |
| 1074 | null, | |
| 1075 | address, | |
| 1076 | "???", | |
| 1077 | module_name orelse "???", | |
| 1078 | tty_config, | |
| 1079 | printLineFromFileAnyOs, | |
| 1080 | ); | |
| 1081 | } | |
| 974 | /// Value to add to a base pointer after loading it from the stack. Yes, SPARC really does this. | |
| 975 | const bp_bias = bias: { | |
| 976 | if (native_arch.isSPARC()) break :bias 2047; | |
| 977 | break :bias 0; | |
| 978 | }; | |
| 1082 | 979 | |
| 1083 | fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: tty.Config) void { | |
| 1084 | if (!have_ucontext) return; | |
| 1085 | if (it.getLastError()) |unwind_error| { | |
| 1086 | printUnwindError(debug_info, writer, unwind_error.address, unwind_error.err, tty_config) catch {}; | |
| 980 | fn applyOffset(addr: usize, comptime off: comptime_int) ?usize { | |
| 981 | if (off >= 0) return math.add(usize, addr, off) catch return null; | |
| 982 | return math.sub(usize, addr, -off) catch return null; | |
| 1087 | 983 | } |
| 1088 | } | |
| 984 | }; | |
| 1089 | 985 | |
| 1090 | fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, err: UnwindError, tty_config: tty.Config) !void { | |
| 1091 | const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; | |
| 1092 | try tty_config.setColor(writer, .dim); | |
| 1093 | if (err == error.MissingDebugInfo) { | |
| 1094 | try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address }); | |
| 1095 | } else { | |
| 1096 | try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err }); | |
| 986 | /// Some platforms use pointer authentication: the upper bits of instruction pointers contain a | |
| 987 | /// signature. This function clears those signature bits to make the pointer directly usable. | |
| 988 | pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { | |
| 989 | if (native_arch.isAARCH64()) { | |
| 990 | // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it) | |
| 991 | // The save / restore is because `xpaclri` operates on x30 (LR) | |
| 992 | return asm ( | |
| 993 | \\mov x16, x30 | |
| 994 | \\mov x30, x15 | |
| 995 | \\hint 0x07 | |
| 996 | \\mov x15, x30 | |
| 997 | \\mov x30, x16 | |
| 998 | : [ret] "={x15}" (-> usize), | |
| 999 | : [ptr] "{x15}" (ptr), | |
| 1000 | : .{ .x16 = true }); | |
| 1097 | 1001 | } |
| 1098 | try tty_config.setColor(writer, .reset); | |
| 1099 | } | |
| 1100 | 1002 | |
| 1101 | pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void { | |
| 1102 | const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { | |
| 1103 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config), | |
| 1104 | else => return err, | |
| 1105 | }; | |
| 1003 | return ptr; | |
| 1004 | } | |
| 1106 | 1005 | |
| 1107 | const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) { | |
| 1108 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config), | |
| 1109 | else => return err, | |
| 1006 | fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void { | |
| 1007 | const symbol: Symbol = debug_info.getSymbol(gpa, address) catch |err| switch (err) { | |
| 1008 | error.MissingDebugInfo, | |
| 1009 | error.UnsupportedDebugInfo, | |
| 1010 | error.InvalidDebugInfo, | |
| 1011 | => .unknown, | |
| 1012 | error.ReadFailed, error.Unexpected => s: { | |
| 1013 | tty_config.setColor(writer, .dim) catch {}; | |
| 1014 | try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); | |
| 1015 | tty_config.setColor(writer, .reset) catch {}; | |
| 1016 | break :s .unknown; | |
| 1017 | }, | |
| 1018 | error.OutOfMemory => s: { | |
| 1019 | tty_config.setColor(writer, .dim) catch {}; | |
| 1020 | try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{}); | |
| 1021 | tty_config.setColor(writer, .reset) catch {}; | |
| 1022 | break :s .unknown; | |
| 1023 | }, | |
| 1110 | 1024 | }; |
| 1111 | defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name); | |
| 1112 | ||
| 1025 | defer if (symbol.source_location) |sl| gpa.free(sl.file_name); | |
| 1113 | 1026 | return printLineInfo( |
| 1114 | 1027 | writer, |
| 1115 | symbol_info.source_location, | |
| 1028 | symbol.source_location, | |
| 1116 | 1029 | address, |
| 1117 | symbol_info.name, | |
| 1118 | symbol_info.compile_unit_name, | |
| 1030 | symbol.name orelse "???", | |
| 1031 | symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???", | |
| 1119 | 1032 | tty_config, |
| 1120 | printLineFromFileAnyOs, | |
| 1121 | 1033 | ); |
| 1122 | 1034 | } |
| 1123 | ||
| 1124 | 1035 | fn printLineInfo( |
| 1125 | 1036 | writer: *Writer, |
| 1126 | 1037 | source_location: ?SourceLocation, |
| ... | ... | @@ -1128,10 +1039,9 @@ fn printLineInfo( |
| 1128 | 1039 | symbol_name: []const u8, |
| 1129 | 1040 | compile_unit_name: []const u8, |
| 1130 | 1041 | tty_config: tty.Config, |
| 1131 | comptime printLineFromFile: anytype, | |
| 1132 | ) !void { | |
| 1042 | ) Writer.Error!void { | |
| 1133 | 1043 | nosuspend { |
| 1134 | try tty_config.setColor(writer, .bold); | |
| 1044 | tty_config.setColor(writer, .bold) catch {}; | |
| 1135 | 1045 | |
| 1136 | 1046 | if (source_location) |*sl| { |
| 1137 | 1047 | try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); |
| ... | ... | @@ -1139,11 +1049,11 @@ fn printLineInfo( |
| 1139 | 1049 | try writer.writeAll("???:?:?"); |
| 1140 | 1050 | } |
| 1141 | 1051 | |
| 1142 | try tty_config.setColor(writer, .reset); | |
| 1052 | tty_config.setColor(writer, .reset) catch {}; | |
| 1143 | 1053 | try writer.writeAll(": "); |
| 1144 | try tty_config.setColor(writer, .dim); | |
| 1054 | tty_config.setColor(writer, .dim) catch {}; | |
| 1145 | 1055 | try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); |
| 1146 | try tty_config.setColor(writer, .reset); | |
| 1056 | tty_config.setColor(writer, .reset) catch {}; | |
| 1147 | 1057 | try writer.writeAll("\n"); |
| 1148 | 1058 | |
| 1149 | 1059 | // Show the matching source code line if possible |
| ... | ... | @@ -1154,22 +1064,24 @@ fn printLineInfo( |
| 1154 | 1064 | const space_needed = @as(usize, @intCast(sl.column - 1)); |
| 1155 | 1065 | |
| 1156 | 1066 | try writer.splatByteAll(' ', space_needed); |
| 1157 | try tty_config.setColor(writer, .green); | |
| 1067 | tty_config.setColor(writer, .green) catch {}; | |
| 1158 | 1068 | try writer.writeAll("^"); |
| 1159 | try tty_config.setColor(writer, .reset); | |
| 1069 | tty_config.setColor(writer, .reset) catch {}; | |
| 1160 | 1070 | } |
| 1161 | 1071 | try writer.writeAll("\n"); |
| 1162 | } else |err| switch (err) { | |
| 1163 | error.EndOfFile, error.FileNotFound => {}, | |
| 1164 | error.BadPathName => {}, | |
| 1165 | error.AccessDenied => {}, | |
| 1166 | else => return err, | |
| 1072 | } else |_| { | |
| 1073 | // Ignore all errors; it's a better UX to just print the source location without the | |
| 1074 | // corresponding line number. The user can always open the source file themselves. | |
| 1167 | 1075 | } |
| 1168 | 1076 | } |
| 1169 | 1077 | } |
| 1170 | 1078 | } |
| 1079 | fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void { | |
| 1080 | // Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`. | |
| 1081 | if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printLineFromFile")) { | |
| 1082 | return root.debug.printLineFromFile(writer, source_location); | |
| 1083 | } | |
| 1171 | 1084 | |
| 1172 | fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !void { | |
| 1173 | 1085 | // Need this to always block even in async I/O mode, because this could potentially |
| 1174 | 1086 | // be called from e.g. the event loop code crashing. |
| 1175 | 1087 | var f = try fs.cwd().openFile(source_location.file_name, .{}); |
| ... | ... | @@ -1223,7 +1135,7 @@ fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !voi |
| 1223 | 1135 | } |
| 1224 | 1136 | } |
| 1225 | 1137 | |
| 1226 | test printLineFromFileAnyOs { | |
| 1138 | test printLineFromFile { | |
| 1227 | 1139 | var aw: Writer.Allocating = .init(std.testing.allocator); |
| 1228 | 1140 | defer aw.deinit(); |
| 1229 | 1141 | const output_stream = &aw.writer; |
| ... | ... | @@ -1245,9 +1157,9 @@ test printLineFromFileAnyOs { |
| 1245 | 1157 | defer allocator.free(path); |
| 1246 | 1158 | try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" }); |
| 1247 | 1159 | |
| 1248 | try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); | |
| 1160 | try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); | |
| 1249 | 1161 | |
| 1250 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); | |
| 1162 | try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); | |
| 1251 | 1163 | try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written()); |
| 1252 | 1164 | aw.clearRetainingCapacity(); |
| 1253 | 1165 | } |
| ... | ... | @@ -1263,11 +1175,11 @@ test printLineFromFileAnyOs { |
| 1263 | 1175 | , |
| 1264 | 1176 | }); |
| 1265 | 1177 | |
| 1266 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); | |
| 1178 | try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); | |
| 1267 | 1179 | try expectEqualStrings("1\n", aw.written()); |
| 1268 | 1180 | aw.clearRetainingCapacity(); |
| 1269 | 1181 | |
| 1270 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 3, .column = 0 }); | |
| 1182 | try printLineFromFile(output_stream, .{ .file_name = path, .line = 3, .column = 0 }); | |
| 1271 | 1183 | try expectEqualStrings("3\n", aw.written()); |
| 1272 | 1184 | aw.clearRetainingCapacity(); |
| 1273 | 1185 | } |
| ... | ... | @@ -1286,7 +1198,7 @@ test printLineFromFileAnyOs { |
| 1286 | 1198 | try writer.splatByteAll('a', overlap); |
| 1287 | 1199 | try writer.flush(); |
| 1288 | 1200 | |
| 1289 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); | |
| 1201 | try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); | |
| 1290 | 1202 | try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written()); |
| 1291 | 1203 | aw.clearRetainingCapacity(); |
| 1292 | 1204 | } |
| ... | ... | @@ -1300,7 +1212,7 @@ test printLineFromFileAnyOs { |
| 1300 | 1212 | const writer = &file_writer.interface; |
| 1301 | 1213 | try writer.splatByteAll('a', std.heap.page_size_max); |
| 1302 | 1214 | |
| 1303 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); | |
| 1215 | try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); | |
| 1304 | 1216 | try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written()); |
| 1305 | 1217 | aw.clearRetainingCapacity(); |
| 1306 | 1218 | } |
| ... | ... | @@ -1314,19 +1226,19 @@ test printLineFromFileAnyOs { |
| 1314 | 1226 | const writer = &file_writer.interface; |
| 1315 | 1227 | try writer.splatByteAll('a', 3 * std.heap.page_size_max); |
| 1316 | 1228 | |
| 1317 | try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); | |
| 1229 | try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); | |
| 1318 | 1230 | |
| 1319 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); | |
| 1231 | try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); | |
| 1320 | 1232 | try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written()); |
| 1321 | 1233 | aw.clearRetainingCapacity(); |
| 1322 | 1234 | |
| 1323 | 1235 | try writer.writeAll("a\na"); |
| 1324 | 1236 | |
| 1325 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); | |
| 1237 | try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); | |
| 1326 | 1238 | try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written()); |
| 1327 | 1239 | aw.clearRetainingCapacity(); |
| 1328 | 1240 | |
| 1329 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); | |
| 1241 | try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); | |
| 1330 | 1242 | try expectEqualStrings("a\n", aw.written()); |
| 1331 | 1243 | aw.clearRetainingCapacity(); |
| 1332 | 1244 | } |
| ... | ... | @@ -1342,26 +1254,29 @@ test printLineFromFileAnyOs { |
| 1342 | 1254 | try writer.splatByteAll('\n', real_file_start); |
| 1343 | 1255 | try writer.writeAll("abc\ndef"); |
| 1344 | 1256 | |
| 1345 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 }); | |
| 1257 | try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 }); | |
| 1346 | 1258 | try expectEqualStrings("abc\n", aw.written()); |
| 1347 | 1259 | aw.clearRetainingCapacity(); |
| 1348 | 1260 | |
| 1349 | try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 }); | |
| 1261 | try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 }); | |
| 1350 | 1262 | try expectEqualStrings("def\n", aw.written()); |
| 1351 | 1263 | aw.clearRetainingCapacity(); |
| 1352 | 1264 | } |
| 1353 | 1265 | } |
| 1354 | 1266 | |
| 1355 | /// TODO multithreaded awareness | |
| 1356 | var debug_info_allocator: ?mem.Allocator = null; | |
| 1357 | var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined; | |
| 1358 | fn getDebugInfoAllocator() mem.Allocator { | |
| 1359 | if (debug_info_allocator) |a| return a; | |
| 1360 | ||
| 1361 | debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
| 1362 | const allocator = debug_info_arena_allocator.allocator(); | |
| 1363 | debug_info_allocator = allocator; | |
| 1364 | return allocator; | |
| 1267 | /// The returned allocator should be thread-safe if the compilation is multi-threaded, because | |
| 1268 | /// multiple threads could capture and/or print stack traces simultaneously. | |
| 1269 | fn getDebugInfoAllocator() Allocator { | |
| 1270 | // Allow overriding the debug info allocator by exposing `root.debug.getDebugInfoAllocator`. | |
| 1271 | if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getDebugInfoAllocator")) { | |
| 1272 | return root.debug.getDebugInfoAllocator(); | |
| 1273 | } | |
| 1274 | // Otherwise, use a global arena backed by the page allocator | |
| 1275 | const S = struct { | |
| 1276 | var arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator); | |
| 1277 | var ts_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena.allocator() }; | |
| 1278 | }; | |
| 1279 | return S.ts_arena.allocator(); | |
| 1365 | 1280 | } |
| 1366 | 1281 | |
| 1367 | 1282 | /// Whether or not the current target can print useful debug information when a segfault occurs. |
| ... | ... | @@ -1372,9 +1287,10 @@ pub const have_segfault_handling_support = switch (native_os) { |
| 1372 | 1287 | .solaris, |
| 1373 | 1288 | .illumos, |
| 1374 | 1289 | .windows, |
| 1290 | .freebsd, | |
| 1291 | .openbsd, | |
| 1375 | 1292 | => true, |
| 1376 | 1293 | |
| 1377 | .freebsd, .openbsd => have_ucontext, | |
| 1378 | 1294 | else => false, |
| 1379 | 1295 | }; |
| 1380 | 1296 | |
| ... | ... | @@ -1396,7 +1312,16 @@ pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) void { |
| 1396 | 1312 | posix.sigaction(posix.SIG.FPE, act, null); |
| 1397 | 1313 | } |
| 1398 | 1314 | |
| 1399 | /// Attaches a global SIGSEGV handler which calls `@panic("segmentation fault");` | |
| 1315 | /// Attaches a global handler for several signals which, when triggered, prints output to stderr | |
| 1316 | /// similar to the default panic handler, with a message containing the type of signal and a stack | |
| 1317 | /// trace if possible. This implementation does not just call the panic handler, because unwinding | |
| 1318 | /// the stack (for a stack trace) when a signal is received requires special target-specific logic. | |
| 1319 | /// | |
| 1320 | /// The signals for which a handler is installed are: | |
| 1321 | /// * SIGSEGV (segmentation fault) | |
| 1322 | /// * SIGILL (illegal instruction) | |
| 1323 | /// * SIGBUS (bus error) | |
| 1324 | /// * SIGFPE (arithmetic exception) | |
| 1400 | 1325 | pub fn attachSegfaultHandler() void { |
| 1401 | 1326 | if (!have_segfault_handling_support) { |
| 1402 | 1327 | @compileError("segfault handler not supported for this target"); |
| ... | ... | @@ -1430,52 +1355,9 @@ fn resetSegfaultHandler() void { |
| 1430 | 1355 | } |
| 1431 | 1356 | |
| 1432 | 1357 | fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn { |
| 1433 | // Reset to the default handler so that if a segfault happens in this handler it will crash | |
| 1434 | // the process. Also when this handler returns, the original instruction will be repeated | |
| 1435 | // and the resulting segfault will crash the process rather than continually dump stack traces. | |
| 1436 | resetSegfaultHandler(); | |
| 1437 | ||
| 1438 | const addr = switch (native_os) { | |
| 1439 | .linux => @intFromPtr(info.fields.sigfault.addr), | |
| 1440 | .freebsd, .macos => @intFromPtr(info.addr), | |
| 1441 | .netbsd => @intFromPtr(info.info.reason.fault.addr), | |
| 1442 | .openbsd => @intFromPtr(info.data.fault.addr), | |
| 1443 | .solaris, .illumos => @intFromPtr(info.reason.fault.addr), | |
| 1444 | else => unreachable, | |
| 1445 | }; | |
| 1446 | ||
| 1447 | const code = if (native_os == .netbsd) info.info.code else info.code; | |
| 1448 | nosuspend switch (panic_stage) { | |
| 1449 | 0 => { | |
| 1450 | panic_stage = 1; | |
| 1451 | _ = panicking.fetchAdd(1, .seq_cst); | |
| 1452 | ||
| 1453 | { | |
| 1454 | lockStdErr(); | |
| 1455 | defer unlockStdErr(); | |
| 1456 | ||
| 1457 | dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr); | |
| 1458 | } | |
| 1459 | ||
| 1460 | waitForOtherThreadToFinishPanicking(); | |
| 1461 | }, | |
| 1462 | else => { | |
| 1463 | // panic mutex already locked | |
| 1464 | dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr); | |
| 1465 | }, | |
| 1466 | }; | |
| 1467 | ||
| 1468 | // We cannot allow the signal handler to return because when it runs the original instruction | |
| 1469 | // again, the memory may be mapped and undefined behavior would occur rather than repeating | |
| 1470 | // the segfault. So we simply abort here. | |
| 1471 | posix.abort(); | |
| 1472 | } | |
| 1473 | ||
| 1474 | fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void { | |
| 1475 | const stderr = lockStderrWriter(&.{}); | |
| 1476 | defer unlockStderrWriter(); | |
| 1477 | _ = switch (sig) { | |
| 1478 | posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL | |
| 1358 | if (use_trap_panic) @trap(); | |
| 1359 | const addr: ?usize, const name: []const u8 = info: { | |
| 1360 | if (native_os == .linux and native_arch == .x86_64) { | |
| 1479 | 1361 | // x86_64 doesn't have a full 64-bit virtual address space. |
| 1480 | 1362 | // Addresses outside of that address space are non-canonical |
| 1481 | 1363 | // and the CPU won't provide the faulting address to us. |
| ... | ... | @@ -1483,100 +1365,92 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) |
| 1483 | 1365 | // but can also happen when no addressable memory is involved; |
| 1484 | 1366 | // for example when reading/writing model-specific registers |
| 1485 | 1367 | // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode). |
| 1486 | stderr.writeAll("General protection exception (no address available)\n") | |
| 1487 | else | |
| 1488 | stderr.print("Segmentation fault at address 0x{x}\n", .{addr}), | |
| 1489 | posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}), | |
| 1490 | posix.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}), | |
| 1491 | posix.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}), | |
| 1492 | else => unreachable, | |
| 1493 | } catch posix.abort(); | |
| 1494 | ||
| 1495 | switch (native_arch) { | |
| 1496 | .x86, | |
| 1497 | .x86_64, | |
| 1498 | .arm, | |
| 1499 | .armeb, | |
| 1500 | .thumb, | |
| 1501 | .thumbeb, | |
| 1502 | .aarch64, | |
| 1503 | .aarch64_be, | |
| 1504 | => { | |
| 1505 | // Some kernels don't align `ctx_ptr` properly. Handle this defensively. | |
| 1506 | const ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr); | |
| 1507 | var new_ctx: posix.ucontext_t = ctx.*; | |
| 1508 | if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) { | |
| 1509 | // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`, | |
| 1510 | // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the | |
| 1511 | // contents to the right place so that the `mcontext` pointer will be correct after the | |
| 1512 | // `relocateContext` call below. | |
| 1513 | new_ctx.__mcontext_data = @as(*align(1) extern struct { | |
| 1514 | onstack: c_int, | |
| 1515 | sigmask: std.c.sigset_t, | |
| 1516 | stack: std.c.stack_t, | |
| 1517 | link: ?*std.c.ucontext_t, | |
| 1518 | mcsize: u64, | |
| 1519 | mcontext: *std.c.mcontext_t, | |
| 1520 | __mcontext_data: std.c.mcontext_t align(@sizeOf(usize)), // Disable padding after `mcontext`. | |
| 1521 | }, @ptrCast(ctx)).__mcontext_data; | |
| 1368 | const SI_KERNEL = 0x80; | |
| 1369 | if (sig == posix.SIG.SEGV and info.code == SI_KERNEL) { | |
| 1370 | break :info .{ null, "General protection exception" }; | |
| 1522 | 1371 | } |
| 1523 | relocateContext(&new_ctx); | |
| 1524 | dumpStackTraceFromBase(&new_ctx, stderr); | |
| 1525 | }, | |
| 1526 | else => {}, | |
| 1527 | } | |
| 1372 | } | |
| 1373 | const addr: usize = switch (native_os) { | |
| 1374 | .linux => @intFromPtr(info.fields.sigfault.addr), | |
| 1375 | .freebsd, .macos => @intFromPtr(info.addr), | |
| 1376 | .netbsd => @intFromPtr(info.info.reason.fault.addr), | |
| 1377 | .openbsd => @intFromPtr(info.data.fault.addr), | |
| 1378 | .solaris, .illumos => @intFromPtr(info.reason.fault.addr), | |
| 1379 | else => comptime unreachable, | |
| 1380 | }; | |
| 1381 | const name = switch (sig) { | |
| 1382 | posix.SIG.SEGV => "Segmentation fault", | |
| 1383 | posix.SIG.ILL => "Illegal instruction", | |
| 1384 | posix.SIG.BUS => "Bus error", | |
| 1385 | posix.SIG.FPE => "Arithmetic exception", | |
| 1386 | else => unreachable, | |
| 1387 | }; | |
| 1388 | break :info .{ addr, name }; | |
| 1389 | }; | |
| 1390 | const opt_cpu_context: ?cpu_context.Native = cpu_context.fromPosixSignalContext(ctx_ptr); | |
| 1391 | handleSegfault(addr, name, if (opt_cpu_context) |*ctx| ctx else null); | |
| 1528 | 1392 | } |
| 1529 | 1393 | |
| 1530 | 1394 | fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long { |
| 1531 | switch (info.ExceptionRecord.ExceptionCode) { | |
| 1532 | windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, 0, "Unaligned Memory Access"), | |
| 1533 | windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, 1, null), | |
| 1534 | windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, 2, null), | |
| 1535 | windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, 0, "Stack Overflow"), | |
| 1395 | if (use_trap_panic) @trap(); | |
| 1396 | const name: []const u8, const addr: ?usize = switch (info.ExceptionRecord.ExceptionCode) { | |
| 1397 | windows.EXCEPTION_DATATYPE_MISALIGNMENT => .{ "Unaligned memory access", null }, | |
| 1398 | windows.EXCEPTION_ACCESS_VIOLATION => .{ "Segmentation fault", info.ExceptionRecord.ExceptionInformation[1] }, | |
| 1399 | windows.EXCEPTION_ILLEGAL_INSTRUCTION => .{ "Illegal instruction", info.ContextRecord.getRegs().ip }, | |
| 1400 | windows.EXCEPTION_STACK_OVERFLOW => .{ "Stack overflow", null }, | |
| 1536 | 1401 | else => return windows.EXCEPTION_CONTINUE_SEARCH, |
| 1537 | } | |
| 1402 | }; | |
| 1403 | handleSegfault(addr, name, &cpu_context.fromWindowsContext(info.ContextRecord)); | |
| 1538 | 1404 | } |
| 1539 | 1405 | |
| 1540 | fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) noreturn { | |
| 1541 | // For backends that cannot handle the language features used by this segfault handler, we have a simpler one, | |
| 1542 | switch (builtin.zig_backend) { | |
| 1543 | .stage2_x86_64 => if (builtin.target.ofmt == .coff) @trap(), | |
| 1544 | else => {}, | |
| 1406 | fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn { | |
| 1407 | // Allow overriding the target-agnostic segfault handler by exposing `root.debug.handleSegfault`. | |
| 1408 | if (@hasDecl(root, "debug") and @hasDecl(root.debug, "handleSegfault")) { | |
| 1409 | return root.debug.handleSegfault(addr, name, opt_ctx); | |
| 1545 | 1410 | } |
| 1411 | return defaultHandleSegfault(addr, name, opt_ctx); | |
| 1412 | } | |
| 1546 | 1413 | |
| 1547 | comptime assert(windows.CONTEXT != void); | |
| 1548 | nosuspend switch (panic_stage) { | |
| 1414 | pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn { | |
| 1415 | // There is very similar logic to the following in `defaultPanic`. | |
| 1416 | switch (panic_stage) { | |
| 1549 | 1417 | 0 => { |
| 1550 | 1418 | panic_stage = 1; |
| 1551 | 1419 | _ = panicking.fetchAdd(1, .seq_cst); |
| 1552 | 1420 | |
| 1553 | { | |
| 1421 | trace: { | |
| 1422 | const tty_config = tty.detectConfig(.stderr()); | |
| 1423 | ||
| 1554 | 1424 | const stderr = lockStderrWriter(&.{}); |
| 1555 | 1425 | defer unlockStderrWriter(); |
| 1556 | 1426 | |
| 1557 | dumpSegfaultInfoWindows(info, msg, label, stderr); | |
| 1427 | if (addr) |a| { | |
| 1428 | stderr.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace; | |
| 1429 | } else { | |
| 1430 | stderr.print("{s} (no address available)\n", .{name}) catch break :trace; | |
| 1431 | } | |
| 1432 | if (opt_ctx) |context| { | |
| 1433 | writeCurrentStackTrace(.{ | |
| 1434 | .context = context, | |
| 1435 | .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! | |
| 1436 | }, stderr, tty_config) catch break :trace; | |
| 1437 | } | |
| 1558 | 1438 | } |
| 1559 | ||
| 1560 | waitForOtherThreadToFinishPanicking(); | |
| 1561 | 1439 | }, |
| 1562 | 1440 | 1 => { |
| 1563 | 1441 | panic_stage = 2; |
| 1442 | // A segfault happened while trying to print a previous panic message. | |
| 1443 | // We're still holding the mutex but that's fine as we're going to | |
| 1444 | // call abort(). | |
| 1564 | 1445 | fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {}; |
| 1565 | 1446 | }, |
| 1566 | else => {}, | |
| 1567 | }; | |
| 1568 | posix.abort(); | |
| 1569 | } | |
| 1570 | ||
| 1571 | fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8, stderr: *Writer) void { | |
| 1572 | _ = switch (msg) { | |
| 1573 | 0 => stderr.print("{s}\n", .{label.?}), | |
| 1574 | 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}), | |
| 1575 | 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{info.ContextRecord.getRegs().ip}), | |
| 1576 | else => unreachable, | |
| 1577 | } catch posix.abort(); | |
| 1447 | else => {}, // Panicked while printing the recursive panic message. | |
| 1448 | } | |
| 1578 | 1449 | |
| 1579 | dumpStackTraceFromBase(info.ContextRecord, stderr); | |
| 1450 | // We cannot allow the signal handler to return because when it runs the original instruction | |
| 1451 | // again, the memory may be mapped and undefined behavior would occur rather than repeating | |
| 1452 | // the segfault. So we simply abort here. | |
| 1453 | posix.abort(); | |
| 1580 | 1454 | } |
| 1581 | 1455 | |
| 1582 | 1456 | pub fn dumpStackPointerAddr(prefix: []const u8) void { |
| ... | ... | @@ -1587,26 +1461,23 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void { |
| 1587 | 1461 | } |
| 1588 | 1462 | |
| 1589 | 1463 | test "manage resources correctly" { |
| 1590 | if (builtin.strip_debug_info) return error.SkipZigTest; | |
| 1591 | ||
| 1592 | if (native_os == .wasi) return error.SkipZigTest; | |
| 1593 | ||
| 1594 | if (native_os == .windows) { | |
| 1595 | // https://github.com/ziglang/zig/issues/13963 | |
| 1596 | return error.SkipZigTest; | |
| 1597 | } | |
| 1598 | ||
| 1599 | // self-hosted debug info is still too buggy | |
| 1600 | if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest; | |
| 1601 | ||
| 1602 | var discarding: Writer.Discarding = .init(&.{}); | |
| 1603 | var di = try SelfInfo.open(testing.allocator); | |
| 1604 | defer di.deinit(); | |
| 1605 | try printSourceAtAddress(&di, &discarding.writer, showMyTrace(), tty.detectConfig(.stderr())); | |
| 1606 | } | |
| 1607 | ||
| 1608 | noinline fn showMyTrace() usize { | |
| 1609 | return @returnAddress(); | |
| 1464 | if (SelfInfo == void) return error.SkipZigTest; | |
| 1465 | const S = struct { | |
| 1466 | noinline fn showMyTrace() usize { | |
| 1467 | return @returnAddress(); | |
| 1468 | } | |
| 1469 | }; | |
| 1470 | const gpa = std.testing.allocator; | |
| 1471 | var discarding: std.Io.Writer.Discarding = .init(&.{}); | |
| 1472 | var di: SelfInfo = .init; | |
| 1473 | defer di.deinit(gpa); | |
| 1474 | try printSourceAtAddress( | |
| 1475 | gpa, | |
| 1476 | &di, | |
| 1477 | &discarding.writer, | |
| 1478 | S.showMyTrace(), | |
| 1479 | tty.detectConfig(.stderr()), | |
| 1480 | ); | |
| 1610 | 1481 | } |
| 1611 | 1482 | |
| 1612 | 1483 | /// This API helps you track where a value originated and where it was mutated, |
| ... | ... | @@ -1653,12 +1524,11 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize |
| 1653 | 1524 | |
| 1654 | 1525 | if (t.index < size) { |
| 1655 | 1526 | t.notes[t.index] = note; |
| 1656 | t.addrs[t.index] = [1]usize{0} ** stack_frame_count; | |
| 1657 | var stack_trace: std.builtin.StackTrace = .{ | |
| 1658 | .index = 0, | |
| 1659 | .instruction_addresses = &t.addrs[t.index], | |
| 1660 | }; | |
| 1661 | captureStackTrace(addr, &stack_trace); | |
| 1527 | const addrs = &t.addrs[t.index]; | |
| 1528 | const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs); | |
| 1529 | if (st.index < addrs.len) { | |
| 1530 | @memset(addrs[st.index..], 0); // zero unused frames to indicate end of trace | |
| 1531 | } | |
| 1662 | 1532 | } |
| 1663 | 1533 | // Keep counting even if the end is reached so that the |
| 1664 | 1534 | // user can find out how much more size they need. |
| ... | ... | @@ -1672,13 +1542,6 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize |
| 1672 | 1542 | const stderr = lockStderrWriter(&.{}); |
| 1673 | 1543 | defer unlockStderrWriter(); |
| 1674 | 1544 | const end = @min(t.index, size); |
| 1675 | const debug_info = getSelfDebugInfo() catch |err| { | |
| 1676 | stderr.print( | |
| 1677 | "Unable to dump stack trace: Unable to open debug info: {s}\n", | |
| 1678 | .{@errorName(err)}, | |
| 1679 | ) catch return; | |
| 1680 | return; | |
| 1681 | }; | |
| 1682 | 1545 | for (t.addrs[0..end], 0..) |frames_array, i| { |
| 1683 | 1546 | stderr.print("{s}:\n", .{t.notes[i]}) catch return; |
| 1684 | 1547 | var frames_array_mutable = frames_array; |
| ... | ... | @@ -1687,7 +1550,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize |
| 1687 | 1550 | .index = frames.len, |
| 1688 | 1551 | .instruction_addresses = frames, |
| 1689 | 1552 | }; |
| 1690 | writeStackTrace(stack_trace, stderr, debug_info, tty_config) catch continue; | |
| 1553 | writeStackTrace(&stack_trace, stderr, tty_config) catch return; | |
| 1691 | 1554 | } |
| 1692 | 1555 | if (t.index > end) { |
| 1693 | 1556 | stderr.print("{d} more traces not shown; consider increasing trace size\n", .{ |
lib/std/debug/Coverage.zig+2-1| ... | ... | @@ -145,6 +145,7 @@ pub const ResolveAddressesDwarfError = Dwarf.ScanError; |
| 145 | 145 | pub fn resolveAddressesDwarf( |
| 146 | 146 | cov: *Coverage, |
| 147 | 147 | gpa: Allocator, |
| 148 | endian: std.builtin.Endian, | |
| 148 | 149 | /// Asserts the addresses are in ascending order. |
| 149 | 150 | sorted_pc_addrs: []const u64, |
| 150 | 151 | /// Asserts its length equals length of `sorted_pc_addrs`. |
| ... | ... | @@ -184,7 +185,7 @@ pub fn resolveAddressesDwarf( |
| 184 | 185 | if (cu.src_loc_cache == null) { |
| 185 | 186 | cov.mutex.unlock(); |
| 186 | 187 | defer cov.mutex.lock(); |
| 187 | d.populateSrcLocCache(gpa, cu) catch |err| switch (err) { | |
| 188 | d.populateSrcLocCache(gpa, endian, cu) catch |err| switch (err) { | |
| 188 | 189 | error.MissingDebugInfo, error.InvalidDebugInfo => { |
| 189 | 190 | out.* = SourceLocation.invalid; |
| 190 | 191 | continue :next_pc; |
lib/std/debug/Dwarf.zig+307-1221| ... | ... | @@ -1,22 +1,18 @@ |
| 1 | 1 | //! Implements parsing, decoding, and caching of DWARF information. |
| 2 | 2 | //! |
| 3 | //! This API does not assume the current executable is itself the thing being | |
| 4 | //! debugged, however, it does assume the debug info has the same CPU | |
| 5 | //! architecture and OS as the current executable. It is planned to remove this | |
| 6 | //! limitation. | |
| 3 | //! This API makes no assumptions about the relationship between the host and | |
| 4 | //! the target being debugged. In other words, any DWARF information can be used | |
| 5 | //! from any host via this API. Note, however, that the limits of 32-bit | |
| 6 | //! addressing can cause very large 64-bit binaries to be impossible to open on | |
| 7 | //! 32-bit hosts. | |
| 7 | 8 | //! |
| 8 | 9 | //! For unopinionated types and bits, see `std.dwarf`. |
| 9 | 10 | |
| 10 | const builtin = @import("builtin"); | |
| 11 | const native_endian = builtin.cpu.arch.endian(); | |
| 12 | ||
| 13 | 11 | const std = @import("../std.zig"); |
| 14 | 12 | const Allocator = std.mem.Allocator; |
| 15 | const elf = std.elf; | |
| 16 | 13 | const mem = std.mem; |
| 17 | 14 | const DW = std.dwarf; |
| 18 | 15 | const AT = DW.AT; |
| 19 | const EH = DW.EH; | |
| 20 | 16 | const FORM = DW.FORM; |
| 21 | 17 | const Format = DW.Format; |
| 22 | 18 | const RLE = DW.RLE; |
| ... | ... | @@ -24,7 +20,6 @@ const UT = DW.UT; |
| 24 | 20 | const assert = std.debug.assert; |
| 25 | 21 | const cast = std.math.cast; |
| 26 | 22 | const maxInt = std.math.maxInt; |
| 27 | const Path = std.Build.Cache.Path; | |
| 28 | 23 | const ArrayList = std.ArrayList; |
| 29 | 24 | const Endian = std.builtin.Endian; |
| 30 | 25 | const Reader = std.Io.Reader; |
| ... | ... | @@ -32,15 +27,13 @@ const Reader = std.Io.Reader; |
| 32 | 27 | const Dwarf = @This(); |
| 33 | 28 | |
| 34 | 29 | pub const expression = @import("Dwarf/expression.zig"); |
| 35 | pub const abi = @import("Dwarf/abi.zig"); | |
| 36 | pub const call_frame = @import("Dwarf/call_frame.zig"); | |
| 30 | pub const Unwind = @import("Dwarf/Unwind.zig"); | |
| 31 | pub const SelfUnwinder = @import("Dwarf/SelfUnwinder.zig"); | |
| 37 | 32 | |
| 38 | 33 | /// Useful to temporarily enable while working on this file. |
| 39 | 34 | const debug_debug_mode = false; |
| 40 | 35 | |
| 41 | endian: Endian, | |
| 42 | sections: SectionArray = null_section_array, | |
| 43 | is_macho: bool, | |
| 36 | sections: SectionArray = @splat(null), | |
| 44 | 37 | |
| 45 | 38 | /// Filled later by the initializer |
| 46 | 39 | abbrev_table_list: ArrayList(Abbrev.Table) = .empty, |
| ... | ... | @@ -49,14 +42,6 @@ compile_unit_list: ArrayList(CompileUnit) = .empty, |
| 49 | 42 | /// Filled later by the initializer |
| 50 | 43 | func_list: ArrayList(Func) = .empty, |
| 51 | 44 | |
| 52 | /// Starts out non-`null` if the `.eh_frame_hdr` section is present. May become `null` later if we | |
| 53 | /// find that `.eh_frame_hdr` is incomplete. | |
| 54 | eh_frame_hdr: ?ExceptionFrameHeader = null, | |
| 55 | /// These lookup tables are only used if `eh_frame_hdr` is null | |
| 56 | cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .empty, | |
| 57 | /// Sorted by start_pc | |
| 58 | fde_list: ArrayList(FrameDescriptionEntry) = .empty, | |
| 59 | ||
| 60 | 45 | /// Populated by `populateRanges`. |
| 61 | 46 | ranges: ArrayList(Range) = .empty, |
| 62 | 47 | |
| ... | ... | @@ -69,10 +54,7 @@ pub const Range = struct { |
| 69 | 54 | |
| 70 | 55 | pub const Section = struct { |
| 71 | 56 | data: []const u8, |
| 72 | // Module-relative virtual address. | |
| 73 | // Only set if the section data was loaded from disk. | |
| 74 | virtual_address: ?usize = null, | |
| 75 | // If `data` is owned by this Dwarf. | |
| 57 | /// If `data` is owned by this Dwarf. | |
| 76 | 58 | owned: bool, |
| 77 | 59 | |
| 78 | 60 | pub const Id = enum { |
| ... | ... | @@ -87,21 +69,7 @@ pub const Section = struct { |
| 87 | 69 | debug_rnglists, |
| 88 | 70 | debug_addr, |
| 89 | 71 | debug_names, |
| 90 | debug_frame, | |
| 91 | eh_frame, | |
| 92 | eh_frame_hdr, | |
| 93 | 72 | }; |
| 94 | ||
| 95 | // For sections that are not memory mapped by the loader, this is an offset | |
| 96 | // from `data.ptr` to where the section would have been mapped. Otherwise, | |
| 97 | // `data` is directly backed by the section and the offset is zero. | |
| 98 | pub fn virtualOffset(self: Section, base_address: usize) i64 { | |
| 99 | return if (self.virtual_address) |va| | |
| 100 | @as(i64, @intCast(base_address + va)) - | |
| 101 | @as(i64, @intCast(@intFromPtr(self.data.ptr))) | |
| 102 | else | |
| 103 | 0; | |
| 104 | } | |
| 105 | 73 | }; |
| 106 | 74 | |
| 107 | 75 | pub const Abbrev = struct { |
| ... | ... | @@ -110,8 +78,8 @@ pub const Abbrev = struct { |
| 110 | 78 | has_children: bool, |
| 111 | 79 | attrs: []Attr, |
| 112 | 80 | |
| 113 | fn deinit(abbrev: *Abbrev, allocator: Allocator) void { | |
| 114 | allocator.free(abbrev.attrs); | |
| 81 | fn deinit(abbrev: *Abbrev, gpa: Allocator) void { | |
| 82 | gpa.free(abbrev.attrs); | |
| 115 | 83 | abbrev.* = undefined; |
| 116 | 84 | } |
| 117 | 85 | |
| ... | ... | @@ -127,11 +95,11 @@ pub const Abbrev = struct { |
| 127 | 95 | offset: u64, |
| 128 | 96 | abbrevs: []Abbrev, |
| 129 | 97 | |
| 130 | fn deinit(table: *Table, allocator: Allocator) void { | |
| 98 | fn deinit(table: *Table, gpa: Allocator) void { | |
| 131 | 99 | for (table.abbrevs) |*abbrev| { |
| 132 | abbrev.deinit(allocator); | |
| 100 | abbrev.deinit(gpa); | |
| 133 | 101 | } |
| 134 | allocator.free(table.abbrevs); | |
| 102 | gpa.free(table.abbrevs); | |
| 135 | 103 | table.* = undefined; |
| 136 | 104 | } |
| 137 | 105 | |
| ... | ... | @@ -146,6 +114,7 @@ pub const Abbrev = struct { |
| 146 | 114 | pub const CompileUnit = struct { |
| 147 | 115 | version: u16, |
| 148 | 116 | format: Format, |
| 117 | addr_size_bytes: u8, | |
| 149 | 118 | die: Die, |
| 150 | 119 | pc_range: ?PcRange, |
| 151 | 120 | |
| ... | ... | @@ -196,7 +165,7 @@ pub const CompileUnit = struct { |
| 196 | 165 | |
| 197 | 166 | pub const FormValue = union(enum) { |
| 198 | 167 | addr: u64, |
| 199 | addrx: usize, | |
| 168 | addrx: u64, | |
| 200 | 169 | block: []const u8, |
| 201 | 170 | udata: u64, |
| 202 | 171 | data16: *const [16]u8, |
| ... | ... | @@ -208,7 +177,7 @@ pub const FormValue = union(enum) { |
| 208 | 177 | ref_addr: u64, |
| 209 | 178 | string: [:0]const u8, |
| 210 | 179 | strp: u64, |
| 211 | strx: usize, | |
| 180 | strx: u64, | |
| 212 | 181 | line_strp: u64, |
| 213 | 182 | loclistx: u64, |
| 214 | 183 | rnglistx: u64, |
| ... | ... | @@ -243,8 +212,8 @@ pub const Die = struct { |
| 243 | 212 | value: FormValue, |
| 244 | 213 | }; |
| 245 | 214 | |
| 246 | fn deinit(self: *Die, allocator: Allocator) void { | |
| 247 | allocator.free(self.attrs); | |
| 215 | fn deinit(self: *Die, gpa: Allocator) void { | |
| 216 | gpa.free(self.attrs); | |
| 248 | 217 | self.* = undefined; |
| 249 | 218 | } |
| 250 | 219 | |
| ... | ... | @@ -258,13 +227,14 @@ pub const Die = struct { |
| 258 | 227 | fn getAttrAddr( |
| 259 | 228 | self: *const Die, |
| 260 | 229 | di: *const Dwarf, |
| 230 | endian: Endian, | |
| 261 | 231 | id: u64, |
| 262 | compile_unit: CompileUnit, | |
| 232 | compile_unit: *const CompileUnit, | |
| 263 | 233 | ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 { |
| 264 | 234 | const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; |
| 265 | 235 | return switch (form_value.*) { |
| 266 | 236 | .addr => |value| value, |
| 267 | .addrx => |index| di.readDebugAddr(compile_unit, index), | |
| 237 | .addrx => |index| di.readDebugAddr(endian, compile_unit, index), | |
| 268 | 238 | else => bad(), |
| 269 | 239 | }; |
| 270 | 240 | } |
| ... | ... | @@ -294,9 +264,10 @@ pub const Die = struct { |
| 294 | 264 | pub fn getAttrString( |
| 295 | 265 | self: *const Die, |
| 296 | 266 | di: *Dwarf, |
| 267 | endian: Endian, | |
| 297 | 268 | id: u64, |
| 298 | 269 | opt_str: ?[]const u8, |
| 299 | compile_unit: CompileUnit, | |
| 270 | compile_unit: *const CompileUnit, | |
| 300 | 271 | ) error{ InvalidDebugInfo, MissingDebugInfo }![]const u8 { |
| 301 | 272 | const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; |
| 302 | 273 | switch (form_value.*) { |
| ... | ... | @@ -309,13 +280,13 @@ pub const Die = struct { |
| 309 | 280 | .@"32" => { |
| 310 | 281 | const byte_offset = compile_unit.str_offsets_base + 4 * index; |
| 311 | 282 | if (byte_offset + 4 > debug_str_offsets.len) return bad(); |
| 312 | const offset = mem.readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); | |
| 283 | const offset = mem.readInt(u32, debug_str_offsets[@intCast(byte_offset)..][0..4], endian); | |
| 313 | 284 | return getStringGeneric(opt_str, offset); |
| 314 | 285 | }, |
| 315 | 286 | .@"64" => { |
| 316 | 287 | const byte_offset = compile_unit.str_offsets_base + 8 * index; |
| 317 | 288 | if (byte_offset + 8 > debug_str_offsets.len) return bad(); |
| 318 | const offset = mem.readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); | |
| 289 | const offset = mem.readInt(u64, debug_str_offsets[@intCast(byte_offset)..][0..8], endian); | |
| 319 | 290 | return getStringGeneric(opt_str, offset); |
| 320 | 291 | }, |
| 321 | 292 | } |
| ... | ... | @@ -326,440 +297,17 @@ pub const Die = struct { |
| 326 | 297 | } |
| 327 | 298 | }; |
| 328 | 299 | |
| 329 | /// This represents the decoded .eh_frame_hdr header | |
| 330 | pub const ExceptionFrameHeader = struct { | |
| 331 | eh_frame_ptr: usize, | |
| 332 | table_enc: u8, | |
| 333 | fde_count: usize, | |
| 334 | entries: []const u8, | |
| 335 | ||
| 336 | pub fn entrySize(table_enc: u8) !u8 { | |
| 337 | return switch (table_enc & EH.PE.type_mask) { | |
| 338 | EH.PE.udata2, | |
| 339 | EH.PE.sdata2, | |
| 340 | => 4, | |
| 341 | EH.PE.udata4, | |
| 342 | EH.PE.sdata4, | |
| 343 | => 8, | |
| 344 | EH.PE.udata8, | |
| 345 | EH.PE.sdata8, | |
| 346 | => 16, | |
| 347 | // This is a binary search table, so all entries must be the same length | |
| 348 | else => return bad(), | |
| 349 | }; | |
| 350 | } | |
| 351 | ||
| 352 | pub fn findEntry( | |
| 353 | self: ExceptionFrameHeader, | |
| 354 | eh_frame_len: usize, | |
| 355 | eh_frame_hdr_ptr: usize, | |
| 356 | pc: usize, | |
| 357 | cie: *CommonInformationEntry, | |
| 358 | fde: *FrameDescriptionEntry, | |
| 359 | endian: Endian, | |
| 360 | ) !void { | |
| 361 | const entry_size = try entrySize(self.table_enc); | |
| 362 | ||
| 363 | var left: usize = 0; | |
| 364 | var len: usize = self.fde_count; | |
| 365 | var fbr: Reader = .fixed(self.entries); | |
| 366 | ||
| 367 | while (len > 1) { | |
| 368 | const mid = left + len / 2; | |
| 369 | ||
| 370 | fbr.seek = mid * entry_size; | |
| 371 | const pc_begin = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ | |
| 372 | .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]), | |
| 373 | .follow_indirect = true, | |
| 374 | .data_rel_base = eh_frame_hdr_ptr, | |
| 375 | }, endian) orelse return bad(); | |
| 376 | ||
| 377 | if (pc < pc_begin) { | |
| 378 | len /= 2; | |
| 379 | } else { | |
| 380 | left = mid; | |
| 381 | if (pc == pc_begin) break; | |
| 382 | len -= len / 2; | |
| 383 | } | |
| 384 | } | |
| 385 | ||
| 386 | if (len == 0) return missing(); | |
| 387 | fbr.seek = left * entry_size; | |
| 388 | ||
| 389 | // Read past the pc_begin field of the entry | |
| 390 | _ = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ | |
| 391 | .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]), | |
| 392 | .follow_indirect = true, | |
| 393 | .data_rel_base = eh_frame_hdr_ptr, | |
| 394 | }, endian) orelse return bad(); | |
| 395 | ||
| 396 | const fde_ptr = cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ | |
| 397 | .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]), | |
| 398 | .follow_indirect = true, | |
| 399 | .data_rel_base = eh_frame_hdr_ptr, | |
| 400 | }, endian) orelse return bad()) orelse return bad(); | |
| 401 | ||
| 402 | if (fde_ptr < self.eh_frame_ptr) return bad(); | |
| 403 | ||
| 404 | const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0..eh_frame_len]; | |
| 405 | ||
| 406 | const fde_offset = fde_ptr - self.eh_frame_ptr; | |
| 407 | var eh_frame_fbr: Reader = .fixed(eh_frame); | |
| 408 | eh_frame_fbr.seek = fde_offset; | |
| 409 | ||
| 410 | const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame, endian); | |
| 411 | if (fde_entry_header.type != .fde) return bad(); | |
| 412 | ||
| 413 | // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable | |
| 414 | const cie_offset = fde_entry_header.type.fde; | |
| 415 | eh_frame_fbr.seek = @intCast(cie_offset); | |
| 416 | const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame, endian); | |
| 417 | if (cie_entry_header.type != .cie) return bad(); | |
| 418 | ||
| 419 | cie.* = try CommonInformationEntry.parse( | |
| 420 | cie_entry_header.entry_bytes, | |
| 421 | 0, | |
| 422 | true, | |
| 423 | cie_entry_header.format, | |
| 424 | .eh_frame, | |
| 425 | cie_entry_header.length_offset, | |
| 426 | @sizeOf(usize), | |
| 427 | endian, | |
| 428 | ); | |
| 429 | ||
| 430 | fde.* = try FrameDescriptionEntry.parse( | |
| 431 | fde_entry_header.entry_bytes, | |
| 432 | 0, | |
| 433 | true, | |
| 434 | cie.*, | |
| 435 | @sizeOf(usize), | |
| 436 | endian, | |
| 437 | ); | |
| 438 | ||
| 439 | if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return missing(); | |
| 440 | } | |
| 441 | }; | |
| 442 | ||
| 443 | pub const EntryHeader = struct { | |
| 444 | /// Offset of the length field in the backing buffer | |
| 445 | length_offset: usize, | |
| 446 | format: Format, | |
| 447 | type: union(enum) { | |
| 448 | cie, | |
| 449 | /// Value is the offset of the corresponding CIE | |
| 450 | fde: u64, | |
| 451 | terminator, | |
| 452 | }, | |
| 453 | /// The entry's contents, not including the ID field | |
| 454 | entry_bytes: []const u8, | |
| 455 | ||
| 456 | /// The length of the entry including the ID field, but not the length field itself | |
| 457 | pub fn entryLength(self: EntryHeader) usize { | |
| 458 | return self.entry_bytes.len + @as(u8, if (self.format == .@"64") 8 else 4); | |
| 459 | } | |
| 460 | ||
| 461 | /// Reads a header for either an FDE or a CIE, then advances the fbr to the | |
| 462 | /// position after the trailing structure. | |
| 463 | /// | |
| 464 | /// `fbr` must be backed by either the .eh_frame or .debug_frame sections. | |
| 465 | /// | |
| 466 | /// TODO that's a bad API, don't do that. this function should neither require | |
| 467 | /// a fixed reader nor depend on seeking. | |
| 468 | pub fn read(fbr: *Reader, dwarf_section: Section.Id, endian: Endian) !EntryHeader { | |
| 469 | assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame); | |
| 470 | ||
| 471 | const length_offset = fbr.seek; | |
| 472 | const unit_header = try readUnitHeader(fbr, endian); | |
| 473 | const unit_length = cast(usize, unit_header.unit_length) orelse return bad(); | |
| 474 | if (unit_length == 0) return .{ | |
| 475 | .length_offset = length_offset, | |
| 476 | .format = unit_header.format, | |
| 477 | .type = .terminator, | |
| 478 | .entry_bytes = &.{}, | |
| 479 | }; | |
| 480 | const start_offset = fbr.seek; | |
| 481 | const end_offset = start_offset + unit_length; | |
| 482 | defer fbr.seek = end_offset; | |
| 483 | ||
| 484 | const id = try readAddress(fbr, unit_header.format, endian); | |
| 485 | const entry_bytes = fbr.buffer[fbr.seek..end_offset]; | |
| 486 | const cie_id: u64 = switch (dwarf_section) { | |
| 487 | .eh_frame => CommonInformationEntry.eh_id, | |
| 488 | .debug_frame => switch (unit_header.format) { | |
| 489 | .@"32" => CommonInformationEntry.dwarf32_id, | |
| 490 | .@"64" => CommonInformationEntry.dwarf64_id, | |
| 491 | }, | |
| 492 | else => unreachable, | |
| 493 | }; | |
| 494 | ||
| 495 | return .{ | |
| 496 | .length_offset = length_offset, | |
| 497 | .format = unit_header.format, | |
| 498 | .type = if (id == cie_id) .cie else .{ .fde = switch (dwarf_section) { | |
| 499 | .eh_frame => try std.math.sub(u64, start_offset, id), | |
| 500 | .debug_frame => id, | |
| 501 | else => unreachable, | |
| 502 | } }, | |
| 503 | .entry_bytes = entry_bytes, | |
| 504 | }; | |
| 505 | } | |
| 506 | }; | |
| 507 | ||
| 508 | pub const CommonInformationEntry = struct { | |
| 509 | // Used in .eh_frame | |
| 510 | pub const eh_id = 0; | |
| 511 | ||
| 512 | // Used in .debug_frame (DWARF32) | |
| 513 | pub const dwarf32_id = maxInt(u32); | |
| 514 | ||
| 515 | // Used in .debug_frame (DWARF64) | |
| 516 | pub const dwarf64_id = maxInt(u64); | |
| 517 | ||
| 518 | // Offset of the length field of this entry in the eh_frame section. | |
| 519 | // This is the key that FDEs use to reference CIEs. | |
| 520 | length_offset: u64, | |
| 521 | version: u8, | |
| 522 | address_size: u8, | |
| 523 | format: Format, | |
| 524 | ||
| 525 | // Only present in version 4 | |
| 526 | segment_selector_size: ?u8, | |
| 527 | ||
| 528 | code_alignment_factor: u32, | |
| 529 | data_alignment_factor: i32, | |
| 530 | return_address_register: u8, | |
| 531 | ||
| 532 | aug_str: []const u8, | |
| 533 | aug_data: []const u8, | |
| 534 | lsda_pointer_enc: u8, | |
| 535 | personality_enc: ?u8, | |
| 536 | personality_routine_pointer: ?u64, | |
| 537 | fde_pointer_enc: u8, | |
| 538 | initial_instructions: []const u8, | |
| 539 | ||
| 540 | pub fn isSignalFrame(self: CommonInformationEntry) bool { | |
| 541 | for (self.aug_str) |c| if (c == 'S') return true; | |
| 542 | return false; | |
| 543 | } | |
| 544 | ||
| 545 | pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool { | |
| 546 | for (self.aug_str) |c| if (c == 'B') return true; | |
| 547 | return false; | |
| 548 | } | |
| 549 | ||
| 550 | pub fn mteTaggedFrame(self: CommonInformationEntry) bool { | |
| 551 | for (self.aug_str) |c| if (c == 'G') return true; | |
| 552 | return false; | |
| 553 | } | |
| 554 | ||
| 555 | /// This function expects to read the CIE starting with the version field. | |
| 556 | /// The returned struct references memory backed by cie_bytes. | |
| 557 | /// | |
| 558 | /// See the FrameDescriptionEntry.parse documentation for the description | |
| 559 | /// of `pc_rel_offset` and `is_runtime`. | |
| 560 | /// | |
| 561 | /// `length_offset` specifies the offset of this CIE's length field in the | |
| 562 | /// .eh_frame / .debug_frame section. | |
| 563 | pub fn parse( | |
| 564 | cie_bytes: []const u8, | |
| 565 | pc_rel_offset: i64, | |
| 566 | is_runtime: bool, | |
| 567 | format: Format, | |
| 568 | dwarf_section: Section.Id, | |
| 569 | length_offset: u64, | |
| 570 | addr_size_bytes: u8, | |
| 571 | endian: Endian, | |
| 572 | ) !CommonInformationEntry { | |
| 573 | if (addr_size_bytes > 8) return error.UnsupportedAddrSize; | |
| 574 | ||
| 575 | var fbr: Reader = .fixed(cie_bytes); | |
| 576 | ||
| 577 | const version = try fbr.takeByte(); | |
| 578 | switch (dwarf_section) { | |
| 579 | .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion, | |
| 580 | .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion, | |
| 581 | else => return error.UnsupportedDwarfSection, | |
| 582 | } | |
| 583 | ||
| 584 | var has_eh_data = false; | |
| 585 | var has_aug_data = false; | |
| 586 | ||
| 587 | var aug_str_len: usize = 0; | |
| 588 | const aug_str_start = fbr.seek; | |
| 589 | var aug_byte = try fbr.takeByte(); | |
| 590 | while (aug_byte != 0) : (aug_byte = try fbr.takeByte()) { | |
| 591 | switch (aug_byte) { | |
| 592 | 'z' => { | |
| 593 | if (aug_str_len != 0) return bad(); | |
| 594 | has_aug_data = true; | |
| 595 | }, | |
| 596 | 'e' => { | |
| 597 | if (has_aug_data or aug_str_len != 0) return bad(); | |
| 598 | if (try fbr.takeByte() != 'h') return bad(); | |
| 599 | has_eh_data = true; | |
| 600 | }, | |
| 601 | else => if (has_eh_data) return bad(), | |
| 602 | } | |
| 603 | ||
| 604 | aug_str_len += 1; | |
| 605 | } | |
| 606 | ||
| 607 | if (has_eh_data) { | |
| 608 | // legacy data created by older versions of gcc - unsupported here | |
| 609 | for (0..addr_size_bytes) |_| _ = try fbr.takeByte(); | |
| 610 | } | |
| 611 | ||
| 612 | const address_size = if (version == 4) try fbr.takeByte() else addr_size_bytes; | |
| 613 | const segment_selector_size = if (version == 4) try fbr.takeByte() else null; | |
| 614 | ||
| 615 | const code_alignment_factor = try fbr.takeLeb128(u32); | |
| 616 | const data_alignment_factor = try fbr.takeLeb128(i32); | |
| 617 | const return_address_register = if (version == 1) try fbr.takeByte() else try fbr.takeLeb128(u8); | |
| 618 | ||
| 619 | var lsda_pointer_enc: u8 = EH.PE.omit; | |
| 620 | var personality_enc: ?u8 = null; | |
| 621 | var personality_routine_pointer: ?u64 = null; | |
| 622 | var fde_pointer_enc: u8 = EH.PE.absptr; | |
| 623 | ||
| 624 | var aug_data: []const u8 = &[_]u8{}; | |
| 625 | const aug_str = if (has_aug_data) blk: { | |
| 626 | const aug_data_len = try fbr.takeLeb128(usize); | |
| 627 | const aug_data_start = fbr.seek; | |
| 628 | aug_data = cie_bytes[aug_data_start..][0..aug_data_len]; | |
| 629 | ||
| 630 | const aug_str = cie_bytes[aug_str_start..][0..aug_str_len]; | |
| 631 | for (aug_str[1..]) |byte| { | |
| 632 | switch (byte) { | |
| 633 | 'L' => { | |
| 634 | lsda_pointer_enc = try fbr.takeByte(); | |
| 635 | }, | |
| 636 | 'P' => { | |
| 637 | personality_enc = try fbr.takeByte(); | |
| 638 | personality_routine_pointer = try readEhPointer(&fbr, personality_enc.?, addr_size_bytes, .{ | |
| 639 | .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[fbr.seek]), pc_rel_offset), | |
| 640 | .follow_indirect = is_runtime, | |
| 641 | }, endian); | |
| 642 | }, | |
| 643 | 'R' => { | |
| 644 | fde_pointer_enc = try fbr.takeByte(); | |
| 645 | }, | |
| 646 | 'S', 'B', 'G' => {}, | |
| 647 | else => return bad(), | |
| 648 | } | |
| 649 | } | |
| 650 | ||
| 651 | // aug_data_len can include padding so the CIE ends on an address boundary | |
| 652 | fbr.seek = aug_data_start + aug_data_len; | |
| 653 | break :blk aug_str; | |
| 654 | } else &[_]u8{}; | |
| 655 | ||
| 656 | const initial_instructions = cie_bytes[fbr.seek..]; | |
| 657 | return .{ | |
| 658 | .length_offset = length_offset, | |
| 659 | .version = version, | |
| 660 | .address_size = address_size, | |
| 661 | .format = format, | |
| 662 | .segment_selector_size = segment_selector_size, | |
| 663 | .code_alignment_factor = code_alignment_factor, | |
| 664 | .data_alignment_factor = data_alignment_factor, | |
| 665 | .return_address_register = return_address_register, | |
| 666 | .aug_str = aug_str, | |
| 667 | .aug_data = aug_data, | |
| 668 | .lsda_pointer_enc = lsda_pointer_enc, | |
| 669 | .personality_enc = personality_enc, | |
| 670 | .personality_routine_pointer = personality_routine_pointer, | |
| 671 | .fde_pointer_enc = fde_pointer_enc, | |
| 672 | .initial_instructions = initial_instructions, | |
| 673 | }; | |
| 674 | } | |
| 675 | }; | |
| 676 | ||
| 677 | pub const FrameDescriptionEntry = struct { | |
| 678 | // Offset into eh_frame where the CIE for this FDE is stored | |
| 679 | cie_length_offset: u64, | |
| 680 | ||
| 681 | pc_begin: u64, | |
| 682 | pc_range: u64, | |
| 683 | lsda_pointer: ?u64, | |
| 684 | aug_data: []const u8, | |
| 685 | instructions: []const u8, | |
| 686 | ||
| 687 | /// This function expects to read the FDE starting at the PC Begin field. | |
| 688 | /// The returned struct references memory backed by `fde_bytes`. | |
| 689 | /// | |
| 690 | /// `pc_rel_offset` specifies an offset to be applied to pc_rel_base values | |
| 691 | /// used when decoding pointers. This should be set to zero if fde_bytes is | |
| 692 | /// backed by the memory of a .eh_frame / .debug_frame section in the running executable. | |
| 693 | /// Otherwise, it should be the relative offset to translate addresses from | |
| 694 | /// where the section is currently stored in memory, to where it *would* be | |
| 695 | /// stored at runtime: section base addr - backing data base ptr. | |
| 696 | /// | |
| 697 | /// Similarly, `is_runtime` specifies this function is being called on a runtime | |
| 698 | /// section, and so indirect pointers can be followed. | |
| 699 | pub fn parse( | |
| 700 | fde_bytes: []const u8, | |
| 701 | pc_rel_offset: i64, | |
| 702 | is_runtime: bool, | |
| 703 | cie: CommonInformationEntry, | |
| 704 | addr_size_bytes: u8, | |
| 705 | endian: Endian, | |
| 706 | ) !FrameDescriptionEntry { | |
| 707 | if (addr_size_bytes > 8) return error.InvalidAddrSize; | |
| 708 | ||
| 709 | var fbr: Reader = .fixed(fde_bytes); | |
| 710 | ||
| 711 | const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ | |
| 712 | .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset), | |
| 713 | .follow_indirect = is_runtime, | |
| 714 | }, endian) orelse return bad(); | |
| 715 | ||
| 716 | const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ | |
| 717 | .pc_rel_base = 0, | |
| 718 | .follow_indirect = false, | |
| 719 | }, endian) orelse return bad(); | |
| 720 | ||
| 721 | var aug_data: []const u8 = &[_]u8{}; | |
| 722 | const lsda_pointer = if (cie.aug_str.len > 0) blk: { | |
| 723 | const aug_data_len = try fbr.takeLeb128(usize); | |
| 724 | const aug_data_start = fbr.seek; | |
| 725 | aug_data = fde_bytes[aug_data_start..][0..aug_data_len]; | |
| 726 | ||
| 727 | const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit) | |
| 728 | try readEhPointer(&fbr, cie.lsda_pointer_enc, addr_size_bytes, .{ | |
| 729 | .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset), | |
| 730 | .follow_indirect = is_runtime, | |
| 731 | }, endian) | |
| 732 | else | |
| 733 | null; | |
| 734 | ||
| 735 | fbr.seek = aug_data_start + aug_data_len; | |
| 736 | break :blk lsda_pointer; | |
| 737 | } else null; | |
| 738 | ||
| 739 | const instructions = fde_bytes[fbr.seek..]; | |
| 740 | return .{ | |
| 741 | .cie_length_offset = cie.length_offset, | |
| 742 | .pc_begin = pc_begin, | |
| 743 | .pc_range = pc_range, | |
| 744 | .lsda_pointer = lsda_pointer, | |
| 745 | .aug_data = aug_data, | |
| 746 | .instructions = instructions, | |
| 747 | }; | |
| 748 | } | |
| 749 | }; | |
| 750 | ||
| 751 | 300 | const num_sections = std.enums.directEnumArrayLen(Section.Id, 0); |
| 752 | 301 | pub const SectionArray = [num_sections]?Section; |
| 753 | pub const null_section_array = [_]?Section{null} ** num_sections; | |
| 754 | 302 | |
| 755 | 303 | pub const OpenError = ScanError; |
| 756 | 304 | |
| 757 | 305 | /// Initialize DWARF info. The caller has the responsibility to initialize most |
| 758 | 306 | /// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the |
| 759 | 307 | /// main binary file (not the secondary debug info file). |
| 760 | pub fn open(d: *Dwarf, gpa: Allocator) OpenError!void { | |
| 761 | try d.scanAllFunctions(gpa); | |
| 762 | try d.scanAllCompileUnits(gpa); | |
| 308 | pub fn open(d: *Dwarf, gpa: Allocator, endian: Endian) OpenError!void { | |
| 309 | try d.scanAllFunctions(gpa, endian); | |
| 310 | try d.scanAllCompileUnits(gpa, endian); | |
| 763 | 311 | } |
| 764 | 312 | |
| 765 | 313 | const PcRange = struct { |
| ... | ... | @@ -776,10 +324,6 @@ pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 { |
| 776 | 324 | return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null; |
| 777 | 325 | } |
| 778 | 326 | |
| 779 | pub fn sectionVirtualOffset(di: Dwarf, dwarf_section: Section.Id, base_address: usize) ?i64 { | |
| 780 | return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null; | |
| 781 | } | |
| 782 | ||
| 783 | 327 | pub fn deinit(di: *Dwarf, gpa: Allocator) void { |
| 784 | 328 | for (di.sections) |opt_section| { |
| 785 | 329 | if (opt_section) |s| if (s.owned) gpa.free(s.data); |
| ... | ... | @@ -798,14 +342,18 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void { |
| 798 | 342 | } |
| 799 | 343 | di.compile_unit_list.deinit(gpa); |
| 800 | 344 | di.func_list.deinit(gpa); |
| 801 | di.cie_map.deinit(gpa); | |
| 802 | di.fde_list.deinit(gpa); | |
| 803 | 345 | di.ranges.deinit(gpa); |
| 804 | 346 | di.* = undefined; |
| 805 | 347 | } |
| 806 | 348 | |
| 807 | pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { | |
| 808 | for (di.func_list.items) |*func| { | |
| 349 | pub fn getSymbolName(di: *const Dwarf, address: u64) ?[]const u8 { | |
| 350 | // Iterate the function list backwards so that we see child DIEs before their parents. This is | |
| 351 | // important because `DW_TAG_inlined_subroutine` DIEs will have a range which is a sub-range of | |
| 352 | // their caller, and we want to return the callee's name, not the caller's. | |
| 353 | var i: usize = di.func_list.items.len; | |
| 354 | while (i > 0) { | |
| 355 | i -= 1; | |
| 356 | const func = &di.func_list.items[i]; | |
| 809 | 357 | if (func.pc_range) |range| { |
| 810 | 358 | if (address >= range.start and address < range.end) { |
| 811 | 359 | return func.name; |
| ... | ... | @@ -825,35 +373,33 @@ pub const ScanError = error{ |
| 825 | 373 | StreamTooLong, |
| 826 | 374 | } || Allocator.Error; |
| 827 | 375 | |
| 828 | fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { | |
| 829 | const endian = di.endian; | |
| 830 | var fbr: Reader = .fixed(di.section(.debug_info).?); | |
| 376 | fn scanAllFunctions(di: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void { | |
| 377 | var fr: Reader = .fixed(di.section(.debug_info).?); | |
| 831 | 378 | var this_unit_offset: u64 = 0; |
| 832 | 379 | |
| 833 | while (this_unit_offset < fbr.buffer.len) { | |
| 834 | fbr.seek = @intCast(this_unit_offset); | |
| 380 | while (this_unit_offset < fr.buffer.len) { | |
| 381 | fr.seek = @intCast(this_unit_offset); | |
| 835 | 382 | |
| 836 | const unit_header = try readUnitHeader(&fbr, endian); | |
| 383 | const unit_header = try readUnitHeader(&fr, endian); | |
| 837 | 384 | if (unit_header.unit_length == 0) return; |
| 838 | 385 | const next_offset = unit_header.header_length + unit_header.unit_length; |
| 839 | 386 | |
| 840 | const version = try fbr.takeInt(u16, endian); | |
| 387 | const version = try fr.takeInt(u16, endian); | |
| 841 | 388 | if (version < 2 or version > 5) return bad(); |
| 842 | 389 | |
| 843 | 390 | var address_size: u8 = undefined; |
| 844 | 391 | var debug_abbrev_offset: u64 = undefined; |
| 845 | 392 | if (version >= 5) { |
| 846 | const unit_type = try fbr.takeByte(); | |
| 393 | const unit_type = try fr.takeByte(); | |
| 847 | 394 | if (unit_type != DW.UT.compile) return bad(); |
| 848 | address_size = try fbr.takeByte(); | |
| 849 | debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian); | |
| 395 | address_size = try fr.takeByte(); | |
| 396 | debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian); | |
| 850 | 397 | } else { |
| 851 | debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian); | |
| 852 | address_size = try fbr.takeByte(); | |
| 398 | debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian); | |
| 399 | address_size = try fr.takeByte(); | |
| 853 | 400 | } |
| 854 | if (address_size != @sizeOf(usize)) return bad(); | |
| 855 | 401 | |
| 856 | const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); | |
| 402 | const abbrev_table = try di.getAbbrevTable(gpa, debug_abbrev_offset); | |
| 857 | 403 | |
| 858 | 404 | var max_attrs: usize = 0; |
| 859 | 405 | var zig_padding_abbrev_code: u7 = 0; |
| ... | ... | @@ -868,8 +414,8 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 868 | 414 | } |
| 869 | 415 | } |
| 870 | 416 | } |
| 871 | const attrs_buf = try allocator.alloc(Die.Attr, max_attrs * 3); | |
| 872 | defer allocator.free(attrs_buf); | |
| 417 | const attrs_buf = try gpa.alloc(Die.Attr, max_attrs * 3); | |
| 418 | defer gpa.free(attrs_buf); | |
| 873 | 419 | var attrs_bufs: [3][]Die.Attr = undefined; |
| 874 | 420 | for (&attrs_bufs, 0..) |*buf, index| buf.* = attrs_buf[index * max_attrs ..][0..max_attrs]; |
| 875 | 421 | |
| ... | ... | @@ -878,6 +424,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 878 | 424 | var compile_unit: CompileUnit = .{ |
| 879 | 425 | .version = version, |
| 880 | 426 | .format = unit_header.format, |
| 427 | .addr_size_bytes = address_size, | |
| 881 | 428 | .die = undefined, |
| 882 | 429 | .pc_range = null, |
| 883 | 430 | |
| ... | ... | @@ -890,16 +437,17 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 890 | 437 | }; |
| 891 | 438 | |
| 892 | 439 | while (true) { |
| 893 | fbr.seek = std.mem.indexOfNonePos(u8, fbr.buffer, fbr.seek, &.{ | |
| 440 | fr.seek = std.mem.indexOfNonePos(u8, fr.buffer, fr.seek, &.{ | |
| 894 | 441 | zig_padding_abbrev_code, 0, |
| 895 | }) orelse fbr.buffer.len; | |
| 896 | if (fbr.seek >= next_unit_pos) break; | |
| 442 | }) orelse fr.buffer.len; | |
| 443 | if (fr.seek >= next_unit_pos) break; | |
| 897 | 444 | var die_obj = (try parseDie( |
| 898 | &fbr, | |
| 445 | &fr, | |
| 899 | 446 | attrs_bufs[0], |
| 900 | 447 | abbrev_table, |
| 901 | 448 | unit_header.format, |
| 902 | 449 | endian, |
| 450 | address_size, | |
| 903 | 451 | )) orelse continue; |
| 904 | 452 | |
| 905 | 453 | switch (die_obj.tag_id) { |
| ... | ... | @@ -920,34 +468,36 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 920 | 468 | // Prevent endless loops |
| 921 | 469 | for (0..3) |_| { |
| 922 | 470 | if (this_die_obj.getAttr(AT.name)) |_| { |
| 923 | break :x try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit); | |
| 471 | break :x try this_die_obj.getAttrString(di, endian, AT.name, di.section(.debug_str), &compile_unit); | |
| 924 | 472 | } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| { |
| 925 | const after_die_offset = fbr.seek; | |
| 926 | defer fbr.seek = after_die_offset; | |
| 473 | const after_die_offset = fr.seek; | |
| 474 | defer fr.seek = after_die_offset; | |
| 927 | 475 | |
| 928 | 476 | // Follow the DIE it points to and repeat |
| 929 | 477 | const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin, this_unit_offset, next_offset); |
| 930 | fbr.seek = @intCast(ref_offset); | |
| 478 | fr.seek = @intCast(ref_offset); | |
| 931 | 479 | this_die_obj = (try parseDie( |
| 932 | &fbr, | |
| 480 | &fr, | |
| 933 | 481 | attrs_bufs[2], |
| 934 | 482 | abbrev_table, // wrong abbrev table for different cu |
| 935 | 483 | unit_header.format, |
| 936 | 484 | endian, |
| 485 | address_size, | |
| 937 | 486 | )) orelse return bad(); |
| 938 | 487 | } else if (this_die_obj.getAttr(AT.specification)) |_| { |
| 939 | const after_die_offset = fbr.seek; | |
| 940 | defer fbr.seek = after_die_offset; | |
| 488 | const after_die_offset = fr.seek; | |
| 489 | defer fr.seek = after_die_offset; | |
| 941 | 490 | |
| 942 | 491 | // Follow the DIE it points to and repeat |
| 943 | 492 | const ref_offset = try this_die_obj.getAttrRef(AT.specification, this_unit_offset, next_offset); |
| 944 | fbr.seek = @intCast(ref_offset); | |
| 493 | fr.seek = @intCast(ref_offset); | |
| 945 | 494 | this_die_obj = (try parseDie( |
| 946 | &fbr, | |
| 495 | &fr, | |
| 947 | 496 | attrs_bufs[2], |
| 948 | 497 | abbrev_table, // wrong abbrev table for different cu |
| 949 | 498 | unit_header.format, |
| 950 | 499 | endian, |
| 500 | address_size, | |
| 951 | 501 | )) orelse return bad(); |
| 952 | 502 | } else { |
| 953 | 503 | break :x null; |
| ... | ... | @@ -957,7 +507,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 957 | 507 | break :x null; |
| 958 | 508 | }; |
| 959 | 509 | |
| 960 | var range_added = if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| blk: { | |
| 510 | var range_added = if (die_obj.getAttrAddr(di, endian, AT.low_pc, &compile_unit)) |low_pc| blk: { | |
| 961 | 511 | if (die_obj.getAttr(AT.high_pc)) |high_pc_value| { |
| 962 | 512 | const pc_end = switch (high_pc_value.*) { |
| 963 | 513 | .addr => |value| value, |
| ... | ... | @@ -965,7 +515,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 965 | 515 | else => return bad(), |
| 966 | 516 | }; |
| 967 | 517 | |
| 968 | try di.func_list.append(allocator, .{ | |
| 518 | try di.func_list.append(gpa, .{ | |
| 969 | 519 | .name = fn_name, |
| 970 | 520 | .pc_range = .{ |
| 971 | 521 | .start = low_pc, |
| ... | ... | @@ -983,14 +533,14 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 983 | 533 | }; |
| 984 | 534 | |
| 985 | 535 | if (die_obj.getAttr(AT.ranges)) |ranges_value| blk: { |
| 986 | var iter = DebugRangeIterator.init(ranges_value, di, &compile_unit) catch |err| { | |
| 536 | var iter = DebugRangeIterator.init(ranges_value, di, endian, &compile_unit) catch |err| { | |
| 987 | 537 | if (err != error.MissingDebugInfo) return err; |
| 988 | 538 | break :blk; |
| 989 | 539 | }; |
| 990 | 540 | |
| 991 | 541 | while (try iter.next()) |range| { |
| 992 | 542 | range_added = true; |
| 993 | try di.func_list.append(allocator, .{ | |
| 543 | try di.func_list.append(gpa, .{ | |
| 994 | 544 | .name = fn_name, |
| 995 | 545 | .pc_range = .{ |
| 996 | 546 | .start = range.start, |
| ... | ... | @@ -1001,7 +551,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 1001 | 551 | } |
| 1002 | 552 | |
| 1003 | 553 | if (fn_name != null and !range_added) { |
| 1004 | try di.func_list.append(allocator, .{ | |
| 554 | try di.func_list.append(gpa, .{ | |
| 1005 | 555 | .name = fn_name, |
| 1006 | 556 | .pc_range = null, |
| 1007 | 557 | }); |
| ... | ... | @@ -1015,38 +565,36 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 1015 | 565 | } |
| 1016 | 566 | } |
| 1017 | 567 | |
| 1018 | fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { | |
| 1019 | const endian = di.endian; | |
| 1020 | var fbr: Reader = .fixed(di.section(.debug_info).?); | |
| 568 | fn scanAllCompileUnits(di: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void { | |
| 569 | var fr: Reader = .fixed(di.section(.debug_info).?); | |
| 1021 | 570 | var this_unit_offset: u64 = 0; |
| 1022 | 571 | |
| 1023 | var attrs_buf = std.array_list.Managed(Die.Attr).init(allocator); | |
| 572 | var attrs_buf = std.array_list.Managed(Die.Attr).init(gpa); | |
| 1024 | 573 | defer attrs_buf.deinit(); |
| 1025 | 574 | |
| 1026 | while (this_unit_offset < fbr.buffer.len) { | |
| 1027 | fbr.seek = @intCast(this_unit_offset); | |
| 575 | while (this_unit_offset < fr.buffer.len) { | |
| 576 | fr.seek = @intCast(this_unit_offset); | |
| 1028 | 577 | |
| 1029 | const unit_header = try readUnitHeader(&fbr, endian); | |
| 578 | const unit_header = try readUnitHeader(&fr, endian); | |
| 1030 | 579 | if (unit_header.unit_length == 0) return; |
| 1031 | 580 | const next_offset = unit_header.header_length + unit_header.unit_length; |
| 1032 | 581 | |
| 1033 | const version = try fbr.takeInt(u16, endian); | |
| 582 | const version = try fr.takeInt(u16, endian); | |
| 1034 | 583 | if (version < 2 or version > 5) return bad(); |
| 1035 | 584 | |
| 1036 | 585 | var address_size: u8 = undefined; |
| 1037 | 586 | var debug_abbrev_offset: u64 = undefined; |
| 1038 | 587 | if (version >= 5) { |
| 1039 | const unit_type = try fbr.takeByte(); | |
| 588 | const unit_type = try fr.takeByte(); | |
| 1040 | 589 | if (unit_type != UT.compile) return bad(); |
| 1041 | address_size = try fbr.takeByte(); | |
| 1042 | debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian); | |
| 590 | address_size = try fr.takeByte(); | |
| 591 | debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian); | |
| 1043 | 592 | } else { |
| 1044 | debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian); | |
| 1045 | address_size = try fbr.takeByte(); | |
| 593 | debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian); | |
| 594 | address_size = try fr.takeByte(); | |
| 1046 | 595 | } |
| 1047 | if (address_size != @sizeOf(usize)) return bad(); | |
| 1048 | 596 | |
| 1049 | const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); | |
| 597 | const abbrev_table = try di.getAbbrevTable(gpa, debug_abbrev_offset); | |
| 1050 | 598 | |
| 1051 | 599 | var max_attrs: usize = 0; |
| 1052 | 600 | for (abbrev_table.abbrevs) |abbrev| { |
| ... | ... | @@ -1055,20 +603,22 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 1055 | 603 | try attrs_buf.resize(max_attrs); |
| 1056 | 604 | |
| 1057 | 605 | var compile_unit_die = (try parseDie( |
| 1058 | &fbr, | |
| 606 | &fr, | |
| 1059 | 607 | attrs_buf.items, |
| 1060 | 608 | abbrev_table, |
| 1061 | 609 | unit_header.format, |
| 1062 | 610 | endian, |
| 611 | address_size, | |
| 1063 | 612 | )) orelse return bad(); |
| 1064 | 613 | |
| 1065 | 614 | if (compile_unit_die.tag_id != DW.TAG.compile_unit) return bad(); |
| 1066 | 615 | |
| 1067 | compile_unit_die.attrs = try allocator.dupe(Die.Attr, compile_unit_die.attrs); | |
| 616 | compile_unit_die.attrs = try gpa.dupe(Die.Attr, compile_unit_die.attrs); | |
| 1068 | 617 | |
| 1069 | 618 | var compile_unit: CompileUnit = .{ |
| 1070 | 619 | .version = version, |
| 1071 | 620 | .format = unit_header.format, |
| 621 | .addr_size_bytes = address_size, | |
| 1072 | 622 | .pc_range = null, |
| 1073 | 623 | .die = compile_unit_die, |
| 1074 | 624 | .str_offsets_base = if (compile_unit_die.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0, |
| ... | ... | @@ -1080,7 +630,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 1080 | 630 | }; |
| 1081 | 631 | |
| 1082 | 632 | compile_unit.pc_range = x: { |
| 1083 | if (compile_unit_die.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| { | |
| 633 | if (compile_unit_die.getAttrAddr(di, endian, AT.low_pc, &compile_unit)) |low_pc| { | |
| 1084 | 634 | if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| { |
| 1085 | 635 | const pc_end = switch (high_pc_value.*) { |
| 1086 | 636 | .addr => |value| value, |
| ... | ... | @@ -1100,13 +650,13 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 1100 | 650 | } |
| 1101 | 651 | }; |
| 1102 | 652 | |
| 1103 | try di.compile_unit_list.append(allocator, compile_unit); | |
| 653 | try di.compile_unit_list.append(gpa, compile_unit); | |
| 1104 | 654 | |
| 1105 | 655 | this_unit_offset += next_offset; |
| 1106 | 656 | } |
| 1107 | 657 | } |
| 1108 | 658 | |
| 1109 | pub fn populateRanges(d: *Dwarf, gpa: Allocator) ScanError!void { | |
| 659 | pub fn populateRanges(d: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void { | |
| 1110 | 660 | assert(d.ranges.items.len == 0); |
| 1111 | 661 | |
| 1112 | 662 | for (d.compile_unit_list.items, 0..) |*cu, cu_index| { |
| ... | ... | @@ -1119,7 +669,7 @@ pub fn populateRanges(d: *Dwarf, gpa: Allocator) ScanError!void { |
| 1119 | 669 | continue; |
| 1120 | 670 | } |
| 1121 | 671 | const ranges_value = cu.die.getAttr(AT.ranges) orelse continue; |
| 1122 | var iter = DebugRangeIterator.init(ranges_value, d, cu) catch continue; | |
| 672 | var iter = DebugRangeIterator.init(ranges_value, d, endian, cu) catch continue; | |
| 1123 | 673 | while (try iter.next()) |range| { |
| 1124 | 674 | // Not sure why LLVM thinks it's OK to emit these... |
| 1125 | 675 | if (range.start == range.end) continue; |
| ... | ... | @@ -1144,10 +694,11 @@ const DebugRangeIterator = struct { |
| 1144 | 694 | base_address: u64, |
| 1145 | 695 | section_type: Section.Id, |
| 1146 | 696 | di: *const Dwarf, |
| 697 | endian: Endian, | |
| 1147 | 698 | compile_unit: *const CompileUnit, |
| 1148 | fbr: Reader, | |
| 699 | fr: Reader, | |
| 1149 | 700 | |
| 1150 | pub fn init(ranges_value: *const FormValue, di: *const Dwarf, compile_unit: *const CompileUnit) !@This() { | |
| 701 | pub fn init(ranges_value: *const FormValue, di: *const Dwarf, endian: Endian, compile_unit: *const CompileUnit) !@This() { | |
| 1151 | 702 | const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges; |
| 1152 | 703 | const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo; |
| 1153 | 704 | |
| ... | ... | @@ -1156,15 +707,15 @@ const DebugRangeIterator = struct { |
| 1156 | 707 | .rnglistx => |idx| off: { |
| 1157 | 708 | switch (compile_unit.format) { |
| 1158 | 709 | .@"32" => { |
| 1159 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx)); | |
| 710 | const offset_loc = compile_unit.rnglists_base + 4 * idx; | |
| 1160 | 711 | if (offset_loc + 4 > debug_ranges.len) return bad(); |
| 1161 | const offset = mem.readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); | |
| 712 | const offset = mem.readInt(u32, debug_ranges[@intCast(offset_loc)..][0..4], endian); | |
| 1162 | 713 | break :off compile_unit.rnglists_base + offset; |
| 1163 | 714 | }, |
| 1164 | 715 | .@"64" => { |
| 1165 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx)); | |
| 716 | const offset_loc = compile_unit.rnglists_base + 8 * idx; | |
| 1166 | 717 | if (offset_loc + 8 > debug_ranges.len) return bad(); |
| 1167 | const offset = mem.readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); | |
| 718 | const offset = mem.readInt(u64, debug_ranges[@intCast(offset_loc)..][0..8], endian); | |
| 1168 | 719 | break :off compile_unit.rnglists_base + offset; |
| 1169 | 720 | }, |
| 1170 | 721 | } |
| ... | ... | @@ -1176,42 +727,44 @@ const DebugRangeIterator = struct { |
| 1176 | 727 | // specified by DW_AT.low_pc or to some other value encoded |
| 1177 | 728 | // in the list itself. |
| 1178 | 729 | // If no starting value is specified use zero. |
| 1179 | const base_address = compile_unit.die.getAttrAddr(di, AT.low_pc, compile_unit.*) catch |err| switch (err) { | |
| 730 | const base_address = compile_unit.die.getAttrAddr(di, endian, AT.low_pc, compile_unit) catch |err| switch (err) { | |
| 1180 | 731 | error.MissingDebugInfo => 0, |
| 1181 | 732 | else => return err, |
| 1182 | 733 | }; |
| 1183 | 734 | |
| 1184 | var fbr: Reader = .fixed(debug_ranges); | |
| 1185 | fbr.seek = cast(usize, ranges_offset) orelse return bad(); | |
| 735 | var fr: Reader = .fixed(debug_ranges); | |
| 736 | fr.seek = cast(usize, ranges_offset) orelse return bad(); | |
| 1186 | 737 | |
| 1187 | 738 | return .{ |
| 1188 | 739 | .base_address = base_address, |
| 1189 | 740 | .section_type = section_type, |
| 1190 | 741 | .di = di, |
| 742 | .endian = endian, | |
| 1191 | 743 | .compile_unit = compile_unit, |
| 1192 | .fbr = fbr, | |
| 744 | .fr = fr, | |
| 1193 | 745 | }; |
| 1194 | 746 | } |
| 1195 | 747 | |
| 1196 | 748 | // Returns the next range in the list, or null if the end was reached. |
| 1197 | 749 | pub fn next(self: *@This()) !?PcRange { |
| 1198 | const endian = self.di.endian; | |
| 750 | const endian = self.endian; | |
| 751 | const addr_size_bytes = self.compile_unit.addr_size_bytes; | |
| 1199 | 752 | switch (self.section_type) { |
| 1200 | 753 | .debug_rnglists => { |
| 1201 | const kind = try self.fbr.takeByte(); | |
| 754 | const kind = try self.fr.takeByte(); | |
| 1202 | 755 | switch (kind) { |
| 1203 | 756 | RLE.end_of_list => return null, |
| 1204 | 757 | RLE.base_addressx => { |
| 1205 | const index = try self.fbr.takeLeb128(usize); | |
| 1206 | self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index); | |
| 758 | const index = try self.fr.takeLeb128(u64); | |
| 759 | self.base_address = try self.di.readDebugAddr(endian, self.compile_unit, index); | |
| 1207 | 760 | return try self.next(); |
| 1208 | 761 | }, |
| 1209 | 762 | RLE.startx_endx => { |
| 1210 | const start_index = try self.fbr.takeLeb128(usize); | |
| 1211 | const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); | |
| 763 | const start_index = try self.fr.takeLeb128(u64); | |
| 764 | const start_addr = try self.di.readDebugAddr(endian, self.compile_unit, start_index); | |
| 1212 | 765 | |
| 1213 | const end_index = try self.fbr.takeLeb128(usize); | |
| 1214 | const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index); | |
| 766 | const end_index = try self.fr.takeLeb128(u64); | |
| 767 | const end_addr = try self.di.readDebugAddr(endian, self.compile_unit, end_index); | |
| 1215 | 768 | |
| 1216 | 769 | return .{ |
| 1217 | 770 | .start = start_addr, |
| ... | ... | @@ -1219,10 +772,10 @@ const DebugRangeIterator = struct { |
| 1219 | 772 | }; |
| 1220 | 773 | }, |
| 1221 | 774 | RLE.startx_length => { |
| 1222 | const start_index = try self.fbr.takeLeb128(usize); | |
| 1223 | const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); | |
| 775 | const start_index = try self.fr.takeLeb128(u64); | |
| 776 | const start_addr = try self.di.readDebugAddr(endian, self.compile_unit, start_index); | |
| 1224 | 777 | |
| 1225 | const len = try self.fbr.takeLeb128(usize); | |
| 778 | const len = try self.fr.takeLeb128(u64); | |
| 1226 | 779 | const end_addr = start_addr + len; |
| 1227 | 780 | |
| 1228 | 781 | return .{ |
| ... | ... | @@ -1231,8 +784,8 @@ const DebugRangeIterator = struct { |
| 1231 | 784 | }; |
| 1232 | 785 | }, |
| 1233 | 786 | RLE.offset_pair => { |
| 1234 | const start_addr = try self.fbr.takeLeb128(usize); | |
| 1235 | const end_addr = try self.fbr.takeLeb128(usize); | |
| 787 | const start_addr = try self.fr.takeLeb128(u64); | |
| 788 | const end_addr = try self.fr.takeLeb128(u64); | |
| 1236 | 789 | |
| 1237 | 790 | // This is the only kind that uses the base address |
| 1238 | 791 | return .{ |
| ... | ... | @@ -1241,12 +794,12 @@ const DebugRangeIterator = struct { |
| 1241 | 794 | }; |
| 1242 | 795 | }, |
| 1243 | 796 | RLE.base_address => { |
| 1244 | self.base_address = try self.fbr.takeInt(usize, endian); | |
| 797 | self.base_address = try readAddress(&self.fr, endian, addr_size_bytes); | |
| 1245 | 798 | return try self.next(); |
| 1246 | 799 | }, |
| 1247 | 800 | RLE.start_end => { |
| 1248 | const start_addr = try self.fbr.takeInt(usize, endian); | |
| 1249 | const end_addr = try self.fbr.takeInt(usize, endian); | |
| 801 | const start_addr = try readAddress(&self.fr, endian, addr_size_bytes); | |
| 802 | const end_addr = try readAddress(&self.fr, endian, addr_size_bytes); | |
| 1250 | 803 | |
| 1251 | 804 | return .{ |
| 1252 | 805 | .start = start_addr, |
| ... | ... | @@ -1254,8 +807,8 @@ const DebugRangeIterator = struct { |
| 1254 | 807 | }; |
| 1255 | 808 | }, |
| 1256 | 809 | RLE.start_length => { |
| 1257 | const start_addr = try self.fbr.takeInt(usize, endian); | |
| 1258 | const len = try self.fbr.takeLeb128(usize); | |
| 810 | const start_addr = try readAddress(&self.fr, endian, addr_size_bytes); | |
| 811 | const len = try self.fr.takeLeb128(u64); | |
| 1259 | 812 | const end_addr = start_addr + len; |
| 1260 | 813 | |
| 1261 | 814 | return .{ |
| ... | ... | @@ -1267,12 +820,13 @@ const DebugRangeIterator = struct { |
| 1267 | 820 | } |
| 1268 | 821 | }, |
| 1269 | 822 | .debug_ranges => { |
| 1270 | const start_addr = try self.fbr.takeInt(usize, endian); | |
| 1271 | const end_addr = try self.fbr.takeInt(usize, endian); | |
| 823 | const start_addr = try readAddress(&self.fr, endian, addr_size_bytes); | |
| 824 | const end_addr = try readAddress(&self.fr, endian, addr_size_bytes); | |
| 1272 | 825 | if (start_addr == 0 and end_addr == 0) return null; |
| 1273 | 826 | |
| 1274 | // This entry selects a new value for the base address | |
| 1275 | if (start_addr == maxInt(usize)) { | |
| 827 | // The entry with start_addr = max_representable_address selects a new value for the base address | |
| 828 | const max_representable_address = ~@as(u64, 0) >> @intCast(64 - addr_size_bytes); | |
| 829 | if (start_addr == max_representable_address) { | |
| 1276 | 830 | self.base_address = end_addr; |
| 1277 | 831 | return try self.next(); |
| 1278 | 832 | } |
| ... | ... | @@ -1288,14 +842,14 @@ const DebugRangeIterator = struct { |
| 1288 | 842 | }; |
| 1289 | 843 | |
| 1290 | 844 | /// TODO: change this to binary searching the sorted compile unit list |
| 1291 | pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*CompileUnit { | |
| 845 | pub fn findCompileUnit(di: *const Dwarf, endian: Endian, target_address: u64) !*CompileUnit { | |
| 1292 | 846 | for (di.compile_unit_list.items) |*compile_unit| { |
| 1293 | 847 | if (compile_unit.pc_range) |range| { |
| 1294 | 848 | if (target_address >= range.start and target_address < range.end) return compile_unit; |
| 1295 | 849 | } |
| 1296 | 850 | |
| 1297 | 851 | const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue; |
| 1298 | var iter = DebugRangeIterator.init(ranges_value, di, compile_unit) catch continue; | |
| 852 | var iter = DebugRangeIterator.init(ranges_value, di, endian, compile_unit) catch continue; | |
| 1299 | 853 | while (try iter.next()) |range| { |
| 1300 | 854 | if (target_address >= range.start and target_address < range.end) return compile_unit; |
| 1301 | 855 | } |
| ... | ... | @@ -1306,49 +860,49 @@ pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*CompileUnit { |
| 1306 | 860 | |
| 1307 | 861 | /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, |
| 1308 | 862 | /// seeks in the stream and parses it. |
| 1309 | fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const Abbrev.Table { | |
| 863 | fn getAbbrevTable(di: *Dwarf, gpa: Allocator, abbrev_offset: u64) !*const Abbrev.Table { | |
| 1310 | 864 | for (di.abbrev_table_list.items) |*table| { |
| 1311 | 865 | if (table.offset == abbrev_offset) { |
| 1312 | 866 | return table; |
| 1313 | 867 | } |
| 1314 | 868 | } |
| 1315 | 869 | try di.abbrev_table_list.append( |
| 1316 | allocator, | |
| 1317 | try di.parseAbbrevTable(allocator, abbrev_offset), | |
| 870 | gpa, | |
| 871 | try di.parseAbbrevTable(gpa, abbrev_offset), | |
| 1318 | 872 | ); |
| 1319 | 873 | return &di.abbrev_table_list.items[di.abbrev_table_list.items.len - 1]; |
| 1320 | 874 | } |
| 1321 | 875 | |
| 1322 | fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table { | |
| 1323 | var fbr: Reader = .fixed(di.section(.debug_abbrev).?); | |
| 1324 | fbr.seek = cast(usize, offset) orelse return bad(); | |
| 876 | fn parseAbbrevTable(di: *Dwarf, gpa: Allocator, offset: u64) !Abbrev.Table { | |
| 877 | var fr: Reader = .fixed(di.section(.debug_abbrev).?); | |
| 878 | fr.seek = cast(usize, offset) orelse return bad(); | |
| 1325 | 879 | |
| 1326 | var abbrevs = std.array_list.Managed(Abbrev).init(allocator); | |
| 880 | var abbrevs = std.array_list.Managed(Abbrev).init(gpa); | |
| 1327 | 881 | defer { |
| 1328 | 882 | for (abbrevs.items) |*abbrev| { |
| 1329 | abbrev.deinit(allocator); | |
| 883 | abbrev.deinit(gpa); | |
| 1330 | 884 | } |
| 1331 | 885 | abbrevs.deinit(); |
| 1332 | 886 | } |
| 1333 | 887 | |
| 1334 | var attrs = std.array_list.Managed(Abbrev.Attr).init(allocator); | |
| 888 | var attrs = std.array_list.Managed(Abbrev.Attr).init(gpa); | |
| 1335 | 889 | defer attrs.deinit(); |
| 1336 | 890 | |
| 1337 | 891 | while (true) { |
| 1338 | const code = try fbr.takeLeb128(u64); | |
| 892 | const code = try fr.takeLeb128(u64); | |
| 1339 | 893 | if (code == 0) break; |
| 1340 | const tag_id = try fbr.takeLeb128(u64); | |
| 1341 | const has_children = (try fbr.takeByte()) == DW.CHILDREN.yes; | |
| 894 | const tag_id = try fr.takeLeb128(u64); | |
| 895 | const has_children = (try fr.takeByte()) == DW.CHILDREN.yes; | |
| 1342 | 896 | |
| 1343 | 897 | while (true) { |
| 1344 | const attr_id = try fbr.takeLeb128(u64); | |
| 1345 | const form_id = try fbr.takeLeb128(u64); | |
| 898 | const attr_id = try fr.takeLeb128(u64); | |
| 899 | const form_id = try fr.takeLeb128(u64); | |
| 1346 | 900 | if (attr_id == 0 and form_id == 0) break; |
| 1347 | 901 | try attrs.append(.{ |
| 1348 | 902 | .id = attr_id, |
| 1349 | 903 | .form_id = form_id, |
| 1350 | 904 | .payload = switch (form_id) { |
| 1351 | FORM.implicit_const => try fbr.takeLeb128(i64), | |
| 905 | FORM.implicit_const => try fr.takeLeb128(i64), | |
| 1352 | 906 | else => undefined, |
| 1353 | 907 | }, |
| 1354 | 908 | }); |
| ... | ... | @@ -1369,20 +923,21 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table |
| 1369 | 923 | } |
| 1370 | 924 | |
| 1371 | 925 | fn parseDie( |
| 1372 | fbr: *Reader, | |
| 926 | fr: *Reader, | |
| 1373 | 927 | attrs_buf: []Die.Attr, |
| 1374 | 928 | abbrev_table: *const Abbrev.Table, |
| 1375 | 929 | format: Format, |
| 1376 | 930 | endian: Endian, |
| 931 | addr_size_bytes: u8, | |
| 1377 | 932 | ) ScanError!?Die { |
| 1378 | const abbrev_code = try fbr.takeLeb128(u64); | |
| 933 | const abbrev_code = try fr.takeLeb128(u64); | |
| 1379 | 934 | if (abbrev_code == 0) return null; |
| 1380 | 935 | const table_entry = abbrev_table.get(abbrev_code) orelse return bad(); |
| 1381 | 936 | |
| 1382 | 937 | const attrs = attrs_buf[0..table_entry.attrs.len]; |
| 1383 | 938 | for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = .{ |
| 1384 | 939 | .id = attr.id, |
| 1385 | .value = try parseFormValue(fbr, attr.form_id, format, endian, attr.payload), | |
| 940 | .value = try parseFormValue(fr, attr.form_id, format, endian, addr_size_bytes, attr.payload), | |
| 1386 | 941 | }; |
| 1387 | 942 | return .{ |
| 1388 | 943 | .tag_id = table_entry.tag_id, |
| ... | ... | @@ -1392,55 +947,50 @@ fn parseDie( |
| 1392 | 947 | } |
| 1393 | 948 | |
| 1394 | 949 | /// Ensures that addresses in the returned LineTable are monotonically increasing. |
| 1395 | fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !CompileUnit.SrcLocCache { | |
| 1396 | const endian = d.endian; | |
| 1397 | const compile_unit_cwd = try compile_unit.die.getAttrString(d, AT.comp_dir, d.section(.debug_line_str), compile_unit.*); | |
| 950 | fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit: *const CompileUnit) !CompileUnit.SrcLocCache { | |
| 951 | const compile_unit_cwd = try compile_unit.die.getAttrString(d, endian, AT.comp_dir, d.section(.debug_line_str), compile_unit); | |
| 1398 | 952 | const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); |
| 1399 | 953 | |
| 1400 | var fbr: Reader = .fixed(d.section(.debug_line).?); | |
| 1401 | fbr.seek = @intCast(line_info_offset); | |
| 954 | var fr: Reader = .fixed(d.section(.debug_line).?); | |
| 955 | fr.seek = @intCast(line_info_offset); | |
| 1402 | 956 | |
| 1403 | const unit_header = try readUnitHeader(&fbr, endian); | |
| 957 | const unit_header = try readUnitHeader(&fr, endian); | |
| 1404 | 958 | if (unit_header.unit_length == 0) return missing(); |
| 1405 | 959 | |
| 1406 | 960 | const next_offset = unit_header.header_length + unit_header.unit_length; |
| 1407 | 961 | |
| 1408 | const version = try fbr.takeInt(u16, endian); | |
| 962 | const version = try fr.takeInt(u16, endian); | |
| 1409 | 963 | if (version < 2) return bad(); |
| 1410 | 964 | |
| 1411 | const addr_size: u8, const seg_size: u8 = if (version >= 5) .{ | |
| 1412 | try fbr.takeByte(), | |
| 1413 | try fbr.takeByte(), | |
| 965 | const addr_size_bytes: u8, const seg_size: u8 = if (version >= 5) .{ | |
| 966 | try fr.takeByte(), | |
| 967 | try fr.takeByte(), | |
| 1414 | 968 | } else .{ |
| 1415 | switch (unit_header.format) { | |
| 1416 | .@"32" => 4, | |
| 1417 | .@"64" => 8, | |
| 1418 | }, | |
| 969 | compile_unit.addr_size_bytes, | |
| 1419 | 970 | 0, |
| 1420 | 971 | }; |
| 1421 | _ = addr_size; | |
| 1422 | _ = seg_size; | |
| 972 | if (seg_size != 0) return bad(); // unsupported | |
| 1423 | 973 | |
| 1424 | const prologue_length = try readAddress(&fbr, unit_header.format, endian); | |
| 1425 | const prog_start_offset = fbr.seek + prologue_length; | |
| 974 | const prologue_length = try readFormatSizedInt(&fr, unit_header.format, endian); | |
| 975 | const prog_start_offset = fr.seek + prologue_length; | |
| 1426 | 976 | |
| 1427 | const minimum_instruction_length = try fbr.takeByte(); | |
| 977 | const minimum_instruction_length = try fr.takeByte(); | |
| 1428 | 978 | if (minimum_instruction_length == 0) return bad(); |
| 1429 | 979 | |
| 1430 | 980 | if (version >= 4) { |
| 1431 | const maximum_operations_per_instruction = try fbr.takeByte(); | |
| 981 | const maximum_operations_per_instruction = try fr.takeByte(); | |
| 1432 | 982 | _ = maximum_operations_per_instruction; |
| 1433 | 983 | } |
| 1434 | 984 | |
| 1435 | const default_is_stmt = (try fbr.takeByte()) != 0; | |
| 1436 | const line_base = try fbr.takeByteSigned(); | |
| 985 | const default_is_stmt = (try fr.takeByte()) != 0; | |
| 986 | const line_base = try fr.takeByteSigned(); | |
| 1437 | 987 | |
| 1438 | const line_range = try fbr.takeByte(); | |
| 988 | const line_range = try fr.takeByte(); | |
| 1439 | 989 | if (line_range == 0) return bad(); |
| 1440 | 990 | |
| 1441 | const opcode_base = try fbr.takeByte(); | |
| 991 | const opcode_base = try fr.takeByte(); | |
| 1442 | 992 | |
| 1443 | const standard_opcode_lengths = try fbr.take(opcode_base - 1); | |
| 993 | const standard_opcode_lengths = try fr.take(opcode_base - 1); | |
| 1444 | 994 | |
| 1445 | 995 | var directories: ArrayList(FileEntry) = .empty; |
| 1446 | 996 | defer directories.deinit(gpa); |
| ... | ... | @@ -1451,17 +1001,17 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! |
| 1451 | 1001 | try directories.append(gpa, .{ .path = compile_unit_cwd }); |
| 1452 | 1002 | |
| 1453 | 1003 | while (true) { |
| 1454 | const dir = try fbr.takeSentinel(0); | |
| 1004 | const dir = try fr.takeSentinel(0); | |
| 1455 | 1005 | if (dir.len == 0) break; |
| 1456 | 1006 | try directories.append(gpa, .{ .path = dir }); |
| 1457 | 1007 | } |
| 1458 | 1008 | |
| 1459 | 1009 | while (true) { |
| 1460 | const file_name = try fbr.takeSentinel(0); | |
| 1010 | const file_name = try fr.takeSentinel(0); | |
| 1461 | 1011 | if (file_name.len == 0) break; |
| 1462 | const dir_index = try fbr.takeLeb128(u32); | |
| 1463 | const mtime = try fbr.takeLeb128(u64); | |
| 1464 | const size = try fbr.takeLeb128(u64); | |
| 1012 | const dir_index = try fr.takeLeb128(u32); | |
| 1013 | const mtime = try fr.takeLeb128(u64); | |
| 1014 | const size = try fr.takeLeb128(u64); | |
| 1465 | 1015 | try file_entries.append(gpa, .{ |
| 1466 | 1016 | .path = file_name, |
| 1467 | 1017 | .dir_index = dir_index, |
| ... | ... | @@ -1476,21 +1026,21 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! |
| 1476 | 1026 | }; |
| 1477 | 1027 | { |
| 1478 | 1028 | var dir_ent_fmt_buf: [10]FileEntFmt = undefined; |
| 1479 | const directory_entry_format_count = try fbr.takeByte(); | |
| 1029 | const directory_entry_format_count = try fr.takeByte(); | |
| 1480 | 1030 | if (directory_entry_format_count > dir_ent_fmt_buf.len) return bad(); |
| 1481 | 1031 | for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| { |
| 1482 | 1032 | ent_fmt.* = .{ |
| 1483 | .content_type_code = try fbr.takeLeb128(u8), | |
| 1484 | .form_code = try fbr.takeLeb128(u16), | |
| 1033 | .content_type_code = try fr.takeLeb128(u8), | |
| 1034 | .form_code = try fr.takeLeb128(u16), | |
| 1485 | 1035 | }; |
| 1486 | 1036 | } |
| 1487 | 1037 | |
| 1488 | const directories_count = try fbr.takeLeb128(usize); | |
| 1038 | const directories_count = try fr.takeLeb128(usize); | |
| 1489 | 1039 | |
| 1490 | 1040 | for (try directories.addManyAsSlice(gpa, directories_count)) |*e| { |
| 1491 | 1041 | e.* = .{ .path = &.{} }; |
| 1492 | 1042 | for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| { |
| 1493 | const form_value = try parseFormValue(&fbr, ent_fmt.form_code, unit_header.format, endian, null); | |
| 1043 | const form_value = try parseFormValue(&fr, ent_fmt.form_code, unit_header.format, endian, addr_size_bytes, null); | |
| 1494 | 1044 | switch (ent_fmt.content_type_code) { |
| 1495 | 1045 | DW.LNCT.path => e.path = try form_value.getString(d.*), |
| 1496 | 1046 | DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), |
| ... | ... | @@ -1507,22 +1057,22 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! |
| 1507 | 1057 | } |
| 1508 | 1058 | |
| 1509 | 1059 | var file_ent_fmt_buf: [10]FileEntFmt = undefined; |
| 1510 | const file_name_entry_format_count = try fbr.takeByte(); | |
| 1060 | const file_name_entry_format_count = try fr.takeByte(); | |
| 1511 | 1061 | if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad(); |
| 1512 | 1062 | for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| { |
| 1513 | 1063 | ent_fmt.* = .{ |
| 1514 | .content_type_code = try fbr.takeLeb128(u16), | |
| 1515 | .form_code = try fbr.takeLeb128(u16), | |
| 1064 | .content_type_code = try fr.takeLeb128(u16), | |
| 1065 | .form_code = try fr.takeLeb128(u16), | |
| 1516 | 1066 | }; |
| 1517 | 1067 | } |
| 1518 | 1068 | |
| 1519 | const file_names_count = try fbr.takeLeb128(usize); | |
| 1069 | const file_names_count = try fr.takeLeb128(usize); | |
| 1520 | 1070 | try file_entries.ensureUnusedCapacity(gpa, file_names_count); |
| 1521 | 1071 | |
| 1522 | 1072 | for (try file_entries.addManyAsSlice(gpa, file_names_count)) |*e| { |
| 1523 | 1073 | e.* = .{ .path = &.{} }; |
| 1524 | 1074 | for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| { |
| 1525 | const form_value = try parseFormValue(&fbr, ent_fmt.form_code, unit_header.format, endian, null); | |
| 1075 | const form_value = try parseFormValue(&fr, ent_fmt.form_code, unit_header.format, endian, addr_size_bytes, null); | |
| 1526 | 1076 | switch (ent_fmt.content_type_code) { |
| 1527 | 1077 | DW.LNCT.path => e.path = try form_value.getString(d.*), |
| 1528 | 1078 | DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), |
| ... | ... | @@ -1542,17 +1092,17 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! |
| 1542 | 1092 | var line_table: CompileUnit.SrcLocCache.LineTable = .{}; |
| 1543 | 1093 | errdefer line_table.deinit(gpa); |
| 1544 | 1094 | |
| 1545 | fbr.seek = @intCast(prog_start_offset); | |
| 1095 | fr.seek = @intCast(prog_start_offset); | |
| 1546 | 1096 | |
| 1547 | 1097 | const next_unit_pos = line_info_offset + next_offset; |
| 1548 | 1098 | |
| 1549 | while (fbr.seek < next_unit_pos) { | |
| 1550 | const opcode = try fbr.takeByte(); | |
| 1099 | while (fr.seek < next_unit_pos) { | |
| 1100 | const opcode = try fr.takeByte(); | |
| 1551 | 1101 | |
| 1552 | 1102 | if (opcode == DW.LNS.extended_op) { |
| 1553 | const op_size = try fbr.takeLeb128(u64); | |
| 1103 | const op_size = try fr.takeLeb128(u64); | |
| 1554 | 1104 | if (op_size < 1) return bad(); |
| 1555 | const sub_op = try fbr.takeByte(); | |
| 1105 | const sub_op = try fr.takeByte(); | |
| 1556 | 1106 | switch (sub_op) { |
| 1557 | 1107 | DW.LNE.end_sequence => { |
| 1558 | 1108 | // The row being added here is an "end" address, meaning |
| ... | ... | @@ -1571,14 +1121,13 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! |
| 1571 | 1121 | prog.reset(); |
| 1572 | 1122 | }, |
| 1573 | 1123 | DW.LNE.set_address => { |
| 1574 | const addr = try fbr.takeInt(usize, endian); | |
| 1575 | prog.address = addr; | |
| 1124 | prog.address = try readAddress(&fr, endian, addr_size_bytes); | |
| 1576 | 1125 | }, |
| 1577 | 1126 | DW.LNE.define_file => { |
| 1578 | const path = try fbr.takeSentinel(0); | |
| 1579 | const dir_index = try fbr.takeLeb128(u32); | |
| 1580 | const mtime = try fbr.takeLeb128(u64); | |
| 1581 | const size = try fbr.takeLeb128(u64); | |
| 1127 | const path = try fr.takeSentinel(0); | |
| 1128 | const dir_index = try fr.takeLeb128(u32); | |
| 1129 | const mtime = try fr.takeLeb128(u64); | |
| 1130 | const size = try fr.takeLeb128(u64); | |
| 1582 | 1131 | try file_entries.append(gpa, .{ |
| 1583 | 1132 | .path = path, |
| 1584 | 1133 | .dir_index = dir_index, |
| ... | ... | @@ -1586,7 +1135,7 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! |
| 1586 | 1135 | .size = size, |
| 1587 | 1136 | }); |
| 1588 | 1137 | }, |
| 1589 | else => try fbr.discardAll64(op_size - 1), | |
| 1138 | else => try fr.discardAll64(op_size - 1), | |
| 1590 | 1139 | } |
| 1591 | 1140 | } else if (opcode >= opcode_base) { |
| 1592 | 1141 | // special opcodes |
| ... | ... | @@ -1604,19 +1153,19 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! |
| 1604 | 1153 | prog.basic_block = false; |
| 1605 | 1154 | }, |
| 1606 | 1155 | DW.LNS.advance_pc => { |
| 1607 | const arg = try fbr.takeLeb128(usize); | |
| 1156 | const arg = try fr.takeLeb128(u64); | |
| 1608 | 1157 | prog.address += arg * minimum_instruction_length; |
| 1609 | 1158 | }, |
| 1610 | 1159 | DW.LNS.advance_line => { |
| 1611 | const arg = try fbr.takeLeb128(i64); | |
| 1160 | const arg = try fr.takeLeb128(i64); | |
| 1612 | 1161 | prog.line += arg; |
| 1613 | 1162 | }, |
| 1614 | 1163 | DW.LNS.set_file => { |
| 1615 | const arg = try fbr.takeLeb128(usize); | |
| 1164 | const arg = try fr.takeLeb128(usize); | |
| 1616 | 1165 | prog.file = arg; |
| 1617 | 1166 | }, |
| 1618 | 1167 | DW.LNS.set_column => { |
| 1619 | const arg = try fbr.takeLeb128(u64); | |
| 1168 | const arg = try fr.takeLeb128(u64); | |
| 1620 | 1169 | prog.column = arg; |
| 1621 | 1170 | }, |
| 1622 | 1171 | DW.LNS.negate_stmt => { |
| ... | ... | @@ -1630,13 +1179,13 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! |
| 1630 | 1179 | prog.address += inc_addr; |
| 1631 | 1180 | }, |
| 1632 | 1181 | DW.LNS.fixed_advance_pc => { |
| 1633 | const arg = try fbr.takeInt(u16, endian); | |
| 1182 | const arg = try fr.takeInt(u16, endian); | |
| 1634 | 1183 | prog.address += arg; |
| 1635 | 1184 | }, |
| 1636 | 1185 | DW.LNS.set_prologue_end => {}, |
| 1637 | 1186 | else => { |
| 1638 | 1187 | if (opcode - 1 >= standard_opcode_lengths.len) return bad(); |
| 1639 | try fbr.discardAll(standard_opcode_lengths[opcode - 1]); | |
| 1188 | try fr.discardAll(standard_opcode_lengths[opcode - 1]); | |
| 1640 | 1189 | }, |
| 1641 | 1190 | } |
| 1642 | 1191 | } |
| ... | ... | @@ -1661,18 +1210,19 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! |
| 1661 | 1210 | }; |
| 1662 | 1211 | } |
| 1663 | 1212 | |
| 1664 | pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, cu: *CompileUnit) ScanError!void { | |
| 1213 | pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, endian: Endian, cu: *CompileUnit) ScanError!void { | |
| 1665 | 1214 | if (cu.src_loc_cache != null) return; |
| 1666 | cu.src_loc_cache = try runLineNumberProgram(d, gpa, cu); | |
| 1215 | cu.src_loc_cache = try d.runLineNumberProgram(gpa, endian, cu); | |
| 1667 | 1216 | } |
| 1668 | 1217 | |
| 1669 | 1218 | pub fn getLineNumberInfo( |
| 1670 | 1219 | d: *Dwarf, |
| 1671 | 1220 | gpa: Allocator, |
| 1221 | endian: Endian, | |
| 1672 | 1222 | compile_unit: *CompileUnit, |
| 1673 | 1223 | target_address: u64, |
| 1674 | 1224 | ) !std.debug.SourceLocation { |
| 1675 | try populateSrcLocCache(d, gpa, compile_unit); | |
| 1225 | try d.populateSrcLocCache(gpa, endian, compile_unit); | |
| 1676 | 1226 | const slc = &compile_unit.src_loc_cache.?; |
| 1677 | 1227 | const entry = try slc.findSource(target_address); |
| 1678 | 1228 | const file_index = entry.file - @intFromBool(slc.version < 5); |
| ... | ... | @@ -1696,7 +1246,7 @@ fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 { |
| 1696 | 1246 | return getStringGeneric(di.section(.debug_line_str), offset); |
| 1697 | 1247 | } |
| 1698 | 1248 | |
| 1699 | fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { | |
| 1249 | fn readDebugAddr(di: Dwarf, endian: Endian, compile_unit: *const CompileUnit, index: u64) !u64 { | |
| 1700 | 1250 | const debug_addr = di.section(.debug_addr) orelse return bad(); |
| 1701 | 1251 | |
| 1702 | 1252 | // addr_base points to the first item after the header, however we |
| ... | ... | @@ -1705,139 +1255,40 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { |
| 1705 | 1255 | // The header is 8 or 12 bytes depending on is_64. |
| 1706 | 1256 | if (compile_unit.addr_base < 8) return bad(); |
| 1707 | 1257 | |
| 1708 | const version = mem.readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); | |
| 1258 | const version = mem.readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], endian); | |
| 1709 | 1259 | if (version != 5) return bad(); |
| 1710 | 1260 | |
| 1711 | 1261 | const addr_size = debug_addr[compile_unit.addr_base - 2]; |
| 1712 | 1262 | const seg_size = debug_addr[compile_unit.addr_base - 1]; |
| 1713 | 1263 | |
| 1714 | const byte_offset = @as(usize, @intCast(compile_unit.addr_base + (addr_size + seg_size) * index)); | |
| 1264 | const byte_offset = compile_unit.addr_base + (addr_size + seg_size) * index; | |
| 1715 | 1265 | if (byte_offset + addr_size > debug_addr.len) return bad(); |
| 1716 | 1266 | return switch (addr_size) { |
| 1717 | 1 => debug_addr[byte_offset], | |
| 1718 | 2 => mem.readInt(u16, debug_addr[byte_offset..][0..2], di.endian), | |
| 1719 | 4 => mem.readInt(u32, debug_addr[byte_offset..][0..4], di.endian), | |
| 1720 | 8 => mem.readInt(u64, debug_addr[byte_offset..][0..8], di.endian), | |
| 1267 | 1 => debug_addr[@intCast(byte_offset)], | |
| 1268 | 2 => mem.readInt(u16, debug_addr[@intCast(byte_offset)..][0..2], endian), | |
| 1269 | 4 => mem.readInt(u32, debug_addr[@intCast(byte_offset)..][0..4], endian), | |
| 1270 | 8 => mem.readInt(u64, debug_addr[@intCast(byte_offset)..][0..8], endian), | |
| 1721 | 1271 | else => bad(), |
| 1722 | 1272 | }; |
| 1723 | 1273 | } |
| 1724 | 1274 | |
| 1725 | /// If `.eh_frame_hdr` is present, then only the header needs to be parsed. Otherwise, `.eh_frame` | |
| 1726 | /// and `.debug_frame` are scanned and a sorted list of FDEs is built for binary searching during | |
| 1727 | /// unwinding. Even if `.eh_frame_hdr` is used, we may find during unwinding that it's incomplete, | |
| 1728 | /// in which case we build the sorted list of FDEs at that point. | |
| 1729 | /// | |
| 1730 | /// See also `scanCieFdeInfo`. | |
| 1731 | pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void { | |
| 1732 | const endian = di.endian; | |
| 1733 | ||
| 1734 | if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: { | |
| 1735 | var fbr: Reader = .fixed(eh_frame_hdr); | |
| 1736 | ||
| 1737 | const version = try fbr.takeByte(); | |
| 1738 | if (version != 1) break :blk; | |
| 1739 | ||
| 1740 | const eh_frame_ptr_enc = try fbr.takeByte(); | |
| 1741 | if (eh_frame_ptr_enc == EH.PE.omit) break :blk; | |
| 1742 | const fde_count_enc = try fbr.takeByte(); | |
| 1743 | if (fde_count_enc == EH.PE.omit) break :blk; | |
| 1744 | const table_enc = try fbr.takeByte(); | |
| 1745 | if (table_enc == EH.PE.omit) break :blk; | |
| 1746 | ||
| 1747 | const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{ | |
| 1748 | .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]), | |
| 1749 | .follow_indirect = true, | |
| 1750 | }, endian) orelse return bad()) orelse return bad(); | |
| 1751 | ||
| 1752 | const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{ | |
| 1753 | .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]), | |
| 1754 | .follow_indirect = true, | |
| 1755 | }, endian) orelse return bad()) orelse return bad(); | |
| 1756 | ||
| 1757 | const entry_size = try ExceptionFrameHeader.entrySize(table_enc); | |
| 1758 | const entries_len = fde_count * entry_size; | |
| 1759 | if (entries_len > eh_frame_hdr.len - fbr.seek) return bad(); | |
| 1760 | ||
| 1761 | di.eh_frame_hdr = .{ | |
| 1762 | .eh_frame_ptr = eh_frame_ptr, | |
| 1763 | .table_enc = table_enc, | |
| 1764 | .fde_count = fde_count, | |
| 1765 | .entries = eh_frame_hdr[fbr.seek..][0..entries_len], | |
| 1766 | }; | |
| 1767 | ||
| 1768 | // No need to scan .eh_frame, we have a binary search table already | |
| 1769 | return; | |
| 1770 | } | |
| 1771 | ||
| 1772 | try di.scanCieFdeInfo(allocator, base_address); | |
| 1773 | } | |
| 1774 | ||
| 1775 | /// Scan `.eh_frame` and `.debug_frame` and build a sorted list of FDEs for binary searching during | |
| 1776 | /// unwinding. | |
| 1777 | pub fn scanCieFdeInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void { | |
| 1778 | const endian = di.endian; | |
| 1779 | const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame }; | |
| 1780 | for (frame_sections) |frame_section| { | |
| 1781 | if (di.section(frame_section)) |section_data| { | |
| 1782 | var fbr: Reader = .fixed(section_data); | |
| 1783 | while (fbr.seek < fbr.buffer.len) { | |
| 1784 | const entry_header = try EntryHeader.read(&fbr, frame_section, endian); | |
| 1785 | switch (entry_header.type) { | |
| 1786 | .cie => { | |
| 1787 | const cie = try CommonInformationEntry.parse( | |
| 1788 | entry_header.entry_bytes, | |
| 1789 | di.sectionVirtualOffset(frame_section, base_address).?, | |
| 1790 | true, | |
| 1791 | entry_header.format, | |
| 1792 | frame_section, | |
| 1793 | entry_header.length_offset, | |
| 1794 | @sizeOf(usize), | |
| 1795 | di.endian, | |
| 1796 | ); | |
| 1797 | try di.cie_map.put(allocator, entry_header.length_offset, cie); | |
| 1798 | }, | |
| 1799 | .fde => |cie_offset| { | |
| 1800 | const cie = di.cie_map.get(cie_offset) orelse return bad(); | |
| 1801 | const fde = try FrameDescriptionEntry.parse( | |
| 1802 | entry_header.entry_bytes, | |
| 1803 | di.sectionVirtualOffset(frame_section, base_address).?, | |
| 1804 | true, | |
| 1805 | cie, | |
| 1806 | @sizeOf(usize), | |
| 1807 | di.endian, | |
| 1808 | ); | |
| 1809 | try di.fde_list.append(allocator, fde); | |
| 1810 | }, | |
| 1811 | .terminator => break, | |
| 1812 | } | |
| 1813 | } | |
| 1814 | ||
| 1815 | std.mem.sortUnstable(FrameDescriptionEntry, di.fde_list.items, {}, struct { | |
| 1816 | fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool { | |
| 1817 | _ = ctx; | |
| 1818 | return a.pc_begin < b.pc_begin; | |
| 1819 | } | |
| 1820 | }.lessThan); | |
| 1821 | } | |
| 1822 | } | |
| 1823 | } | |
| 1824 | ||
| 1825 | 1275 | fn parseFormValue( |
| 1826 | 1276 | r: *Reader, |
| 1827 | 1277 | form_id: u64, |
| 1828 | 1278 | format: Format, |
| 1829 | 1279 | endian: Endian, |
| 1280 | addr_size_bytes: u8, | |
| 1830 | 1281 | implicit_const: ?i64, |
| 1831 | 1282 | ) ScanError!FormValue { |
| 1832 | 1283 | return switch (form_id) { |
| 1833 | 1284 | // DWARF5.pdf page 213: the size of this value is encoded in the |
| 1834 | 1285 | // compilation unit header as address size. |
| 1835 | FORM.addr => .{ .addr = try readAddress(r, nativeFormat(), endian) }, | |
| 1286 | FORM.addr => .{ .addr = try readAddress(r, endian, addr_size_bytes) }, | |
| 1836 | 1287 | FORM.addrx1 => .{ .addrx = try r.takeByte() }, |
| 1837 | 1288 | FORM.addrx2 => .{ .addrx = try r.takeInt(u16, endian) }, |
| 1838 | 1289 | FORM.addrx3 => .{ .addrx = try r.takeInt(u24, endian) }, |
| 1839 | 1290 | FORM.addrx4 => .{ .addrx = try r.takeInt(u32, endian) }, |
| 1840 | FORM.addrx => .{ .addrx = try r.takeLeb128(usize) }, | |
| 1291 | FORM.addrx => .{ .addrx = try r.takeLeb128(u64) }, | |
| 1841 | 1292 | |
| 1842 | 1293 | FORM.block1 => .{ .block = try r.take(try r.takeByte()) }, |
| 1843 | 1294 | FORM.block2 => .{ .block = try r.take(try r.takeInt(u16, endian)) }, |
| ... | ... | @@ -1854,7 +1305,7 @@ fn parseFormValue( |
| 1854 | 1305 | FORM.exprloc => .{ .exprloc = try r.take(try r.takeLeb128(usize)) }, |
| 1855 | 1306 | FORM.flag => .{ .flag = (try r.takeByte()) != 0 }, |
| 1856 | 1307 | FORM.flag_present => .{ .flag = true }, |
| 1857 | FORM.sec_offset => .{ .sec_offset = try readAddress(r, format, endian) }, | |
| 1308 | FORM.sec_offset => .{ .sec_offset = try readFormatSizedInt(r, format, endian) }, | |
| 1858 | 1309 | |
| 1859 | 1310 | FORM.ref1 => .{ .ref = try r.takeByte() }, |
| 1860 | 1311 | FORM.ref2 => .{ .ref = try r.takeInt(u16, endian) }, |
| ... | ... | @@ -1862,18 +1313,18 @@ fn parseFormValue( |
| 1862 | 1313 | FORM.ref8 => .{ .ref = try r.takeInt(u64, endian) }, |
| 1863 | 1314 | FORM.ref_udata => .{ .ref = try r.takeLeb128(u64) }, |
| 1864 | 1315 | |
| 1865 | FORM.ref_addr => .{ .ref_addr = try readAddress(r, format, endian) }, | |
| 1316 | FORM.ref_addr => .{ .ref_addr = try readFormatSizedInt(r, format, endian) }, | |
| 1866 | 1317 | FORM.ref_sig8 => .{ .ref = try r.takeInt(u64, endian) }, |
| 1867 | 1318 | |
| 1868 | 1319 | FORM.string => .{ .string = try r.takeSentinel(0) }, |
| 1869 | FORM.strp => .{ .strp = try readAddress(r, format, endian) }, | |
| 1320 | FORM.strp => .{ .strp = try readFormatSizedInt(r, format, endian) }, | |
| 1870 | 1321 | FORM.strx1 => .{ .strx = try r.takeByte() }, |
| 1871 | 1322 | FORM.strx2 => .{ .strx = try r.takeInt(u16, endian) }, |
| 1872 | 1323 | FORM.strx3 => .{ .strx = try r.takeInt(u24, endian) }, |
| 1873 | 1324 | FORM.strx4 => .{ .strx = try r.takeInt(u32, endian) }, |
| 1874 | 1325 | FORM.strx => .{ .strx = try r.takeLeb128(usize) }, |
| 1875 | FORM.line_strp => .{ .line_strp = try readAddress(r, format, endian) }, | |
| 1876 | FORM.indirect => parseFormValue(r, try r.takeLeb128(u64), format, endian, implicit_const), | |
| 1326 | FORM.line_strp => .{ .line_strp = try readFormatSizedInt(r, format, endian) }, | |
| 1327 | FORM.indirect => parseFormValue(r, try r.takeLeb128(u64), format, endian, addr_size_bytes, implicit_const), | |
| 1877 | 1328 | FORM.implicit_const => .{ .sdata = implicit_const orelse return bad() }, |
| 1878 | 1329 | FORM.loclistx => .{ .loclistx = try r.takeLeb128(u64) }, |
| 1879 | 1330 | FORM.rnglistx => .{ .rnglistx = try r.takeLeb128(u64) }, |
| ... | ... | @@ -1946,7 +1397,7 @@ const UnitHeader = struct { |
| 1946 | 1397 | unit_length: u64, |
| 1947 | 1398 | }; |
| 1948 | 1399 | |
| 1949 | fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader { | |
| 1400 | pub fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader { | |
| 1950 | 1401 | return switch (try r.takeInt(u32, endian)) { |
| 1951 | 1402 | 0...0xfffffff0 - 1 => |unit_length| .{ |
| 1952 | 1403 | .format = .@"32", |
| ... | ... | @@ -1963,7 +1414,7 @@ fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader { |
| 1963 | 1414 | } |
| 1964 | 1415 | |
| 1965 | 1416 | /// Returns the DWARF register number for an x86_64 register number found in compact unwind info |
| 1966 | pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { | |
| 1417 | pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u16 { | |
| 1967 | 1418 | return switch (unwind_reg_number) { |
| 1968 | 1419 | 1 => 3, // RBX |
| 1969 | 1420 | 2 => 12, // R12 |
| ... | ... | @@ -1971,7 +1422,61 @@ pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { |
| 1971 | 1422 | 4 => 14, // R14 |
| 1972 | 1423 | 5 => 15, // R15 |
| 1973 | 1424 | 6 => 6, // RBP |
| 1974 | else => error.InvalidUnwindRegisterNumber, | |
| 1425 | else => error.InvalidRegister, | |
| 1426 | }; | |
| 1427 | } | |
| 1428 | ||
| 1429 | /// Returns `null` for CPU architectures without an instruction pointer register. | |
| 1430 | pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 { | |
| 1431 | return switch (arch) { | |
| 1432 | .x86 => 8, | |
| 1433 | .x86_64 => 16, | |
| 1434 | .arm, .armeb, .thumb, .thumbeb => 15, | |
| 1435 | .aarch64, .aarch64_be => 32, | |
| 1436 | else => null, | |
| 1437 | }; | |
| 1438 | } | |
| 1439 | ||
| 1440 | pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 { | |
| 1441 | return switch (arch) { | |
| 1442 | .x86 => 5, | |
| 1443 | .x86_64 => 6, | |
| 1444 | .arm, .armeb, .thumb, .thumbeb => 11, | |
| 1445 | .aarch64, .aarch64_be => 29, | |
| 1446 | else => unreachable, | |
| 1447 | }; | |
| 1448 | } | |
| 1449 | ||
| 1450 | pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 { | |
| 1451 | return switch (arch) { | |
| 1452 | .x86 => 4, | |
| 1453 | .x86_64 => 7, | |
| 1454 | .arm, .armeb, .thumb, .thumbeb => 13, | |
| 1455 | .aarch64, .aarch64_be => 31, | |
| 1456 | else => unreachable, | |
| 1457 | }; | |
| 1458 | } | |
| 1459 | ||
| 1460 | /// Tells whether unwinding for this target is supported by the Dwarf standard. | |
| 1461 | /// | |
| 1462 | /// See also `std.debug.SelfInfo.can_unwind` which tells whether the Zig standard | |
| 1463 | /// library has a working implementation of unwinding for the current target. | |
| 1464 | pub fn supportsUnwinding(target: *const std.Target) bool { | |
| 1465 | return switch (target.cpu.arch) { | |
| 1466 | .amdgcn, | |
| 1467 | .nvptx, | |
| 1468 | .nvptx64, | |
| 1469 | .spirv32, | |
| 1470 | .spirv64, | |
| 1471 | => false, | |
| 1472 | ||
| 1473 | // Enabling this causes relocation errors such as: | |
| 1474 | // error: invalid relocation type R_RISCV_SUB32 at offset 0x20 | |
| 1475 | .riscv64, .riscv64be, .riscv32, .riscv32be => false, | |
| 1476 | ||
| 1477 | // Conservative guess. Feel free to update this logic with any targets | |
| 1478 | // that are known to not support Dwarf unwinding. | |
| 1479 | else => true, | |
| 1975 | 1480 | }; |
| 1976 | 1481 | } |
| 1977 | 1482 | |
| ... | ... | @@ -1982,11 +1487,11 @@ pub fn bad() error{InvalidDebugInfo} { |
| 1982 | 1487 | return error.InvalidDebugInfo; |
| 1983 | 1488 | } |
| 1984 | 1489 | |
| 1985 | fn invalidDebugInfoDetected() void { | |
| 1490 | pub fn invalidDebugInfoDetected() void { | |
| 1986 | 1491 | if (debug_debug_mode) @panic("bad dwarf"); |
| 1987 | 1492 | } |
| 1988 | 1493 | |
| 1989 | fn missing() error{MissingDebugInfo} { | |
| 1494 | pub fn missing() error{MissingDebugInfo} { | |
| 1990 | 1495 | if (debug_debug_mode) @panic("missing dwarf"); |
| 1991 | 1496 | return error.MissingDebugInfo; |
| 1992 | 1497 | } |
| ... | ... | @@ -2000,460 +1505,41 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { |
| 2000 | 1505 | return str[casted_offset..last :0]; |
| 2001 | 1506 | } |
| 2002 | 1507 | |
| 2003 | const EhPointerContext = struct { | |
| 2004 | // The address of the pointer field itself | |
| 2005 | pc_rel_base: u64, | |
| 2006 | ||
| 2007 | // Whether or not to follow indirect pointers. This should only be | |
| 2008 | // used when decoding pointers at runtime using the current process's | |
| 2009 | // debug info | |
| 2010 | follow_indirect: bool, | |
| 2011 | ||
| 2012 | // These relative addressing modes are only used in specific cases, and | |
| 2013 | // might not be available / required in all parsing contexts | |
| 2014 | data_rel_base: ?u64 = null, | |
| 2015 | text_rel_base: ?u64 = null, | |
| 2016 | function_rel_base: ?u64 = null, | |
| 2017 | }; | |
| 2018 | ||
| 2019 | fn readEhPointer(fbr: *Reader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !?u64 { | |
| 2020 | if (enc == EH.PE.omit) return null; | |
| 2021 | ||
| 2022 | const value: union(enum) { | |
| 2023 | signed: i64, | |
| 2024 | unsigned: u64, | |
| 2025 | } = switch (enc & EH.PE.type_mask) { | |
| 2026 | EH.PE.absptr => .{ | |
| 2027 | .unsigned = switch (addr_size_bytes) { | |
| 2028 | 2 => try fbr.takeInt(u16, endian), | |
| 2029 | 4 => try fbr.takeInt(u32, endian), | |
| 2030 | 8 => try fbr.takeInt(u64, endian), | |
| 2031 | else => return error.InvalidAddrSize, | |
| 2032 | }, | |
| 2033 | }, | |
| 2034 | EH.PE.uleb128 => .{ .unsigned = try fbr.takeLeb128(u64) }, | |
| 2035 | EH.PE.udata2 => .{ .unsigned = try fbr.takeInt(u16, endian) }, | |
| 2036 | EH.PE.udata4 => .{ .unsigned = try fbr.takeInt(u32, endian) }, | |
| 2037 | EH.PE.udata8 => .{ .unsigned = try fbr.takeInt(u64, endian) }, | |
| 2038 | EH.PE.sleb128 => .{ .signed = try fbr.takeLeb128(i64) }, | |
| 2039 | EH.PE.sdata2 => .{ .signed = try fbr.takeInt(i16, endian) }, | |
| 2040 | EH.PE.sdata4 => .{ .signed = try fbr.takeInt(i32, endian) }, | |
| 2041 | EH.PE.sdata8 => .{ .signed = try fbr.takeInt(i64, endian) }, | |
| 2042 | else => return bad(), | |
| 2043 | }; | |
| 2044 | ||
| 2045 | const base = switch (enc & EH.PE.rel_mask) { | |
| 2046 | EH.PE.pcrel => ctx.pc_rel_base, | |
| 2047 | EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified, | |
| 2048 | EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified, | |
| 2049 | EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified, | |
| 2050 | else => null, | |
| 2051 | }; | |
| 2052 | ||
| 2053 | const ptr: u64 = if (base) |b| switch (value) { | |
| 2054 | .signed => |s| @intCast(try std.math.add(i64, s, @as(i64, @intCast(b)))), | |
| 2055 | // absptr can actually contain signed values in some cases (aarch64 MachO) | |
| 2056 | .unsigned => |u| u +% b, | |
| 2057 | } else switch (value) { | |
| 2058 | .signed => |s| @as(u64, @intCast(s)), | |
| 2059 | .unsigned => |u| u, | |
| 1508 | pub fn getSymbol(di: *Dwarf, gpa: Allocator, endian: Endian, address: u64) !std.debug.Symbol { | |
| 1509 | const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) { | |
| 1510 | error.MissingDebugInfo, error.InvalidDebugInfo => return .unknown, | |
| 1511 | else => return err, | |
| 2060 | 1512 | }; |
| 2061 | ||
| 2062 | if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) { | |
| 2063 | if (@sizeOf(usize) != addr_size_bytes) { | |
| 2064 | // See the documentation for `follow_indirect` | |
| 2065 | return error.NonNativeIndirection; | |
| 2066 | } | |
| 2067 | ||
| 2068 | const native_ptr = cast(usize, ptr) orelse return error.PointerOverflow; | |
| 2069 | return switch (addr_size_bytes) { | |
| 2070 | 2, 4, 8 => return @as(*const usize, @ptrFromInt(native_ptr)).*, | |
| 2071 | else => return error.UnsupportedAddrSize, | |
| 2072 | }; | |
| 2073 | } else { | |
| 2074 | return ptr; | |
| 2075 | } | |
| 2076 | } | |
| 2077 | ||
| 2078 | fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { | |
| 2079 | if (pc_rel_offset < 0) { | |
| 2080 | return std.math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset))); | |
| 2081 | } else { | |
| 2082 | return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); | |
| 2083 | } | |
| 2084 | } | |
| 2085 | ||
| 2086 | pub const ElfModule = struct { | |
| 2087 | base_address: usize, | |
| 2088 | dwarf: Dwarf, | |
| 2089 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 2090 | external_mapped_memory: ?[]align(std.heap.page_size_min) const u8, | |
| 2091 | ||
| 2092 | pub fn deinit(self: *@This(), allocator: Allocator) void { | |
| 2093 | self.dwarf.deinit(allocator); | |
| 2094 | std.posix.munmap(self.mapped_memory); | |
| 2095 | if (self.external_mapped_memory) |m| std.posix.munmap(m); | |
| 2096 | } | |
| 2097 | ||
| 2098 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { | |
| 2099 | // Translate the VA into an address into this object | |
| 2100 | const relocated_address = address - self.base_address; | |
| 2101 | return self.dwarf.getSymbol(allocator, relocated_address); | |
| 2102 | } | |
| 2103 | ||
| 2104 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf { | |
| 2105 | _ = allocator; | |
| 2106 | _ = address; | |
| 2107 | return &self.dwarf; | |
| 2108 | } | |
| 2109 | ||
| 2110 | pub const LoadError = error{ | |
| 2111 | InvalidDebugInfo, | |
| 2112 | MissingDebugInfo, | |
| 2113 | InvalidElfMagic, | |
| 2114 | InvalidElfVersion, | |
| 2115 | InvalidElfEndian, | |
| 2116 | /// TODO: implement this and then remove this error code | |
| 2117 | UnimplementedDwarfForeignEndian, | |
| 2118 | /// The debug info may be valid but this implementation uses memory | |
| 2119 | /// mapping which limits things to usize. If the target debug info is | |
| 2120 | /// 64-bit and host is 32-bit, there may be debug info that is not | |
| 2121 | /// supportable using this method. | |
| 2122 | Overflow, | |
| 2123 | ||
| 2124 | PermissionDenied, | |
| 2125 | LockedMemoryLimitExceeded, | |
| 2126 | MemoryMappingNotSupported, | |
| 2127 | } || Allocator.Error || std.fs.File.OpenError || OpenError; | |
| 2128 | ||
| 2129 | /// Reads debug info from an already mapped ELF file. | |
| 2130 | /// | |
| 2131 | /// If the required sections aren't present but a reference to external debug | |
| 2132 | /// info is, then this this function will recurse to attempt to load the debug | |
| 2133 | /// sections from an external file. | |
| 2134 | pub fn load( | |
| 2135 | gpa: Allocator, | |
| 2136 | mapped_mem: []align(std.heap.page_size_min) const u8, | |
| 2137 | build_id: ?[]const u8, | |
| 2138 | expected_crc: ?u32, | |
| 2139 | parent_sections: *Dwarf.SectionArray, | |
| 2140 | parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8, | |
| 2141 | elf_filename: ?[]const u8, | |
| 2142 | ) LoadError!Dwarf.ElfModule { | |
| 2143 | if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo; | |
| 2144 | ||
| 2145 | const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); | |
| 2146 | if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; | |
| 2147 | if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; | |
| 2148 | ||
| 2149 | const endian: Endian = switch (hdr.e_ident[elf.EI_DATA]) { | |
| 2150 | elf.ELFDATA2LSB => .little, | |
| 2151 | elf.ELFDATA2MSB => .big, | |
| 2152 | else => return error.InvalidElfEndian, | |
| 2153 | }; | |
| 2154 | if (endian != native_endian) return error.UnimplementedDwarfForeignEndian; | |
| 2155 | ||
| 2156 | const shoff = hdr.e_shoff; | |
| 2157 | const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); | |
| 2158 | const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[cast(usize, str_section_off) orelse return error.Overflow])); | |
| 2159 | const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size]; | |
| 2160 | const shdrs = @as( | |
| 2161 | [*]const elf.Shdr, | |
| 2162 | @ptrCast(@alignCast(&mapped_mem[shoff])), | |
| 2163 | )[0..hdr.e_shnum]; | |
| 2164 | ||
| 2165 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | |
| 2166 | ||
| 2167 | // Combine section list. This takes ownership over any owned sections from the parent scope. | |
| 2168 | for (parent_sections, &sections) |*parent, *section_elem| { | |
| 2169 | if (parent.*) |*p| { | |
| 2170 | section_elem.* = p.*; | |
| 2171 | p.owned = false; | |
| 2172 | } | |
| 2173 | } | |
| 2174 | errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data); | |
| 2175 | ||
| 2176 | var separate_debug_filename: ?[]const u8 = null; | |
| 2177 | var separate_debug_crc: ?u32 = null; | |
| 2178 | ||
| 2179 | for (shdrs) |*shdr| { | |
| 2180 | if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; | |
| 2181 | const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); | |
| 2182 | ||
| 2183 | if (mem.eql(u8, name, ".gnu_debuglink")) { | |
| 2184 | const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | |
| 2185 | const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0); | |
| 2186 | const crc_offset = mem.alignForward(usize, debug_filename.len + 1, 4); | |
| 2187 | const crc_bytes = gnu_debuglink[crc_offset..][0..4]; | |
| 2188 | separate_debug_crc = mem.readInt(u32, crc_bytes, endian); | |
| 2189 | separate_debug_filename = debug_filename; | |
| 2190 | continue; | |
| 2191 | } | |
| 2192 | ||
| 2193 | var section_index: ?usize = null; | |
| 2194 | inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |sect, i| { | |
| 2195 | if (mem.eql(u8, "." ++ sect.name, name)) section_index = i; | |
| 2196 | } | |
| 2197 | if (section_index == null) continue; | |
| 2198 | if (sections[section_index.?] != null) continue; | |
| 2199 | ||
| 2200 | const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | |
| 2201 | sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { | |
| 2202 | var section_reader: Reader = .fixed(section_bytes); | |
| 2203 | const chdr = section_reader.takeStruct(elf.Chdr, endian) catch continue; | |
| 2204 | if (chdr.ch_type != .ZLIB) continue; | |
| 2205 | ||
| 2206 | var decompress: std.compress.flate.Decompress = .init(&section_reader, .zlib, &.{}); | |
| 2207 | var decompressed_section: ArrayList(u8) = .empty; | |
| 2208 | defer decompressed_section.deinit(gpa); | |
| 2209 | decompress.reader.appendRemainingUnlimited(gpa, &decompressed_section) catch { | |
| 2210 | invalidDebugInfoDetected(); | |
| 2211 | continue; | |
| 2212 | }; | |
| 2213 | if (chdr.ch_size != decompressed_section.items.len) { | |
| 2214 | invalidDebugInfoDetected(); | |
| 2215 | continue; | |
| 2216 | } | |
| 2217 | break :blk .{ | |
| 2218 | .data = try decompressed_section.toOwnedSlice(gpa), | |
| 2219 | .virtual_address = shdr.sh_addr, | |
| 2220 | .owned = true, | |
| 2221 | }; | |
| 2222 | } else .{ | |
| 2223 | .data = section_bytes, | |
| 2224 | .virtual_address = shdr.sh_addr, | |
| 2225 | .owned = false, | |
| 2226 | }; | |
| 2227 | } | |
| 2228 | ||
| 2229 | const missing_debug_info = | |
| 2230 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | |
| 2231 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | |
| 2232 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | |
| 2233 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; | |
| 2234 | ||
| 2235 | // Attempt to load debug info from an external file | |
| 2236 | // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html | |
| 2237 | if (missing_debug_info) { | |
| 2238 | ||
| 2239 | // Only allow one level of debug info nesting | |
| 2240 | if (parent_mapped_mem) |_| { | |
| 2241 | return error.MissingDebugInfo; | |
| 2242 | } | |
| 2243 | ||
| 2244 | // $XDG_CACHE_HOME/debuginfod_client/<buildid>/debuginfo | |
| 2245 | // This only opportunisticly tries to load from the debuginfod cache, but doesn't try to populate it. | |
| 2246 | // One can manually run `debuginfod-find debuginfo PATH` to download the symbols | |
| 2247 | if (build_id) |id| blk: { | |
| 2248 | var debuginfod_dir: std.fs.Dir = switch (builtin.os.tag) { | |
| 2249 | .wasi, .windows => break :blk, | |
| 2250 | else => dir: { | |
| 2251 | if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |path| { | |
| 2252 | break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk; | |
| 2253 | } | |
| 2254 | if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| { | |
| 2255 | if (cache_path.len > 0) { | |
| 2256 | const path = std.fs.path.join(gpa, &[_][]const u8{ cache_path, "debuginfod_client" }) catch break :blk; | |
| 2257 | defer gpa.free(path); | |
| 2258 | break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk; | |
| 2259 | } | |
| 2260 | } | |
| 2261 | if (std.posix.getenv("HOME")) |home_path| { | |
| 2262 | const path = std.fs.path.join(gpa, &[_][]const u8{ home_path, ".cache", "debuginfod_client" }) catch break :blk; | |
| 2263 | defer gpa.free(path); | |
| 2264 | break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk; | |
| 2265 | } | |
| 2266 | break :blk; | |
| 2267 | }, | |
| 2268 | }; | |
| 2269 | defer debuginfod_dir.close(); | |
| 2270 | ||
| 2271 | const filename = std.fmt.allocPrint(gpa, "{x}/debuginfo", .{id}) catch break :blk; | |
| 2272 | defer gpa.free(filename); | |
| 2273 | ||
| 2274 | const path: Path = .{ | |
| 2275 | .root_dir = .{ .path = null, .handle = debuginfod_dir }, | |
| 2276 | .sub_path = filename, | |
| 2277 | }; | |
| 2278 | ||
| 2279 | return loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch break :blk; | |
| 2280 | } | |
| 2281 | ||
| 2282 | const global_debug_directories = [_][]const u8{ | |
| 2283 | "/usr/lib/debug", | |
| 2284 | }; | |
| 2285 | ||
| 2286 | // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug | |
| 2287 | if (build_id) |id| blk: { | |
| 2288 | if (id.len < 3) break :blk; | |
| 2289 | ||
| 2290 | // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice | |
| 2291 | const extension = ".debug"; | |
| 2292 | var id_prefix_buf: [2]u8 = undefined; | |
| 2293 | var filename_buf: [38 + extension.len]u8 = undefined; | |
| 2294 | ||
| 2295 | _ = std.fmt.bufPrint(&id_prefix_buf, "{x}", .{id[0..1]}) catch unreachable; | |
| 2296 | const filename = std.fmt.bufPrint(&filename_buf, "{x}" ++ extension, .{id[1..]}) catch break :blk; | |
| 2297 | ||
| 2298 | for (global_debug_directories) |global_directory| { | |
| 2299 | const path: Path = .{ | |
| 2300 | .root_dir = std.Build.Cache.Directory.cwd(), | |
| 2301 | .sub_path = try std.fs.path.join(gpa, &.{ | |
| 2302 | global_directory, ".build-id", &id_prefix_buf, filename, | |
| 2303 | }), | |
| 2304 | }; | |
| 2305 | defer gpa.free(path.sub_path); | |
| 2306 | ||
| 2307 | return loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch continue; | |
| 2308 | } | |
| 2309 | } | |
| 2310 | ||
| 2311 | // use the path from .gnu_debuglink, in the same search order as gdb | |
| 2312 | if (separate_debug_filename) |separate_filename| blk: { | |
| 2313 | if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) | |
| 2314 | return error.MissingDebugInfo; | |
| 2315 | ||
| 2316 | exe_dir: { | |
| 2317 | var exe_dir_buf: [std.fs.max_path_bytes]u8 = undefined; | |
| 2318 | const exe_dir_path = std.fs.selfExeDirPath(&exe_dir_buf) catch break :exe_dir; | |
| 2319 | var exe_dir = std.fs.openDirAbsolute(exe_dir_path, .{}) catch break :exe_dir; | |
| 2320 | defer exe_dir.close(); | |
| 2321 | ||
| 2322 | // <exe_dir>/<gnu_debuglink> | |
| 2323 | if (loadPath( | |
| 2324 | gpa, | |
| 2325 | .{ | |
| 2326 | .root_dir = .{ .path = null, .handle = exe_dir }, | |
| 2327 | .sub_path = separate_filename, | |
| 2328 | }, | |
| 2329 | null, | |
| 2330 | separate_debug_crc, | |
| 2331 | &sections, | |
| 2332 | mapped_mem, | |
| 2333 | )) |debug_info| { | |
| 2334 | return debug_info; | |
| 2335 | } else |_| {} | |
| 2336 | ||
| 2337 | // <exe_dir>/.debug/<gnu_debuglink> | |
| 2338 | const path: Path = .{ | |
| 2339 | .root_dir = .{ .path = null, .handle = exe_dir }, | |
| 2340 | .sub_path = try std.fs.path.join(gpa, &.{ ".debug", separate_filename }), | |
| 2341 | }; | |
| 2342 | defer gpa.free(path.sub_path); | |
| 2343 | ||
| 2344 | if (loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | |
| 2345 | } | |
| 2346 | ||
| 2347 | var cwd_buf: [std.fs.max_path_bytes]u8 = undefined; | |
| 2348 | const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :blk; | |
| 2349 | ||
| 2350 | // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink> | |
| 2351 | for (global_debug_directories) |global_directory| { | |
| 2352 | const path: Path = .{ | |
| 2353 | .root_dir = std.Build.Cache.Directory.cwd(), | |
| 2354 | .sub_path = try std.fs.path.join(gpa, &.{ global_directory, cwd_path, separate_filename }), | |
| 2355 | }; | |
| 2356 | defer gpa.free(path.sub_path); | |
| 2357 | if (loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | |
| 2358 | } | |
| 2359 | } | |
| 2360 | ||
| 2361 | return error.MissingDebugInfo; | |
| 2362 | } | |
| 2363 | ||
| 2364 | var di: Dwarf = .{ | |
| 2365 | .endian = endian, | |
| 2366 | .sections = sections, | |
| 2367 | .is_macho = false, | |
| 2368 | }; | |
| 2369 | ||
| 2370 | try Dwarf.open(&di, gpa); | |
| 2371 | ||
| 2372 | return .{ | |
| 2373 | .base_address = 0, | |
| 2374 | .dwarf = di, | |
| 2375 | .mapped_memory = parent_mapped_mem orelse mapped_mem, | |
| 2376 | .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null, | |
| 2377 | }; | |
| 2378 | } | |
| 2379 | ||
| 2380 | pub fn loadPath( | |
| 2381 | gpa: Allocator, | |
| 2382 | elf_file_path: Path, | |
| 2383 | build_id: ?[]const u8, | |
| 2384 | expected_crc: ?u32, | |
| 2385 | parent_sections: *Dwarf.SectionArray, | |
| 2386 | parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8, | |
| 2387 | ) LoadError!Dwarf.ElfModule { | |
| 2388 | const elf_file = elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{}) catch |err| switch (err) { | |
| 2389 | error.FileNotFound => return missing(), | |
| 1513 | return .{ | |
| 1514 | .name = di.getSymbolName(address), | |
| 1515 | .compile_unit_name = compile_unit.die.getAttrString(di, endian, std.dwarf.AT.name, di.section(.debug_str), compile_unit) catch |err| switch (err) { | |
| 1516 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | |
| 1517 | }, | |
| 1518 | .source_location = di.getLineNumberInfo(gpa, endian, compile_unit, address) catch |err| switch (err) { | |
| 1519 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | |
| 2390 | 1520 | else => return err, |
| 2391 | }; | |
| 2392 | defer elf_file.close(); | |
| 2393 | ||
| 2394 | const end_pos = elf_file.getEndPos() catch return bad(); | |
| 2395 | const file_len = cast(usize, end_pos) orelse return error.Overflow; | |
| 2396 | ||
| 2397 | const mapped_mem = std.posix.mmap( | |
| 2398 | null, | |
| 2399 | file_len, | |
| 2400 | std.posix.PROT.READ, | |
| 2401 | .{ .TYPE = .SHARED }, | |
| 2402 | elf_file.handle, | |
| 2403 | 0, | |
| 2404 | ) catch |err| switch (err) { | |
| 2405 | error.MappingAlreadyExists => unreachable, | |
| 2406 | else => |e| return e, | |
| 2407 | }; | |
| 2408 | errdefer std.posix.munmap(mapped_mem); | |
| 2409 | ||
| 2410 | return load( | |
| 2411 | gpa, | |
| 2412 | mapped_mem, | |
| 2413 | build_id, | |
| 2414 | expected_crc, | |
| 2415 | parent_sections, | |
| 2416 | parent_mapped_mem, | |
| 2417 | elf_file_path.sub_path, | |
| 2418 | ); | |
| 2419 | } | |
| 2420 | }; | |
| 2421 | ||
| 2422 | pub fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol { | |
| 2423 | if (di.findCompileUnit(address)) |compile_unit| { | |
| 2424 | return .{ | |
| 2425 | .name = di.getSymbolName(address) orelse "???", | |
| 2426 | .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) { | |
| 2427 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", | |
| 2428 | }, | |
| 2429 | .source_location = di.getLineNumberInfo(allocator, compile_unit, address) catch |err| switch (err) { | |
| 2430 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | |
| 2431 | else => return err, | |
| 2432 | }, | |
| 2433 | }; | |
| 2434 | } else |err| switch (err) { | |
| 2435 | error.MissingDebugInfo, error.InvalidDebugInfo => return .{}, | |
| 2436 | else => return err, | |
| 2437 | } | |
| 2438 | } | |
| 2439 | ||
| 2440 | pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 { | |
| 2441 | const start = cast(usize, offset) orelse return error.Overflow; | |
| 2442 | const end = start + (cast(usize, size) orelse return error.Overflow); | |
| 2443 | return ptr[start..end]; | |
| 1521 | }, | |
| 1522 | }; | |
| 2444 | 1523 | } |
| 2445 | 1524 | |
| 2446 | fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 { | |
| 1525 | /// DWARF5 7.4: "In the 32-bit DWARF format, all values that represent lengths of DWARF sections and | |
| 1526 | /// offsets relative to the beginning of DWARF sections are represented using four bytes. In the | |
| 1527 | /// 64-bit DWARF format, all values that represent lengths of DWARF sections and offsets relative to | |
| 1528 | /// the beginning of DWARF sections are represented using eight bytes". | |
| 1529 | /// | |
| 1530 | /// This function is for reading such values. | |
| 1531 | fn readFormatSizedInt(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 { | |
| 2447 | 1532 | return switch (format) { |
| 2448 | 1533 | .@"32" => try r.takeInt(u32, endian), |
| 2449 | 1534 | .@"64" => try r.takeInt(u64, endian), |
| 2450 | 1535 | }; |
| 2451 | 1536 | } |
| 2452 | 1537 | |
| 2453 | fn nativeFormat() std.dwarf.Format { | |
| 2454 | return switch (@sizeOf(usize)) { | |
| 2455 | 4 => .@"32", | |
| 2456 | 8 => .@"64", | |
| 2457 | else => @compileError("unsupported @sizeOf(usize)"), | |
| 1538 | fn readAddress(r: *Reader, endian: Endian, addr_size_bytes: u8) !u64 { | |
| 1539 | return switch (addr_size_bytes) { | |
| 1540 | 2 => try r.takeInt(u16, endian), | |
| 1541 | 4 => try r.takeInt(u32, endian), | |
| 1542 | 8 => try r.takeInt(u64, endian), | |
| 1543 | else => return bad(), | |
| 2458 | 1544 | }; |
| 2459 | 1545 | } |
lib/std/debug/Dwarf/SelfUnwinder.zig created+334| ... | ... | @@ -0,0 +1,334 @@ |
| 1 | //! Implements stack unwinding based on `Dwarf.Unwind`. The caller is responsible for providing the | |
| 2 | //! initialized `Dwarf.Unwind` from the `.debug_frame` (or equivalent) section; this type handles | |
| 3 | //! computing and applying the CFI register rules to evolve a `std.debug.cpu_context.Native` through | |
| 4 | //! stack frames, hence performing the virtual unwind. | |
| 5 | //! | |
| 6 | //! Notably, this type is a valid implementation of `std.debug.SelfInfo.UnwindContext`. | |
| 7 | ||
| 8 | /// The state of the CPU in the current stack frame. | |
| 9 | cpu_state: std.debug.cpu_context.Native, | |
| 10 | /// The value of the Program Counter in this frame. This is almost the same as the value of the IP | |
| 11 | /// register in `cpu_state`, but may be off by one because the IP is typically a *return* address. | |
| 12 | pc: usize, | |
| 13 | ||
| 14 | cfi_vm: Dwarf.Unwind.VirtualMachine, | |
| 15 | expr_vm: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), | |
| 16 | ||
| 17 | pub const CacheEntry = struct { | |
| 18 | const max_regs = 32; | |
| 19 | ||
| 20 | pc: usize, | |
| 21 | cie: *const Dwarf.Unwind.CommonInformationEntry, | |
| 22 | cfa_rule: Dwarf.Unwind.VirtualMachine.CfaRule, | |
| 23 | num_rules: u8, | |
| 24 | rules_regs: [max_regs]u16, | |
| 25 | rules: [max_regs]Dwarf.Unwind.VirtualMachine.RegisterRule, | |
| 26 | ||
| 27 | pub fn find(entries: []const CacheEntry, pc: usize) ?*const CacheEntry { | |
| 28 | assert(pc != 0); | |
| 29 | const idx = std.hash.int(pc) % entries.len; | |
| 30 | const entry = &entries[idx]; | |
| 31 | return if (entry.pc == pc) entry else null; | |
| 32 | } | |
| 33 | ||
| 34 | pub fn populate(entry: *const CacheEntry, entries: []CacheEntry) void { | |
| 35 | const idx = std.hash.int(entry.pc) % entries.len; | |
| 36 | entries[idx] = entry.*; | |
| 37 | } | |
| 38 | ||
| 39 | pub const empty: CacheEntry = .{ | |
| 40 | .pc = 0, | |
| 41 | .cie = undefined, | |
| 42 | .cfa_rule = undefined, | |
| 43 | .num_rules = undefined, | |
| 44 | .rules_regs = undefined, | |
| 45 | .rules = undefined, | |
| 46 | }; | |
| 47 | }; | |
| 48 | ||
| 49 | pub fn init(cpu_context: *const std.debug.cpu_context.Native) SelfUnwinder { | |
| 50 | // `@constCast` is safe because we aren't going to store to the resulting pointer. | |
| 51 | const raw_pc_ptr = regNative(@constCast(cpu_context), ip_reg_num) catch |err| switch (err) { | |
| 52 | error.InvalidRegister => unreachable, // `ip_reg_num` is definitely valid | |
| 53 | error.UnsupportedRegister => unreachable, // the implementation needs to support ip | |
| 54 | error.IncompatibleRegisterSize => unreachable, // ip is definitely `usize`-sized | |
| 55 | }; | |
| 56 | const pc = stripInstructionPtrAuthCode(raw_pc_ptr.*); | |
| 57 | return .{ | |
| 58 | .cpu_state = cpu_context.*, | |
| 59 | .pc = pc, | |
| 60 | .cfi_vm = .{}, | |
| 61 | .expr_vm = .{}, | |
| 62 | }; | |
| 63 | } | |
| 64 | ||
| 65 | pub fn deinit(unwinder: *SelfUnwinder, gpa: Allocator) void { | |
| 66 | unwinder.cfi_vm.deinit(gpa); | |
| 67 | unwinder.expr_vm.deinit(gpa); | |
| 68 | unwinder.* = undefined; | |
| 69 | } | |
| 70 | ||
| 71 | pub fn getFp(unwinder: *const SelfUnwinder) usize { | |
| 72 | // `@constCast` is safe because we aren't going to store to the resulting pointer. | |
| 73 | const ptr = regNative(@constCast(&unwinder.cpu_state), fp_reg_num) catch |err| switch (err) { | |
| 74 | error.InvalidRegister => unreachable, // `fp_reg_num` is definitely valid | |
| 75 | error.UnsupportedRegister => unreachable, // the implementation needs to support fp | |
| 76 | error.IncompatibleRegisterSize => unreachable, // fp is a pointer so is `usize`-sized | |
| 77 | }; | |
| 78 | return ptr.*; | |
| 79 | } | |
| 80 | ||
| 81 | /// Compute the rule set for the address `unwinder.pc` from the information in `unwind`. The caller | |
| 82 | /// may store the returned rule set in a simple fixed-size cache keyed on the `pc` field to avoid | |
| 83 | /// frequently recomputing register rules when unwinding many times. | |
| 84 | /// | |
| 85 | /// To actually apply the computed rules, see `next`. | |
| 86 | pub fn computeRules( | |
| 87 | unwinder: *SelfUnwinder, | |
| 88 | gpa: Allocator, | |
| 89 | unwind: *const Dwarf.Unwind, | |
| 90 | load_offset: usize, | |
| 91 | explicit_fde_offset: ?usize, | |
| 92 | ) !CacheEntry { | |
| 93 | assert(unwinder.pc != 0); | |
| 94 | ||
| 95 | const pc_vaddr = unwinder.pc - load_offset; | |
| 96 | ||
| 97 | const fde_offset = explicit_fde_offset orelse try unwind.lookupPc( | |
| 98 | pc_vaddr, | |
| 99 | @sizeOf(usize), | |
| 100 | native_endian, | |
| 101 | ) orelse return error.MissingDebugInfo; | |
| 102 | const cie, const fde = try unwind.getFde(fde_offset, native_endian); | |
| 103 | ||
| 104 | // `lookupPc` can return false positives, so check if the FDE *actually* includes the pc | |
| 105 | if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) { | |
| 106 | return error.MissingDebugInfo; | |
| 107 | } | |
| 108 | ||
| 109 | unwinder.cfi_vm.reset(); | |
| 110 | const row = try unwinder.cfi_vm.runTo(gpa, pc_vaddr, cie, &fde, @sizeOf(usize), native_endian); | |
| 111 | const cols = unwinder.cfi_vm.rowColumns(&row); | |
| 112 | ||
| 113 | if (cols.len > CacheEntry.max_regs) return error.UnsupportedDebugInfo; | |
| 114 | ||
| 115 | var entry: CacheEntry = .{ | |
| 116 | .pc = unwinder.pc, | |
| 117 | .cie = cie, | |
| 118 | .cfa_rule = row.cfa, | |
| 119 | .num_rules = @intCast(cols.len), | |
| 120 | .rules_regs = undefined, | |
| 121 | .rules = undefined, | |
| 122 | }; | |
| 123 | for (cols, 0..) |col, i| { | |
| 124 | entry.rules_regs[i] = col.register; | |
| 125 | entry.rules[i] = col.rule; | |
| 126 | } | |
| 127 | return entry; | |
| 128 | } | |
| 129 | ||
| 130 | /// Applies the register rules given in `cache_entry` to the current state of `unwinder`. The caller | |
| 131 | /// is responsible for ensuring that `cache_entry` contains the correct rule set for `unwinder.pc`. | |
| 132 | /// | |
| 133 | /// `unwinder.cpu_state` and `unwinder.pc` are updated to refer to the next frame, and this frame's | |
| 134 | /// return address is returned as a `usize`. | |
| 135 | pub fn next(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) std.debug.SelfInfoError!usize { | |
| 136 | return unwinder.nextInner(gpa, cache_entry) catch |err| switch (err) { | |
| 137 | error.OutOfMemory, | |
| 138 | error.InvalidDebugInfo, | |
| 139 | => |e| return e, | |
| 140 | ||
| 141 | error.UnsupportedRegister, | |
| 142 | error.UnimplementedExpressionCall, | |
| 143 | error.UnimplementedOpcode, | |
| 144 | error.UnimplementedUserOpcode, | |
| 145 | error.UnimplementedTypedComparison, | |
| 146 | error.UnimplementedTypeConversion, | |
| 147 | error.UnknownExpressionOpcode, | |
| 148 | => return error.UnsupportedDebugInfo, | |
| 149 | ||
| 150 | error.ReadFailed, | |
| 151 | error.EndOfStream, | |
| 152 | error.Overflow, | |
| 153 | error.IncompatibleRegisterSize, | |
| 154 | error.InvalidRegister, | |
| 155 | error.IncompleteExpressionContext, | |
| 156 | error.InvalidCFAOpcode, | |
| 157 | error.InvalidExpression, | |
| 158 | error.InvalidFrameBase, | |
| 159 | error.InvalidIntegralTypeSize, | |
| 160 | error.InvalidSubExpression, | |
| 161 | error.InvalidTypeLength, | |
| 162 | error.TruncatedIntegralType, | |
| 163 | error.DivisionByZero, | |
| 164 | => return error.InvalidDebugInfo, | |
| 165 | }; | |
| 166 | } | |
| 167 | ||
| 168 | fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) !usize { | |
| 169 | const format = cache_entry.cie.format; | |
| 170 | const return_address_register = cache_entry.cie.return_address_register; | |
| 171 | ||
| 172 | const cfa = switch (cache_entry.cfa_rule) { | |
| 173 | .none => return error.InvalidDebugInfo, | |
| 174 | .reg_off => |ro| cfa: { | |
| 175 | const ptr = try regNative(&unwinder.cpu_state, ro.register); | |
| 176 | break :cfa try applyOffset(ptr.*, ro.offset); | |
| 177 | }, | |
| 178 | .expression => |expr| cfa: { | |
| 179 | // On all implemented architectures, the CFA is defined to be the previous frame's SP | |
| 180 | const prev_cfa_val = (try regNative(&unwinder.cpu_state, sp_reg_num)).*; | |
| 181 | unwinder.expr_vm.reset(); | |
| 182 | const value = try unwinder.expr_vm.run(expr, gpa, .{ | |
| 183 | .format = format, | |
| 184 | .cpu_context = &unwinder.cpu_state, | |
| 185 | }, prev_cfa_val) orelse return error.InvalidDebugInfo; | |
| 186 | switch (value) { | |
| 187 | .generic => |g| break :cfa g, | |
| 188 | else => return error.InvalidDebugInfo, | |
| 189 | } | |
| 190 | }, | |
| 191 | }; | |
| 192 | ||
| 193 | // If unspecified, we'll use the default rule for the return address register, which is | |
| 194 | // typically equivalent to `.undefined` (meaning there is no return address), but may be | |
| 195 | // overriden by ABIs. | |
| 196 | var has_return_address: bool = builtin.cpu.arch.isAARCH64() and | |
| 197 | return_address_register >= 19 and | |
| 198 | return_address_register <= 28; | |
| 199 | ||
| 200 | // Create a copy of the CPU state, to which we will apply the new rules. | |
| 201 | var new_cpu_state = unwinder.cpu_state; | |
| 202 | ||
| 203 | // On all implemented architectures, the CFA is defined to be the previous frame's SP | |
| 204 | (try regNative(&new_cpu_state, sp_reg_num)).* = cfa; | |
| 205 | ||
| 206 | const rules_len = cache_entry.num_rules; | |
| 207 | for (cache_entry.rules_regs[0..rules_len], cache_entry.rules[0..rules_len]) |register, rule| { | |
| 208 | const new_val: union(enum) { | |
| 209 | same, | |
| 210 | undefined, | |
| 211 | val: usize, | |
| 212 | bytes: []const u8, | |
| 213 | } = switch (rule) { | |
| 214 | .default => val: { | |
| 215 | // The default rule is typically equivalent to `.undefined`, but ABIs may override it. | |
| 216 | if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 28) { | |
| 217 | break :val .same; | |
| 218 | } | |
| 219 | break :val .undefined; | |
| 220 | }, | |
| 221 | .undefined => .undefined, | |
| 222 | .same_value => .same, | |
| 223 | .offset => |offset| val: { | |
| 224 | const ptr: *const usize = @ptrFromInt(try applyOffset(cfa, offset)); | |
| 225 | break :val .{ .val = ptr.* }; | |
| 226 | }, | |
| 227 | .val_offset => |offset| .{ .val = try applyOffset(cfa, offset) }, | |
| 228 | .register => |r| .{ .bytes = try unwinder.cpu_state.dwarfRegisterBytes(r) }, | |
| 229 | .expression => |expr| val: { | |
| 230 | unwinder.expr_vm.reset(); | |
| 231 | const value = try unwinder.expr_vm.run(expr, gpa, .{ | |
| 232 | .format = format, | |
| 233 | .cpu_context = &unwinder.cpu_state, | |
| 234 | }, cfa) orelse return error.InvalidDebugInfo; | |
| 235 | const ptr: *const usize = switch (value) { | |
| 236 | .generic => |addr| @ptrFromInt(addr), | |
| 237 | else => return error.InvalidDebugInfo, | |
| 238 | }; | |
| 239 | break :val .{ .val = ptr.* }; | |
| 240 | }, | |
| 241 | .val_expression => |expr| val: { | |
| 242 | unwinder.expr_vm.reset(); | |
| 243 | const value = try unwinder.expr_vm.run(expr, gpa, .{ | |
| 244 | .format = format, | |
| 245 | .cpu_context = &unwinder.cpu_state, | |
| 246 | }, cfa) orelse return error.InvalidDebugInfo; | |
| 247 | switch (value) { | |
| 248 | .generic => |val| break :val .{ .val = val }, | |
| 249 | else => return error.InvalidDebugInfo, | |
| 250 | } | |
| 251 | }, | |
| 252 | }; | |
| 253 | switch (new_val) { | |
| 254 | .same => {}, | |
| 255 | .undefined => { | |
| 256 | const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); | |
| 257 | @memset(dest, undefined); | |
| 258 | }, | |
| 259 | .val => |val| { | |
| 260 | const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); | |
| 261 | if (dest.len != @sizeOf(usize)) return error.InvalidDebugInfo; | |
| 262 | const dest_ptr: *align(1) usize = @ptrCast(dest); | |
| 263 | dest_ptr.* = val; | |
| 264 | }, | |
| 265 | .bytes => |src| { | |
| 266 | const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); | |
| 267 | if (dest.len != src.len) return error.InvalidDebugInfo; | |
| 268 | @memcpy(dest, src); | |
| 269 | }, | |
| 270 | } | |
| 271 | if (register == return_address_register) { | |
| 272 | has_return_address = new_val != .undefined; | |
| 273 | } | |
| 274 | } | |
| 275 | ||
| 276 | const return_address: usize = if (has_return_address) pc: { | |
| 277 | const raw_ptr = try regNative(&new_cpu_state, return_address_register); | |
| 278 | break :pc stripInstructionPtrAuthCode(raw_ptr.*); | |
| 279 | } else 0; | |
| 280 | ||
| 281 | (try regNative(&new_cpu_state, ip_reg_num)).* = return_address; | |
| 282 | ||
| 283 | // The new CPU state is complete; flush changes. | |
| 284 | unwinder.cpu_state = new_cpu_state; | |
| 285 | ||
| 286 | // The caller will subtract 1 from the return address to get an address corresponding to the | |
| 287 | // function call. However, if this is a signal frame, that's actually incorrect, because the | |
| 288 | // "return address" we have is the instruction which triggered the signal (if the signal | |
| 289 | // handler returned, the instruction would be re-run). Compensate for this by incrementing | |
| 290 | // the address in that case. | |
| 291 | const adjusted_ret_addr = if (cache_entry.cie.is_signal_frame) return_address +| 1 else return_address; | |
| 292 | ||
| 293 | // We also want to do that same subtraction here to get the PC for the next frame's FDE. | |
| 294 | // This is because if the callee was noreturn, then the function call might be the caller's | |
| 295 | // last instruction, so `return_address` might actually point outside of it! | |
| 296 | unwinder.pc = adjusted_ret_addr -| 1; | |
| 297 | ||
| 298 | return adjusted_ret_addr; | |
| 299 | } | |
| 300 | ||
| 301 | pub fn regNative(ctx: *std.debug.cpu_context.Native, num: u16) error{ | |
| 302 | InvalidRegister, | |
| 303 | UnsupportedRegister, | |
| 304 | IncompatibleRegisterSize, | |
| 305 | }!*align(1) usize { | |
| 306 | const bytes = try ctx.dwarfRegisterBytes(num); | |
| 307 | if (bytes.len != @sizeOf(usize)) return error.IncompatibleRegisterSize; | |
| 308 | return @ptrCast(bytes); | |
| 309 | } | |
| 310 | ||
| 311 | /// Since register rules are applied (usually) during a panic, | |
| 312 | /// checked addition / subtraction is used so that we can return | |
| 313 | /// an error and fall back to FP-based unwinding. | |
| 314 | fn applyOffset(base: usize, offset: i64) !usize { | |
| 315 | return if (offset >= 0) | |
| 316 | try std.math.add(usize, base, @as(usize, @intCast(offset))) | |
| 317 | else | |
| 318 | try std.math.sub(usize, base, @as(usize, @intCast(-offset))); | |
| 319 | } | |
| 320 | ||
| 321 | const ip_reg_num = Dwarf.ipRegNum(builtin.target.cpu.arch).?; | |
| 322 | const fp_reg_num = Dwarf.fpRegNum(builtin.target.cpu.arch); | |
| 323 | const sp_reg_num = Dwarf.spRegNum(builtin.target.cpu.arch); | |
| 324 | ||
| 325 | const std = @import("std"); | |
| 326 | const Allocator = std.mem.Allocator; | |
| 327 | const Dwarf = std.debug.Dwarf; | |
| 328 | const assert = std.debug.assert; | |
| 329 | const stripInstructionPtrAuthCode = std.debug.stripInstructionPtrAuthCode; | |
| 330 | ||
| 331 | const builtin = @import("builtin"); | |
| 332 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 333 | ||
| 334 | const SelfUnwinder = @This(); |
lib/std/debug/Dwarf/Unwind.zig created+702| ... | ... | @@ -0,0 +1,702 @@ |
| 1 | //! Contains state relevant to stack unwinding through the DWARF `.debug_frame` section, or the | |
| 2 | //! `.eh_frame` section which is an extension of the former specified by Linux Standard Base Core. | |
| 3 | //! Like `Dwarf`, no assumptions are made about the host's relationship to the target of the unwind | |
| 4 | //! information -- unwind data for any target can be read by any host. | |
| 5 | //! | |
| 6 | //! `Unwind` specifically deals with loading the data from CIEs and FDEs in the section, and with | |
| 7 | //! performing fast lookups of a program counter's corresponding FDE. The CFI instructions in the | |
| 8 | //! CIEs and FDEs can be interpreted by `VirtualMachine`. | |
| 9 | //! | |
| 10 | //! The typical usage of `Unwind` is as follows: | |
| 11 | //! | |
| 12 | //! * Initialize with `initEhFrameHdr` or `initSection`, depending on the available data | |
| 13 | //! * Call `prepare` to scan CIEs and, if necessary, construct a search table | |
| 14 | //! * Call `lookupPc` to find the section offset of the FDE corresponding to a PC | |
| 15 | //! * Call `getFde` to load the corresponding FDE and CIE | |
| 16 | //! * Check that the PC does indeed fall in that range (`lookupPc` may return a false positive) | |
| 17 | //! * Interpret the embedded CFI instructions using `VirtualMachine` | |
| 18 | //! | |
| 19 | //! In some cases, such as when using the "compact unwind" data in Mach-O binaries, the FDE offsets | |
| 20 | //! may already be known. In that case, no call to `lookupPc` is necessary, which means the call to | |
| 21 | //! `prepare` can be optimized to only scan CIEs. | |
| 22 | ||
| 23 | pub const VirtualMachine = @import("Unwind/VirtualMachine.zig"); | |
| 24 | ||
| 25 | frame_section: struct { | |
| 26 | id: Section, | |
| 27 | /// The virtual address of the start of the section. "Virtual address" refers to the address in | |
| 28 | /// the binary (e.g. `sh_addr` in an ELF file); the equivalent runtime address may be relocated | |
| 29 | /// in position-independent binaries. | |
| 30 | vaddr: u64, | |
| 31 | /// The full contents of the section. May have imprecise bounds depending on `section`. This | |
| 32 | /// memory is externally managed. | |
| 33 | /// | |
| 34 | /// For `.debug_frame`, the slice length is exactly equal to the section length. This is needed | |
| 35 | /// to know the number of CIEs and FDEs. | |
| 36 | /// | |
| 37 | /// For `.eh_frame`, the slice length may exceed the section length, i.e. the slice may refer to | |
| 38 | /// more bytes than are in the second. This restriction exists because `.eh_frame_hdr` only | |
| 39 | /// includes the address of the loaded `.eh_frame` data, not its length. It is not a problem | |
| 40 | /// because unlike `.debug_frame`, the end of the CIE/FDE list is signaled through a sentinel | |
| 41 | /// value. If this slice does have bounds, they will still be checked, preventing crashes when | |
| 42 | /// reading potentially-invalid `.eh_frame` data from files. | |
| 43 | bytes: []const u8, | |
| 44 | }, | |
| 45 | ||
| 46 | /// A structure allowing fast lookups of the FDE corresponding to a particular PC. We use a binary | |
| 47 | /// search table for the lookup; essentially, a list of all FDEs ordered by PC range. `null` means | |
| 48 | /// the lookup data is not yet populated, so `prepare` must be called before `lookupPc`. | |
| 49 | lookup: ?union(enum) { | |
| 50 | /// The `.eh_frame_hdr` section contains a pre-computed search table which we can use. | |
| 51 | eh_frame_hdr: struct { | |
| 52 | /// Virtual address of the `.eh_frame_hdr` section. | |
| 53 | vaddr: u64, | |
| 54 | table: EhFrameHeader.SearchTable, | |
| 55 | }, | |
| 56 | /// There is no pre-computed search table, so we have built one ourselves. | |
| 57 | /// Allocated into `gpa` and freed by `deinit`. | |
| 58 | sorted_fdes: []SortedFdeEntry, | |
| 59 | }, | |
| 60 | ||
| 61 | /// Initially empty; populated by `prepare`. | |
| 62 | cie_list: std.MultiArrayList(struct { | |
| 63 | offset: u64, | |
| 64 | cie: CommonInformationEntry, | |
| 65 | }), | |
| 66 | ||
| 67 | const SortedFdeEntry = struct { | |
| 68 | /// This FDE's value of `pc_begin`. | |
| 69 | pc_begin: u64, | |
| 70 | /// Offset into the section of the corresponding FDE, including the entry header. | |
| 71 | fde_offset: u64, | |
| 72 | }; | |
| 73 | ||
| 74 | pub const Section = enum { debug_frame, eh_frame }; | |
| 75 | ||
| 76 | /// Initialize with unwind information from a header loaded from an `.eh_frame_hdr` section, and a | |
| 77 | /// pointer to the contents of the `.eh_frame` section. | |
| 78 | /// | |
| 79 | /// `.eh_frame_hdr` may embed a binary search table of FDEs. If it does, we will use that table for | |
| 80 | /// PC lookups rather than spending time constructing our own search table. | |
| 81 | pub fn initEhFrameHdr(header: EhFrameHeader, section_vaddr: u64, section_bytes_ptr: [*]const u8) Unwind { | |
| 82 | return .{ | |
| 83 | .frame_section = .{ | |
| 84 | .id = .eh_frame, | |
| 85 | .bytes = maxSlice(section_bytes_ptr), | |
| 86 | .vaddr = header.eh_frame_vaddr, | |
| 87 | }, | |
| 88 | .lookup = if (header.search_table) |table| .{ .eh_frame_hdr = .{ | |
| 89 | .vaddr = section_vaddr, | |
| 90 | .table = table, | |
| 91 | } } else null, | |
| 92 | .cie_list = .empty, | |
| 93 | }; | |
| 94 | } | |
| 95 | ||
| 96 | /// Initialize with unwind information from the contents of a `.debug_frame` or `.eh_frame` section. | |
| 97 | /// | |
| 98 | /// If the `.eh_frame_hdr` section is available, consider instead using `initEhFrameHdr`, which | |
| 99 | /// allows the implementation to use a search table embedded in that section if it is available. | |
| 100 | pub fn initSection(section: Section, section_vaddr: u64, section_bytes: []const u8) Unwind { | |
| 101 | return .{ | |
| 102 | .frame_section = .{ | |
| 103 | .id = section, | |
| 104 | .bytes = section_bytes, | |
| 105 | .vaddr = section_vaddr, | |
| 106 | }, | |
| 107 | .lookup = null, | |
| 108 | .cie_list = .empty, | |
| 109 | }; | |
| 110 | } | |
| 111 | ||
| 112 | pub fn deinit(unwind: *Unwind, gpa: Allocator) void { | |
| 113 | if (unwind.lookup) |lookup| switch (lookup) { | |
| 114 | .eh_frame_hdr => {}, | |
| 115 | .sorted_fdes => |fdes| gpa.free(fdes), | |
| 116 | }; | |
| 117 | for (unwind.cie_list.items(.cie)) |*cie| { | |
| 118 | if (cie.last_row) |*lr| { | |
| 119 | gpa.free(lr.cols); | |
| 120 | } | |
| 121 | } | |
| 122 | unwind.cie_list.deinit(gpa); | |
| 123 | } | |
| 124 | ||
| 125 | /// Decoded version of the `.eh_frame_hdr` section. | |
| 126 | pub const EhFrameHeader = struct { | |
| 127 | /// The virtual address (i.e. as given in the binary, before relocations) of the `.eh_frame` | |
| 128 | /// section. This value is important when using `.eh_frame_hdr` to find debug information for | |
| 129 | /// the current binary, because it allows locating where the `.eh_frame` section is loaded in | |
| 130 | /// memory (by adding it to the ELF module's base address). | |
| 131 | eh_frame_vaddr: u64, | |
| 132 | search_table: ?SearchTable, | |
| 133 | ||
| 134 | pub const SearchTable = struct { | |
| 135 | /// The byte offset of the search table into the `.eh_frame_hdr` section. | |
| 136 | offset: u8, | |
| 137 | encoding: EH.PE, | |
| 138 | fde_count: usize, | |
| 139 | /// The actual table entries are viewed as a plain byte slice because `encoding` causes the | |
| 140 | /// size of entries in the table to vary. | |
| 141 | entries: []const u8, | |
| 142 | ||
| 143 | /// Returns the vaddr of the FDE for `pc`, or `null` if no matching FDE was found. | |
| 144 | fn findEntry( | |
| 145 | table: *const SearchTable, | |
| 146 | eh_frame_hdr_vaddr: u64, | |
| 147 | pc: u64, | |
| 148 | addr_size_bytes: u8, | |
| 149 | endian: Endian, | |
| 150 | ) !?u64 { | |
| 151 | const table_vaddr = eh_frame_hdr_vaddr + table.offset; | |
| 152 | const entry_size = try entrySize(table.encoding, addr_size_bytes); | |
| 153 | var left: usize = 0; | |
| 154 | var len: usize = table.fde_count; | |
| 155 | while (len > 1) { | |
| 156 | const mid = left + len / 2; | |
| 157 | var entry_reader: Reader = .fixed(table.entries[mid * entry_size ..][0..entry_size]); | |
| 158 | const pc_begin = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{ | |
| 159 | .pc_rel_base = table_vaddr + left * entry_size, | |
| 160 | .data_rel_base = eh_frame_hdr_vaddr, | |
| 161 | }, endian); | |
| 162 | if (pc < pc_begin) { | |
| 163 | len /= 2; | |
| 164 | } else { | |
| 165 | left = mid; | |
| 166 | len -= len / 2; | |
| 167 | } | |
| 168 | } | |
| 169 | if (len == 0) return null; | |
| 170 | var entry_reader: Reader = .fixed(table.entries[left * entry_size ..][0..entry_size]); | |
| 171 | // Skip past `pc_begin`; we're now interested in the fde offset | |
| 172 | _ = try readEhPointerAbs(&entry_reader, table.encoding.type, addr_size_bytes, endian); | |
| 173 | const fde_ptr = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{ | |
| 174 | .pc_rel_base = table_vaddr + left * entry_size, | |
| 175 | .data_rel_base = eh_frame_hdr_vaddr, | |
| 176 | }, endian); | |
| 177 | return fde_ptr; | |
| 178 | } | |
| 179 | ||
| 180 | fn entrySize(table_enc: EH.PE, addr_size_bytes: u8) !u8 { | |
| 181 | return switch (table_enc.type) { | |
| 182 | .absptr => 2 * addr_size_bytes, | |
| 183 | .udata2, .sdata2 => 4, | |
| 184 | .udata4, .sdata4 => 8, | |
| 185 | .udata8, .sdata8 => 16, | |
| 186 | .uleb128, .sleb128 => return bad(), // this is a binary search table; all entries must be the same size | |
| 187 | _ => return bad(), | |
| 188 | }; | |
| 189 | } | |
| 190 | }; | |
| 191 | ||
| 192 | pub fn parse( | |
| 193 | eh_frame_hdr_vaddr: u64, | |
| 194 | eh_frame_hdr_bytes: []const u8, | |
| 195 | addr_size_bytes: u8, | |
| 196 | endian: Endian, | |
| 197 | ) !EhFrameHeader { | |
| 198 | var r: Reader = .fixed(eh_frame_hdr_bytes); | |
| 199 | ||
| 200 | const version = try r.takeByte(); | |
| 201 | if (version != 1) return bad(); | |
| 202 | ||
| 203 | const eh_frame_ptr_enc: EH.PE = @bitCast(try r.takeByte()); | |
| 204 | const fde_count_enc: EH.PE = @bitCast(try r.takeByte()); | |
| 205 | const table_enc: EH.PE = @bitCast(try r.takeByte()); | |
| 206 | ||
| 207 | const eh_frame_ptr = try readEhPointer(&r, eh_frame_ptr_enc, addr_size_bytes, .{ | |
| 208 | .pc_rel_base = eh_frame_hdr_vaddr + r.seek, | |
| 209 | }, endian); | |
| 210 | ||
| 211 | const table: ?SearchTable = table: { | |
| 212 | if (fde_count_enc == EH.PE.omit) break :table null; | |
| 213 | if (table_enc == EH.PE.omit) break :table null; | |
| 214 | const fde_count = try readEhPointer(&r, fde_count_enc, addr_size_bytes, .{ | |
| 215 | .pc_rel_base = eh_frame_hdr_vaddr + r.seek, | |
| 216 | }, endian); | |
| 217 | const entry_size = try SearchTable.entrySize(table_enc, addr_size_bytes); | |
| 218 | const bytes_offset = r.seek; | |
| 219 | const bytes_len = cast(usize, fde_count * entry_size) orelse return error.EndOfStream; | |
| 220 | const bytes = try r.take(bytes_len); | |
| 221 | break :table .{ | |
| 222 | .encoding = table_enc, | |
| 223 | .fde_count = @intCast(fde_count), | |
| 224 | .entries = bytes, | |
| 225 | .offset = @intCast(bytes_offset), | |
| 226 | }; | |
| 227 | }; | |
| 228 | ||
| 229 | return .{ | |
| 230 | .eh_frame_vaddr = eh_frame_ptr, | |
| 231 | .search_table = table, | |
| 232 | }; | |
| 233 | } | |
| 234 | }; | |
| 235 | ||
| 236 | /// The shared header of an FDE/CIE, containing a length in bytes (DWARF's "initial length field") | |
| 237 | /// and a value which differentiates CIEs from FDEs and maps FDEs to their corresponding CIEs. The | |
| 238 | /// `.eh_frame` format also includes a third variation, here called `.terminator`, which acts as a | |
| 239 | /// sentinel for the whole section. | |
| 240 | /// | |
| 241 | /// `CommonInformationEntry.parse` and `FrameDescriptionEntry.parse` expect the `EntryHeader` to | |
| 242 | /// have been parsed first: they accept data stored in the `EntryHeader`, and only read the bytes | |
| 243 | /// following this header. | |
| 244 | const EntryHeader = union(enum) { | |
| 245 | cie: struct { | |
| 246 | format: Format, | |
| 247 | /// Remaining bytes in the CIE. These are parseable by `CommonInformationEntry.parse`. | |
| 248 | bytes_len: u64, | |
| 249 | }, | |
| 250 | fde: struct { | |
| 251 | /// Offset into the section of the corresponding CIE, *including* its entry header. | |
| 252 | cie_offset: u64, | |
| 253 | /// Remaining bytes in the FDE. These are parseable by `FrameDescriptionEntry.parse`. | |
| 254 | bytes_len: u64, | |
| 255 | }, | |
| 256 | /// The `.eh_frame` format includes terminators which indicate that the last CIE/FDE has been | |
| 257 | /// reached. However, `.debug_frame` does not include such a terminator, so the caller must | |
| 258 | /// keep track of how many section bytes remain when parsing all entries in `.debug_frame`. | |
| 259 | terminator, | |
| 260 | ||
| 261 | fn read(r: *Reader, header_section_offset: u64, section: Section, endian: Endian) !EntryHeader { | |
| 262 | const unit_header = try Dwarf.readUnitHeader(r, endian); | |
| 263 | if (unit_header.unit_length == 0) return .terminator; | |
| 264 | ||
| 265 | // Next is a value which will disambiguate CIEs and FDEs. Annoyingly, LSB Core makes this | |
| 266 | // value always 4-byte, whereas DWARF makes it depend on the `dwarf.Format`. | |
| 267 | const cie_ptr_or_id_size: u8 = switch (section) { | |
| 268 | .eh_frame => 4, | |
| 269 | .debug_frame => switch (unit_header.format) { | |
| 270 | .@"32" => 4, | |
| 271 | .@"64" => 8, | |
| 272 | }, | |
| 273 | }; | |
| 274 | const cie_ptr_or_id = switch (cie_ptr_or_id_size) { | |
| 275 | 4 => try r.takeInt(u32, endian), | |
| 276 | 8 => try r.takeInt(u64, endian), | |
| 277 | else => unreachable, | |
| 278 | }; | |
| 279 | const remaining_bytes = unit_header.unit_length - cie_ptr_or_id_size; | |
| 280 | ||
| 281 | // If this entry is a CIE, then `cie_ptr_or_id` will have this value, which is different | |
| 282 | // between the DWARF `.debug_frame` section and the LSB Core `.eh_frame` section. | |
| 283 | const cie_id: u64 = switch (section) { | |
| 284 | .eh_frame => 0, | |
| 285 | .debug_frame => switch (unit_header.format) { | |
| 286 | .@"32" => maxInt(u32), | |
| 287 | .@"64" => maxInt(u64), | |
| 288 | }, | |
| 289 | }; | |
| 290 | if (cie_ptr_or_id == cie_id) { | |
| 291 | return .{ .cie = .{ | |
| 292 | .format = unit_header.format, | |
| 293 | .bytes_len = remaining_bytes, | |
| 294 | } }; | |
| 295 | } | |
| 296 | ||
| 297 | // This is an FDE -- `cie_ptr_or_id` points to the associated CIE. Unfortunately, the format | |
| 298 | // of that pointer again differs between `.debug_frame` and `.eh_frame`. | |
| 299 | const cie_offset = switch (section) { | |
| 300 | .eh_frame => try std.math.sub(u64, header_section_offset + unit_header.header_length, cie_ptr_or_id), | |
| 301 | .debug_frame => cie_ptr_or_id, | |
| 302 | }; | |
| 303 | return .{ .fde = .{ | |
| 304 | .cie_offset = cie_offset, | |
| 305 | .bytes_len = remaining_bytes, | |
| 306 | } }; | |
| 307 | } | |
| 308 | }; | |
| 309 | ||
| 310 | pub const CommonInformationEntry = struct { | |
| 311 | version: u8, | |
| 312 | format: Format, | |
| 313 | ||
| 314 | /// In version 4, CIEs can specify the address size used in the CIE and associated FDEs. | |
| 315 | /// This value must be used *only* to parse associated FDEs in `FrameDescriptionEntry.parse`. | |
| 316 | addr_size_bytes: u8, | |
| 317 | ||
| 318 | /// Always 0 for versions which do not specify this (currently all versions other than 4). | |
| 319 | segment_selector_size: u8, | |
| 320 | ||
| 321 | code_alignment_factor: u32, | |
| 322 | data_alignment_factor: i32, | |
| 323 | return_address_register: u8, | |
| 324 | ||
| 325 | fde_pointer_enc: EH.PE, | |
| 326 | is_signal_frame: bool, | |
| 327 | ||
| 328 | augmentation_kind: AugmentationKind, | |
| 329 | ||
| 330 | initial_instructions: []const u8, | |
| 331 | ||
| 332 | last_row: ?struct { | |
| 333 | offset: u64, | |
| 334 | cfa: VirtualMachine.CfaRule, | |
| 335 | cols: []VirtualMachine.Column, | |
| 336 | }, | |
| 337 | ||
| 338 | pub const AugmentationKind = enum { none, gcc_eh, lsb_z }; | |
| 339 | ||
| 340 | /// This function expects to read the CIE starting with the version field. | |
| 341 | /// The returned struct references memory backed by `cie_bytes`. | |
| 342 | /// | |
| 343 | /// `length_offset` specifies the offset of this CIE's length field in the | |
| 344 | /// .eh_frame / .debug_frame section. | |
| 345 | fn parse( | |
| 346 | format: Format, | |
| 347 | cie_bytes: []const u8, | |
| 348 | section: Section, | |
| 349 | default_addr_size_bytes: u8, | |
| 350 | ) !CommonInformationEntry { | |
| 351 | // We only read the data through this reader. | |
| 352 | var r: Reader = .fixed(cie_bytes); | |
| 353 | ||
| 354 | const version = try r.takeByte(); | |
| 355 | switch (section) { | |
| 356 | .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion, | |
| 357 | .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion, | |
| 358 | } | |
| 359 | ||
| 360 | const aug_str = try r.takeSentinel(0); | |
| 361 | const aug_kind: AugmentationKind = aug: { | |
| 362 | if (aug_str.len == 0) break :aug .none; | |
| 363 | if (aug_str[0] == 'z') break :aug .lsb_z; | |
| 364 | if (std.mem.eql(u8, aug_str, "eh")) break :aug .gcc_eh; | |
| 365 | // We can't finish parsing the CIE if we don't know what its augmentation means. | |
| 366 | return bad(); | |
| 367 | }; | |
| 368 | ||
| 369 | switch (aug_kind) { | |
| 370 | .none => {}, // no extra data | |
| 371 | .lsb_z => {}, // no extra data yet, but there is a bit later | |
| 372 | .gcc_eh => try r.discardAll(default_addr_size_bytes), // unsupported data | |
| 373 | } | |
| 374 | ||
| 375 | const addr_size_bytes = if (version == 4) try r.takeByte() else default_addr_size_bytes; | |
| 376 | const segment_selector_size: u8 = if (version == 4) try r.takeByte() else 0; | |
| 377 | const code_alignment_factor = try r.takeLeb128(u32); | |
| 378 | const data_alignment_factor = try r.takeLeb128(i32); | |
| 379 | const return_address_register = if (version == 1) try r.takeByte() else try r.takeLeb128(u8); | |
| 380 | ||
| 381 | // This is where LSB's augmentation might add some data. | |
| 382 | const fde_pointer_enc: EH.PE, const is_signal_frame: bool = aug: { | |
| 383 | const default_fde_pointer_enc: EH.PE = .{ .type = .absptr, .rel = .abs }; | |
| 384 | if (aug_kind != .lsb_z) break :aug .{ default_fde_pointer_enc, false }; | |
| 385 | const aug_data_len = try r.takeLeb128(u32); | |
| 386 | var aug_data: Reader = .fixed(try r.take(aug_data_len)); | |
| 387 | var fde_pointer_enc: EH.PE = default_fde_pointer_enc; | |
| 388 | var is_signal_frame = false; | |
| 389 | for (aug_str[1..]) |byte| switch (byte) { | |
| 390 | 'L' => _ = try aug_data.takeByte(), // we ignore the LSDA pointer | |
| 391 | 'P' => { | |
| 392 | const enc: EH.PE = @bitCast(try aug_data.takeByte()); | |
| 393 | const endian: Endian = .little; // irrelevant because we're discarding the value anyway | |
| 394 | _ = try readEhPointerAbs(&aug_data, enc.type, addr_size_bytes, endian); // we ignore the personality routine; endianness is irrelevant since we're discarding | |
| 395 | }, | |
| 396 | 'R' => fde_pointer_enc = @bitCast(try aug_data.takeByte()), | |
| 397 | 'S' => is_signal_frame = true, | |
| 398 | 'B', 'G' => {}, | |
| 399 | else => return bad(), | |
| 400 | }; | |
| 401 | break :aug .{ fde_pointer_enc, is_signal_frame }; | |
| 402 | }; | |
| 403 | ||
| 404 | return .{ | |
| 405 | .format = format, | |
| 406 | .version = version, | |
| 407 | .addr_size_bytes = addr_size_bytes, | |
| 408 | .segment_selector_size = segment_selector_size, | |
| 409 | .code_alignment_factor = code_alignment_factor, | |
| 410 | .data_alignment_factor = data_alignment_factor, | |
| 411 | .return_address_register = return_address_register, | |
| 412 | .fde_pointer_enc = fde_pointer_enc, | |
| 413 | .is_signal_frame = is_signal_frame, | |
| 414 | .augmentation_kind = aug_kind, | |
| 415 | .initial_instructions = r.buffered(), | |
| 416 | .last_row = null, | |
| 417 | }; | |
| 418 | } | |
| 419 | }; | |
| 420 | ||
| 421 | pub const FrameDescriptionEntry = struct { | |
| 422 | pc_begin: u64, | |
| 423 | pc_range: u64, | |
| 424 | instructions: []const u8, | |
| 425 | ||
| 426 | /// This function expects to read the FDE starting at the PC Begin field. | |
| 427 | /// The returned struct references memory backed by `fde_bytes`. | |
| 428 | fn parse( | |
| 429 | /// The virtual address of the FDE we're parsing, *excluding* its entry header (i.e. the | |
| 430 | /// address is after the header). If `fde_bytes` is backed by the memory of a loaded | |
| 431 | /// module's `.eh_frame` section, this will equal `fde_bytes.ptr`. | |
| 432 | fde_vaddr: u64, | |
| 433 | fde_bytes: []const u8, | |
| 434 | cie: *const CommonInformationEntry, | |
| 435 | endian: Endian, | |
| 436 | ) !FrameDescriptionEntry { | |
| 437 | if (cie.segment_selector_size != 0) return error.UnsupportedAddrSize; | |
| 438 | ||
| 439 | var r: Reader = .fixed(fde_bytes); | |
| 440 | ||
| 441 | const pc_begin = try readEhPointer(&r, cie.fde_pointer_enc, cie.addr_size_bytes, .{ | |
| 442 | .pc_rel_base = fde_vaddr, | |
| 443 | }, endian); | |
| 444 | ||
| 445 | // I swear I'm not kidding when I say that PC Range is encoded with `cie.fde_pointer_enc`, but ignoring `rel`. | |
| 446 | const pc_range = switch (try readEhPointerAbs(&r, cie.fde_pointer_enc.type, cie.addr_size_bytes, endian)) { | |
| 447 | .unsigned => |x| x, | |
| 448 | .signed => |x| cast(u64, x) orelse return bad(), | |
| 449 | }; | |
| 450 | ||
| 451 | switch (cie.augmentation_kind) { | |
| 452 | .none, .gcc_eh => {}, | |
| 453 | .lsb_z => { | |
| 454 | // There is augmentation data, but it's irrelevant to us -- it | |
| 455 | // only contains the LSDA pointer, which we don't care about. | |
| 456 | const aug_data_len = try r.takeLeb128(usize); | |
| 457 | _ = try r.discardAll(aug_data_len); | |
| 458 | }, | |
| 459 | } | |
| 460 | ||
| 461 | return .{ | |
| 462 | .pc_begin = pc_begin, | |
| 463 | .pc_range = pc_range, | |
| 464 | .instructions = r.buffered(), | |
| 465 | }; | |
| 466 | } | |
| 467 | }; | |
| 468 | ||
| 469 | /// Builds the CIE list and FDE lookup table if they are not already built. It is required to call | |
| 470 | /// this function at least once before calling `lookupPc` or `getFde`. If only `getFde` is needed, | |
| 471 | /// then `need_lookup` can be set to `false` to make this function more efficient. | |
| 472 | pub fn prepare( | |
| 473 | unwind: *Unwind, | |
| 474 | gpa: Allocator, | |
| 475 | addr_size_bytes: u8, | |
| 476 | endian: Endian, | |
| 477 | need_lookup: bool, | |
| 478 | /// The `__eh_frame` section in Mach-O binaries deviates from the standard `.eh_frame` section | |
| 479 | /// in one way which this function needs to be aware of. | |
| 480 | is_macho: bool, | |
| 481 | ) !void { | |
| 482 | if (unwind.cie_list.len > 0 and (!need_lookup or unwind.lookup != null)) return; | |
| 483 | unwind.cie_list.clearRetainingCapacity(); | |
| 484 | ||
| 485 | if (is_macho) assert(unwind.lookup == null or unwind.lookup.? != .eh_frame_hdr); | |
| 486 | ||
| 487 | const section = unwind.frame_section; | |
| 488 | ||
| 489 | var r: Reader = .fixed(section.bytes); | |
| 490 | var fde_list: std.ArrayList(SortedFdeEntry) = .empty; | |
| 491 | defer fde_list.deinit(gpa); | |
| 492 | ||
| 493 | const saw_terminator = while (r.seek < r.buffer.len) { | |
| 494 | const entry_offset = r.seek; | |
| 495 | switch (try EntryHeader.read(&r, entry_offset, section.id, endian)) { | |
| 496 | .cie => |cie_info| { | |
| 497 | // We will pre-populate a list of CIEs for efficiency: this avoids work re-parsing | |
| 498 | // them every time we look up an FDE. It also lets us cache the result of evaluating | |
| 499 | // the CIE's initial CFI instructions, which is useful because in the vast majority | |
| 500 | // of cases those instructions will be needed to reach the PC we are unwinding to. | |
| 501 | const bytes_len = cast(usize, cie_info.bytes_len) orelse return error.EndOfStream; | |
| 502 | const idx = unwind.cie_list.len; | |
| 503 | try unwind.cie_list.append(gpa, .{ | |
| 504 | .offset = entry_offset, | |
| 505 | .cie = try .parse(cie_info.format, try r.take(bytes_len), section.id, addr_size_bytes), | |
| 506 | }); | |
| 507 | errdefer _ = unwind.cie_list.pop().?; | |
| 508 | try VirtualMachine.populateCieLastRow(gpa, &unwind.cie_list.items(.cie)[idx], addr_size_bytes, endian); | |
| 509 | continue; | |
| 510 | }, | |
| 511 | .fde => |fde_info| { | |
| 512 | const bytes_len = cast(usize, fde_info.bytes_len) orelse return error.EndOfStream; | |
| 513 | if (!need_lookup) { | |
| 514 | try r.discardAll(bytes_len); | |
| 515 | continue; | |
| 516 | } | |
| 517 | const cie = unwind.findCie(fde_info.cie_offset) orelse return error.InvalidDebugInfo; | |
| 518 | const fde: FrameDescriptionEntry = try .parse(section.vaddr + r.seek, try r.take(bytes_len), cie, endian); | |
| 519 | try fde_list.append(gpa, .{ | |
| 520 | .pc_begin = fde.pc_begin, | |
| 521 | .fde_offset = entry_offset, | |
| 522 | }); | |
| 523 | }, | |
| 524 | .terminator => break true, | |
| 525 | } | |
| 526 | } else false; | |
| 527 | const expect_terminator = switch (section.id) { | |
| 528 | .eh_frame => !is_macho, // `.eh_frame` indicates the end of the CIE/FDE list with a sentinel entry, though macOS omits this | |
| 529 | .debug_frame => false, // `.debug_frame` uses the section bounds and does not specify a sentinel entry | |
| 530 | }; | |
| 531 | if (saw_terminator != expect_terminator) return bad(); | |
| 532 | ||
| 533 | if (need_lookup) { | |
| 534 | std.mem.sortUnstable(SortedFdeEntry, fde_list.items, {}, struct { | |
| 535 | fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool { | |
| 536 | ctx; | |
| 537 | return a.pc_begin < b.pc_begin; | |
| 538 | } | |
| 539 | }.lessThan); | |
| 540 | ||
| 541 | // This temporary is necessary to avoid an RLS footgun where `lookup` ends up non-null `undefined` on OOM. | |
| 542 | const final_fdes = try fde_list.toOwnedSlice(gpa); | |
| 543 | unwind.lookup = .{ .sorted_fdes = final_fdes }; | |
| 544 | } | |
| 545 | } | |
| 546 | ||
| 547 | fn findCie(unwind: *const Unwind, offset: u64) ?*const CommonInformationEntry { | |
| 548 | const offsets = unwind.cie_list.items(.offset); | |
| 549 | if (offsets.len == 0) return null; | |
| 550 | var start: usize = 0; | |
| 551 | var len: usize = offsets.len; | |
| 552 | while (len > 1) { | |
| 553 | const mid = len / 2; | |
| 554 | if (offset < offsets[start + mid]) { | |
| 555 | len = mid; | |
| 556 | } else { | |
| 557 | start += mid; | |
| 558 | len -= mid; | |
| 559 | } | |
| 560 | } | |
| 561 | if (offsets[start] != offset) return null; | |
| 562 | return &unwind.cie_list.items(.cie)[start]; | |
| 563 | } | |
| 564 | ||
| 565 | /// Given a program counter value, returns the offset of the corresponding FDE, or `null` if no | |
| 566 | /// matching FDE was found. The returned offset can be passed to `getFde` to load the data | |
| 567 | /// associated with the FDE. | |
| 568 | /// | |
| 569 | /// Before calling this function, `prepare` must return successfully at least once, to ensure that | |
| 570 | /// `unwind.lookup` is populated. | |
| 571 | /// | |
| 572 | /// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must | |
| 573 | /// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`. | |
| 574 | pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: Endian) !?u64 { | |
| 575 | const sorted_fdes: []const SortedFdeEntry = switch (unwind.lookup.?) { | |
| 576 | .eh_frame_hdr => |eh_frame_hdr| { | |
| 577 | const fde_vaddr = try eh_frame_hdr.table.findEntry( | |
| 578 | eh_frame_hdr.vaddr, | |
| 579 | pc, | |
| 580 | addr_size_bytes, | |
| 581 | endian, | |
| 582 | ) orelse return null; | |
| 583 | return std.math.sub(u64, fde_vaddr, unwind.frame_section.vaddr) catch bad(); // convert vaddr to offset | |
| 584 | }, | |
| 585 | .sorted_fdes => |sorted_fdes| sorted_fdes, | |
| 586 | }; | |
| 587 | if (sorted_fdes.len == 0) return null; | |
| 588 | var start: usize = 0; | |
| 589 | var len: usize = sorted_fdes.len; | |
| 590 | while (len > 1) { | |
| 591 | const half = len / 2; | |
| 592 | if (pc < sorted_fdes[start + half].pc_begin) { | |
| 593 | len = half; | |
| 594 | } else { | |
| 595 | start += half; | |
| 596 | len -= half; | |
| 597 | } | |
| 598 | } | |
| 599 | // If any FDE matches, it'll be the one at `start` (maybe false positive). | |
| 600 | return sorted_fdes[start].fde_offset; | |
| 601 | } | |
| 602 | ||
| 603 | /// Get the FDE at a given offset, as well as its associated CIE. This offset typically comes from | |
| 604 | /// `lookupPc`. The CFI instructions within can be evaluated with `VirtualMachine`. | |
| 605 | pub fn getFde(unwind: *const Unwind, fde_offset: u64, endian: Endian) !struct { *const CommonInformationEntry, FrameDescriptionEntry } { | |
| 606 | const section = unwind.frame_section; | |
| 607 | ||
| 608 | if (fde_offset > section.bytes.len) return error.EndOfStream; | |
| 609 | var fde_reader: Reader = .fixed(section.bytes[@intCast(fde_offset)..]); | |
| 610 | const fde_info = switch (try EntryHeader.read(&fde_reader, fde_offset, section.id, endian)) { | |
| 611 | .fde => |info| info, | |
| 612 | .cie, .terminator => return bad(), // This is meant to be an FDE | |
| 613 | }; | |
| 614 | ||
| 615 | const cie = unwind.findCie(fde_info.cie_offset) orelse return error.InvalidDebugInfo; | |
| 616 | const fde: FrameDescriptionEntry = try .parse( | |
| 617 | section.vaddr + fde_offset + fde_reader.seek, | |
| 618 | try fde_reader.take(cast(usize, fde_info.bytes_len) orelse return error.EndOfStream), | |
| 619 | cie, | |
| 620 | endian, | |
| 621 | ); | |
| 622 | ||
| 623 | return .{ cie, fde }; | |
| 624 | } | |
| 625 | ||
| 626 | const EhPointerContext = struct { | |
| 627 | /// The address of the pointer field itself | |
| 628 | pc_rel_base: u64, | |
| 629 | // These relative addressing modes are only used in specific cases, and | |
| 630 | // might not be available / required in all parsing contexts | |
| 631 | data_rel_base: ?u64 = null, | |
| 632 | text_rel_base: ?u64 = null, | |
| 633 | function_rel_base: ?u64 = null, | |
| 634 | }; | |
| 635 | /// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`. | |
| 636 | fn readEhPointerAbs(r: *Reader, enc_ty: EH.PE.Type, addr_size_bytes: u8, endian: Endian) !union(enum) { | |
| 637 | signed: i64, | |
| 638 | unsigned: u64, | |
| 639 | } { | |
| 640 | return switch (enc_ty) { | |
| 641 | .absptr => .{ | |
| 642 | .unsigned = switch (addr_size_bytes) { | |
| 643 | 2 => try r.takeInt(u16, endian), | |
| 644 | 4 => try r.takeInt(u32, endian), | |
| 645 | 8 => try r.takeInt(u64, endian), | |
| 646 | else => return error.UnsupportedAddrSize, | |
| 647 | }, | |
| 648 | }, | |
| 649 | .uleb128 => .{ .unsigned = try r.takeLeb128(u64) }, | |
| 650 | .udata2 => .{ .unsigned = try r.takeInt(u16, endian) }, | |
| 651 | .udata4 => .{ .unsigned = try r.takeInt(u32, endian) }, | |
| 652 | .udata8 => .{ .unsigned = try r.takeInt(u64, endian) }, | |
| 653 | .sleb128 => .{ .signed = try r.takeLeb128(i64) }, | |
| 654 | .sdata2 => .{ .signed = try r.takeInt(i16, endian) }, | |
| 655 | .sdata4 => .{ .signed = try r.takeInt(i32, endian) }, | |
| 656 | .sdata8 => .{ .signed = try r.takeInt(i64, endian) }, | |
| 657 | else => return bad(), | |
| 658 | }; | |
| 659 | } | |
| 660 | /// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`. | |
| 661 | fn readEhPointer(r: *Reader, enc: EH.PE, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !u64 { | |
| 662 | const offset = try readEhPointerAbs(r, enc.type, addr_size_bytes, endian); | |
| 663 | if (enc.indirect) return bad(); // GCC extension; not supported | |
| 664 | const base: u64 = switch (enc.rel) { | |
| 665 | .abs, .aligned => 0, | |
| 666 | .pcrel => ctx.pc_rel_base, | |
| 667 | .textrel => ctx.text_rel_base orelse return bad(), | |
| 668 | .datarel => ctx.data_rel_base orelse return bad(), | |
| 669 | .funcrel => ctx.function_rel_base orelse return bad(), | |
| 670 | _ => return bad(), | |
| 671 | }; | |
| 672 | return switch (offset) { | |
| 673 | .signed => |s| if (s >= 0) | |
| 674 | try std.math.add(u64, base, @intCast(s)) | |
| 675 | else | |
| 676 | try std.math.sub(u64, base, @intCast(-s)), | |
| 677 | // absptr can actually contain signed values in some cases (aarch64 MachO) | |
| 678 | .unsigned => |u| u +% base, | |
| 679 | }; | |
| 680 | } | |
| 681 | ||
| 682 | /// Like `Reader.fixed`, but when the length of the data is unknown and we just want to allow | |
| 683 | /// reading indefinitely. | |
| 684 | fn maxSlice(ptr: [*]const u8) []const u8 { | |
| 685 | const len = std.math.maxInt(usize) - @intFromPtr(ptr); | |
| 686 | return ptr[0..len]; | |
| 687 | } | |
| 688 | ||
| 689 | const Allocator = std.mem.Allocator; | |
| 690 | const assert = std.debug.assert; | |
| 691 | const bad = Dwarf.bad; | |
| 692 | const cast = std.math.cast; | |
| 693 | const DW = std.dwarf; | |
| 694 | const Dwarf = std.debug.Dwarf; | |
| 695 | const EH = DW.EH; | |
| 696 | const Endian = std.builtin.Endian; | |
| 697 | const Format = DW.Format; | |
| 698 | const maxInt = std.math.maxInt; | |
| 699 | const missing = Dwarf.missing; | |
| 700 | const Reader = std.Io.Reader; | |
| 701 | const std = @import("std"); | |
| 702 | const Unwind = @This(); |
lib/std/debug/Dwarf/Unwind/VirtualMachine.zig created+459| ... | ... | @@ -0,0 +1,459 @@ |
| 1 | //! Virtual machine that evaluates DWARF call frame instructions | |
| 2 | ||
| 3 | /// See section 6.4.1 of the DWARF5 specification for details on each | |
| 4 | pub const RegisterRule = union(enum) { | |
| 5 | /// The spec says that the default rule for each column is the undefined rule. | |
| 6 | /// However, it also allows ABI / compiler authors to specify alternate defaults, so | |
| 7 | /// there is a distinction made here. | |
| 8 | default, | |
| 9 | undefined, | |
| 10 | same_value, | |
| 11 | /// offset(N) | |
| 12 | offset: i64, | |
| 13 | /// val_offset(N) | |
| 14 | val_offset: i64, | |
| 15 | /// register(R) | |
| 16 | register: u8, | |
| 17 | /// expression(E) | |
| 18 | expression: []const u8, | |
| 19 | /// val_expression(E) | |
| 20 | val_expression: []const u8, | |
| 21 | }; | |
| 22 | ||
| 23 | pub const CfaRule = union(enum) { | |
| 24 | none, | |
| 25 | reg_off: struct { | |
| 26 | register: u8, | |
| 27 | offset: i64, | |
| 28 | }, | |
| 29 | expression: []const u8, | |
| 30 | }; | |
| 31 | ||
| 32 | /// Each row contains unwinding rules for a set of registers. | |
| 33 | pub const Row = struct { | |
| 34 | /// Offset from `FrameDescriptionEntry.pc_begin` | |
| 35 | offset: u64 = 0, | |
| 36 | cfa: CfaRule = .none, | |
| 37 | /// The register fields in these columns define the register the rule applies to. | |
| 38 | columns: ColumnRange = .{ .start = undefined, .len = 0 }, | |
| 39 | }; | |
| 40 | ||
| 41 | pub const Column = struct { | |
| 42 | register: u8, | |
| 43 | rule: RegisterRule, | |
| 44 | }; | |
| 45 | ||
| 46 | const ColumnRange = struct { | |
| 47 | start: usize, | |
| 48 | len: u8, | |
| 49 | }; | |
| 50 | ||
| 51 | columns: std.ArrayList(Column) = .empty, | |
| 52 | stack: std.ArrayList(struct { | |
| 53 | cfa: CfaRule, | |
| 54 | columns: ColumnRange, | |
| 55 | }) = .empty, | |
| 56 | current_row: Row = .{}, | |
| 57 | ||
| 58 | /// The result of executing the CIE's initial_instructions | |
| 59 | cie_row: ?Row = null, | |
| 60 | ||
| 61 | pub fn deinit(self: *VirtualMachine, gpa: Allocator) void { | |
| 62 | self.stack.deinit(gpa); | |
| 63 | self.columns.deinit(gpa); | |
| 64 | self.* = undefined; | |
| 65 | } | |
| 66 | ||
| 67 | pub fn reset(self: *VirtualMachine) void { | |
| 68 | self.stack.clearRetainingCapacity(); | |
| 69 | self.columns.clearRetainingCapacity(); | |
| 70 | self.current_row = .{}; | |
| 71 | self.cie_row = null; | |
| 72 | } | |
| 73 | ||
| 74 | /// Return a slice backed by the row's non-CFA columns | |
| 75 | pub fn rowColumns(self: *const VirtualMachine, row: *const Row) []Column { | |
| 76 | if (row.columns.len == 0) return &.{}; | |
| 77 | return self.columns.items[row.columns.start..][0..row.columns.len]; | |
| 78 | } | |
| 79 | ||
| 80 | /// Either retrieves or adds a column for `register` (non-CFA) in the current row. | |
| 81 | fn getOrAddColumn(self: *VirtualMachine, gpa: Allocator, register: u8) !*Column { | |
| 82 | for (self.rowColumns(&self.current_row)) |*c| { | |
| 83 | if (c.register == register) return c; | |
| 84 | } | |
| 85 | ||
| 86 | if (self.current_row.columns.len == 0) { | |
| 87 | self.current_row.columns.start = self.columns.items.len; | |
| 88 | } else { | |
| 89 | assert(self.current_row.columns.start + self.current_row.columns.len == self.columns.items.len); | |
| 90 | } | |
| 91 | self.current_row.columns.len += 1; | |
| 92 | ||
| 93 | const column = try self.columns.addOne(gpa); | |
| 94 | column.* = .{ | |
| 95 | .register = register, | |
| 96 | .rule = .default, | |
| 97 | }; | |
| 98 | ||
| 99 | return column; | |
| 100 | } | |
| 101 | ||
| 102 | pub fn populateCieLastRow( | |
| 103 | gpa: Allocator, | |
| 104 | cie: *Unwind.CommonInformationEntry, | |
| 105 | addr_size_bytes: u8, | |
| 106 | endian: std.builtin.Endian, | |
| 107 | ) !void { | |
| 108 | assert(cie.last_row == null); | |
| 109 | ||
| 110 | var vm: VirtualMachine = .{}; | |
| 111 | defer vm.deinit(gpa); | |
| 112 | ||
| 113 | try vm.evalInstructions( | |
| 114 | gpa, | |
| 115 | cie, | |
| 116 | std.math.maxInt(u64), | |
| 117 | cie.initial_instructions, | |
| 118 | addr_size_bytes, | |
| 119 | endian, | |
| 120 | ); | |
| 121 | ||
| 122 | cie.last_row = .{ | |
| 123 | .offset = vm.current_row.offset, | |
| 124 | .cfa = vm.current_row.cfa, | |
| 125 | .cols = try gpa.dupe(Column, vm.rowColumns(&vm.current_row)), | |
| 126 | }; | |
| 127 | } | |
| 128 | ||
| 129 | /// Runs the CIE instructions, then the FDE instructions. Execution halts | |
| 130 | /// once the row that corresponds to `pc` is known, and the row is returned. | |
| 131 | pub fn runTo( | |
| 132 | vm: *VirtualMachine, | |
| 133 | gpa: Allocator, | |
| 134 | pc: u64, | |
| 135 | cie: *const Unwind.CommonInformationEntry, | |
| 136 | fde: *const Unwind.FrameDescriptionEntry, | |
| 137 | addr_size_bytes: u8, | |
| 138 | endian: std.builtin.Endian, | |
| 139 | ) !Row { | |
| 140 | assert(vm.cie_row == null); | |
| 141 | ||
| 142 | const target_offset = pc - fde.pc_begin; | |
| 143 | assert(target_offset < fde.pc_range); | |
| 144 | ||
| 145 | const instruction_bytes: []const u8 = insts: { | |
| 146 | if (target_offset < cie.last_row.?.offset) { | |
| 147 | break :insts cie.initial_instructions; | |
| 148 | } | |
| 149 | // This is the more common case: start from the CIE's last row. | |
| 150 | assert(vm.columns.items.len == 0); | |
| 151 | vm.current_row = .{ | |
| 152 | .offset = cie.last_row.?.offset, | |
| 153 | .cfa = cie.last_row.?.cfa, | |
| 154 | .columns = .{ | |
| 155 | .start = 0, | |
| 156 | .len = @intCast(cie.last_row.?.cols.len), | |
| 157 | }, | |
| 158 | }; | |
| 159 | try vm.columns.appendSlice(gpa, cie.last_row.?.cols); | |
| 160 | vm.cie_row = vm.current_row; | |
| 161 | break :insts fde.instructions; | |
| 162 | }; | |
| 163 | ||
| 164 | try vm.evalInstructions( | |
| 165 | gpa, | |
| 166 | cie, | |
| 167 | target_offset, | |
| 168 | instruction_bytes, | |
| 169 | addr_size_bytes, | |
| 170 | endian, | |
| 171 | ); | |
| 172 | return vm.current_row; | |
| 173 | } | |
| 174 | ||
| 175 | /// Evaluates instructions from `instruction_bytes` until `target_addr` is reached or all | |
| 176 | /// instructions have been evaluated. | |
| 177 | fn evalInstructions( | |
| 178 | vm: *VirtualMachine, | |
| 179 | gpa: Allocator, | |
| 180 | cie: *const Unwind.CommonInformationEntry, | |
| 181 | target_addr: u64, | |
| 182 | instruction_bytes: []const u8, | |
| 183 | addr_size_bytes: u8, | |
| 184 | endian: std.builtin.Endian, | |
| 185 | ) !void { | |
| 186 | var fr: std.Io.Reader = .fixed(instruction_bytes); | |
| 187 | while (fr.seek < fr.buffer.len) { | |
| 188 | switch (try Instruction.read(&fr, addr_size_bytes, endian)) { | |
| 189 | .nop => { | |
| 190 | // If there was one nop, there's a good chance we've reached the padding and so | |
| 191 | // everything left is a nop, which is represented by a 0 byte. | |
| 192 | if (std.mem.allEqual(u8, fr.buffered(), 0)) return; | |
| 193 | }, | |
| 194 | ||
| 195 | .remember_state => { | |
| 196 | try vm.stack.append(gpa, .{ | |
| 197 | .cfa = vm.current_row.cfa, | |
| 198 | .columns = vm.current_row.columns, | |
| 199 | }); | |
| 200 | const cols_len = vm.current_row.columns.len; | |
| 201 | const copy_start = vm.columns.items.len; | |
| 202 | assert(vm.current_row.columns.start == copy_start - cols_len); | |
| 203 | try vm.columns.ensureUnusedCapacity(gpa, cols_len); // to prevent aliasing issues | |
| 204 | vm.columns.appendSliceAssumeCapacity(vm.columns.items[copy_start - cols_len ..]); | |
| 205 | vm.current_row.columns.start = copy_start; | |
| 206 | }, | |
| 207 | .restore_state => { | |
| 208 | const restored = vm.stack.pop() orelse return error.InvalidOperation; | |
| 209 | vm.columns.shrinkRetainingCapacity(restored.columns.start + restored.columns.len); | |
| 210 | ||
| 211 | vm.current_row.cfa = restored.cfa; | |
| 212 | vm.current_row.columns = restored.columns; | |
| 213 | }, | |
| 214 | ||
| 215 | .advance_loc => |delta| { | |
| 216 | const new_addr = vm.current_row.offset + delta * cie.code_alignment_factor; | |
| 217 | if (new_addr > target_addr) return; | |
| 218 | vm.current_row.offset = new_addr; | |
| 219 | }, | |
| 220 | .set_loc => |new_addr| { | |
| 221 | if (new_addr <= vm.current_row.offset) return error.InvalidOperation; | |
| 222 | if (cie.segment_selector_size != 0) return error.InvalidOperation; // unsupported | |
| 223 | // TODO: Check cie.segment_selector_size != 0 for DWARFV4 | |
| 224 | ||
| 225 | if (new_addr > target_addr) return; | |
| 226 | vm.current_row.offset = new_addr; | |
| 227 | }, | |
| 228 | ||
| 229 | .register => |reg| { | |
| 230 | const column = try vm.getOrAddColumn(gpa, reg.index); | |
| 231 | column.rule = switch (reg.rule) { | |
| 232 | .restore => rule: { | |
| 233 | const cie_row = &(vm.cie_row orelse return error.InvalidOperation); | |
| 234 | for (vm.rowColumns(cie_row)) |cie_col| { | |
| 235 | if (cie_col.register == reg.index) break :rule cie_col.rule; | |
| 236 | } | |
| 237 | break :rule .default; | |
| 238 | }, | |
| 239 | .undefined => .undefined, | |
| 240 | .same_value => .same_value, | |
| 241 | .offset_uf => |off| .{ .offset = @as(i64, @intCast(off)) * cie.data_alignment_factor }, | |
| 242 | .offset_sf => |off| .{ .offset = off * cie.data_alignment_factor }, | |
| 243 | .val_offset_uf => |off| .{ .val_offset = @as(i64, @intCast(off)) * cie.data_alignment_factor }, | |
| 244 | .val_offset_sf => |off| .{ .val_offset = off * cie.data_alignment_factor }, | |
| 245 | .register => |callee_reg| .{ .register = callee_reg }, | |
| 246 | .expr => |len| .{ .expression = try takeExprBlock(&fr, len) }, | |
| 247 | .val_expr => |len| .{ .val_expression = try takeExprBlock(&fr, len) }, | |
| 248 | }; | |
| 249 | }, | |
| 250 | .def_cfa => |cfa| vm.current_row.cfa = .{ .reg_off = .{ | |
| 251 | .register = cfa.register, | |
| 252 | .offset = @intCast(cfa.offset), | |
| 253 | } }, | |
| 254 | .def_cfa_sf => |cfa| vm.current_row.cfa = .{ .reg_off = .{ | |
| 255 | .register = cfa.register, | |
| 256 | .offset = cfa.offset_sf * cie.data_alignment_factor, | |
| 257 | } }, | |
| 258 | .def_cfa_reg => |register| switch (vm.current_row.cfa) { | |
| 259 | .none, .expression => return error.InvalidOperation, | |
| 260 | .reg_off => |*ro| ro.register = register, | |
| 261 | }, | |
| 262 | .def_cfa_offset => |offset| switch (vm.current_row.cfa) { | |
| 263 | .none, .expression => return error.InvalidOperation, | |
| 264 | .reg_off => |*ro| ro.offset = @intCast(offset), | |
| 265 | }, | |
| 266 | .def_cfa_offset_sf => |offset_sf| switch (vm.current_row.cfa) { | |
| 267 | .none, .expression => return error.InvalidOperation, | |
| 268 | .reg_off => |*ro| ro.offset = offset_sf * cie.data_alignment_factor, | |
| 269 | }, | |
| 270 | .def_cfa_expr => |len| { | |
| 271 | vm.current_row.cfa = .{ .expression = try takeExprBlock(&fr, len) }; | |
| 272 | }, | |
| 273 | } | |
| 274 | } | |
| 275 | } | |
| 276 | ||
| 277 | fn takeExprBlock(r: *std.Io.Reader, len: usize) error{ ReadFailed, InvalidOperand }![]const u8 { | |
| 278 | return r.take(len) catch |err| switch (err) { | |
| 279 | error.ReadFailed => |e| return e, | |
| 280 | error.EndOfStream => return error.InvalidOperand, | |
| 281 | }; | |
| 282 | } | |
| 283 | ||
| 284 | const OpcodeByte = packed struct(u8) { | |
| 285 | low: packed union { | |
| 286 | operand: u6, | |
| 287 | extended: enum(u6) { | |
| 288 | nop = 0, | |
| 289 | set_loc = 1, | |
| 290 | advance_loc1 = 2, | |
| 291 | advance_loc2 = 3, | |
| 292 | advance_loc4 = 4, | |
| 293 | offset_extended = 5, | |
| 294 | restore_extended = 6, | |
| 295 | undefined = 7, | |
| 296 | same_value = 8, | |
| 297 | register = 9, | |
| 298 | remember_state = 10, | |
| 299 | restore_state = 11, | |
| 300 | def_cfa = 12, | |
| 301 | def_cfa_register = 13, | |
| 302 | def_cfa_offset = 14, | |
| 303 | def_cfa_expression = 15, | |
| 304 | expression = 16, | |
| 305 | offset_extended_sf = 17, | |
| 306 | def_cfa_sf = 18, | |
| 307 | def_cfa_offset_sf = 19, | |
| 308 | val_offset = 20, | |
| 309 | val_offset_sf = 21, | |
| 310 | val_expression = 22, | |
| 311 | _, | |
| 312 | }, | |
| 313 | }, | |
| 314 | opcode: enum(u2) { | |
| 315 | extended = 0, | |
| 316 | advance_loc = 1, | |
| 317 | offset = 2, | |
| 318 | restore = 3, | |
| 319 | }, | |
| 320 | }; | |
| 321 | ||
| 322 | pub const Instruction = union(enum) { | |
| 323 | nop, | |
| 324 | remember_state, | |
| 325 | restore_state, | |
| 326 | advance_loc: u32, | |
| 327 | set_loc: u64, | |
| 328 | ||
| 329 | register: struct { | |
| 330 | index: u8, | |
| 331 | rule: union(enum) { | |
| 332 | restore, // restore from cie | |
| 333 | undefined, | |
| 334 | same_value, | |
| 335 | offset_uf: u64, | |
| 336 | offset_sf: i64, | |
| 337 | val_offset_uf: u64, | |
| 338 | val_offset_sf: i64, | |
| 339 | register: u8, | |
| 340 | /// Value is the number of bytes in the DWARF expression, which the caller must read. | |
| 341 | expr: usize, | |
| 342 | /// Value is the number of bytes in the DWARF expression, which the caller must read. | |
| 343 | val_expr: usize, | |
| 344 | }, | |
| 345 | }, | |
| 346 | ||
| 347 | def_cfa: struct { | |
| 348 | register: u8, | |
| 349 | offset: u64, | |
| 350 | }, | |
| 351 | def_cfa_sf: struct { | |
| 352 | register: u8, | |
| 353 | offset_sf: i64, | |
| 354 | }, | |
| 355 | def_cfa_reg: u8, | |
| 356 | def_cfa_offset: u64, | |
| 357 | def_cfa_offset_sf: i64, | |
| 358 | /// Value is the number of bytes in the DWARF expression, which the caller must read. | |
| 359 | def_cfa_expr: usize, | |
| 360 | ||
| 361 | pub fn read( | |
| 362 | reader: *std.Io.Reader, | |
| 363 | addr_size_bytes: u8, | |
| 364 | endian: std.builtin.Endian, | |
| 365 | ) !Instruction { | |
| 366 | const inst: OpcodeByte = @bitCast(try reader.takeByte()); | |
| 367 | return switch (inst.opcode) { | |
| 368 | .advance_loc => .{ .advance_loc = inst.low.operand }, | |
| 369 | .offset => .{ .register = .{ | |
| 370 | .index = inst.low.operand, | |
| 371 | .rule = .{ .offset_uf = try reader.takeLeb128(u64) }, | |
| 372 | } }, | |
| 373 | .restore => .{ .register = .{ | |
| 374 | .index = inst.low.operand, | |
| 375 | .rule = .restore, | |
| 376 | } }, | |
| 377 | .extended => switch (inst.low.extended) { | |
| 378 | .nop => .nop, | |
| 379 | .remember_state => .remember_state, | |
| 380 | .restore_state => .restore_state, | |
| 381 | .advance_loc1 => .{ .advance_loc = try reader.takeByte() }, | |
| 382 | .advance_loc2 => .{ .advance_loc = try reader.takeInt(u16, endian) }, | |
| 383 | .advance_loc4 => .{ .advance_loc = try reader.takeInt(u32, endian) }, | |
| 384 | .set_loc => .{ .set_loc = switch (addr_size_bytes) { | |
| 385 | 2 => try reader.takeInt(u16, endian), | |
| 386 | 4 => try reader.takeInt(u32, endian), | |
| 387 | 8 => try reader.takeInt(u64, endian), | |
| 388 | else => return error.UnsupportedAddrSize, | |
| 389 | } }, | |
| 390 | ||
| 391 | .offset_extended => .{ .register = .{ | |
| 392 | .index = try reader.takeLeb128(u8), | |
| 393 | .rule = .{ .offset_uf = try reader.takeLeb128(u64) }, | |
| 394 | } }, | |
| 395 | .offset_extended_sf => .{ .register = .{ | |
| 396 | .index = try reader.takeLeb128(u8), | |
| 397 | .rule = .{ .offset_sf = try reader.takeLeb128(i64) }, | |
| 398 | } }, | |
| 399 | .restore_extended => .{ .register = .{ | |
| 400 | .index = try reader.takeLeb128(u8), | |
| 401 | .rule = .restore, | |
| 402 | } }, | |
| 403 | .undefined => .{ .register = .{ | |
| 404 | .index = try reader.takeLeb128(u8), | |
| 405 | .rule = .undefined, | |
| 406 | } }, | |
| 407 | .same_value => .{ .register = .{ | |
| 408 | .index = try reader.takeLeb128(u8), | |
| 409 | .rule = .same_value, | |
| 410 | } }, | |
| 411 | .register => .{ .register = .{ | |
| 412 | .index = try reader.takeLeb128(u8), | |
| 413 | .rule = .{ .register = try reader.takeLeb128(u8) }, | |
| 414 | } }, | |
| 415 | .val_offset => .{ .register = .{ | |
| 416 | .index = try reader.takeLeb128(u8), | |
| 417 | .rule = .{ .val_offset_uf = try reader.takeLeb128(u64) }, | |
| 418 | } }, | |
| 419 | .val_offset_sf => .{ .register = .{ | |
| 420 | .index = try reader.takeLeb128(u8), | |
| 421 | .rule = .{ .val_offset_sf = try reader.takeLeb128(i64) }, | |
| 422 | } }, | |
| 423 | .expression => .{ .register = .{ | |
| 424 | .index = try reader.takeLeb128(u8), | |
| 425 | .rule = .{ .expr = try reader.takeLeb128(usize) }, | |
| 426 | } }, | |
| 427 | .val_expression => .{ .register = .{ | |
| 428 | .index = try reader.takeLeb128(u8), | |
| 429 | .rule = .{ .val_expr = try reader.takeLeb128(usize) }, | |
| 430 | } }, | |
| 431 | ||
| 432 | .def_cfa => .{ .def_cfa = .{ | |
| 433 | .register = try reader.takeLeb128(u8), | |
| 434 | .offset = try reader.takeLeb128(u64), | |
| 435 | } }, | |
| 436 | .def_cfa_sf => .{ .def_cfa_sf = .{ | |
| 437 | .register = try reader.takeLeb128(u8), | |
| 438 | .offset_sf = try reader.takeLeb128(i64), | |
| 439 | } }, | |
| 440 | .def_cfa_register => .{ .def_cfa_reg = try reader.takeLeb128(u8) }, | |
| 441 | .def_cfa_offset => .{ .def_cfa_offset = try reader.takeLeb128(u64) }, | |
| 442 | .def_cfa_offset_sf => .{ .def_cfa_offset_sf = try reader.takeLeb128(i64) }, | |
| 443 | .def_cfa_expression => .{ .def_cfa_expr = try reader.takeLeb128(usize) }, | |
| 444 | ||
| 445 | _ => switch (@intFromEnum(inst.low.extended)) { | |
| 446 | 0x1C...0x3F => return error.UnimplementedUserOpcode, | |
| 447 | else => return error.InvalidOpcode, | |
| 448 | }, | |
| 449 | }, | |
| 450 | }; | |
| 451 | } | |
| 452 | }; | |
| 453 | ||
| 454 | const std = @import("../../../std.zig"); | |
| 455 | const assert = std.debug.assert; | |
| 456 | const Allocator = std.mem.Allocator; | |
| 457 | const Unwind = std.debug.Dwarf.Unwind; | |
| 458 | ||
| 459 | const VirtualMachine = @This(); |
lib/std/debug/Dwarf/abi.zig deleted-351| ... | ... | @@ -1,351 +0,0 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | ||
| 3 | const std = @import("../../std.zig"); | |
| 4 | const mem = std.mem; | |
| 5 | const posix = std.posix; | |
| 6 | const Arch = std.Target.Cpu.Arch; | |
| 7 | ||
| 8 | /// Tells whether unwinding for this target is supported by the Dwarf standard. | |
| 9 | /// | |
| 10 | /// See also `std.debug.SelfInfo.supportsUnwinding` which tells whether the Zig | |
| 11 | /// standard library has a working implementation of unwinding for this target. | |
| 12 | pub fn supportsUnwinding(target: *const std.Target) bool { | |
| 13 | return switch (target.cpu.arch) { | |
| 14 | .amdgcn, | |
| 15 | .nvptx, | |
| 16 | .nvptx64, | |
| 17 | .spirv32, | |
| 18 | .spirv64, | |
| 19 | => false, | |
| 20 | ||
| 21 | // Enabling this causes relocation errors such as: | |
| 22 | // error: invalid relocation type R_RISCV_SUB32 at offset 0x20 | |
| 23 | .riscv64, .riscv64be, .riscv32, .riscv32be => false, | |
| 24 | ||
| 25 | // Conservative guess. Feel free to update this logic with any targets | |
| 26 | // that are known to not support Dwarf unwinding. | |
| 27 | else => true, | |
| 28 | }; | |
| 29 | } | |
| 30 | ||
| 31 | /// Returns `null` for CPU architectures without an instruction pointer register. | |
| 32 | pub fn ipRegNum(arch: Arch) ?u8 { | |
| 33 | return switch (arch) { | |
| 34 | .x86 => 8, | |
| 35 | .x86_64 => 16, | |
| 36 | .arm, .armeb, .thumb, .thumbeb => 15, | |
| 37 | .aarch64, .aarch64_be => 32, | |
| 38 | else => null, | |
| 39 | }; | |
| 40 | } | |
| 41 | ||
| 42 | pub fn fpRegNum(arch: Arch, reg_context: RegisterContext) u8 { | |
| 43 | return switch (arch) { | |
| 44 | // GCC on OS X historically did the opposite of ELF for these registers | |
| 45 | // (only in .eh_frame), and that is now the convention for MachO | |
| 46 | .x86 => if (reg_context.eh_frame and reg_context.is_macho) 4 else 5, | |
| 47 | .x86_64 => 6, | |
| 48 | .arm, .armeb, .thumb, .thumbeb => 11, | |
| 49 | .aarch64, .aarch64_be => 29, | |
| 50 | else => unreachable, | |
| 51 | }; | |
| 52 | } | |
| 53 | ||
| 54 | pub fn spRegNum(arch: Arch, reg_context: RegisterContext) u8 { | |
| 55 | return switch (arch) { | |
| 56 | .x86 => if (reg_context.eh_frame and reg_context.is_macho) 5 else 4, | |
| 57 | .x86_64 => 7, | |
| 58 | .arm, .armeb, .thumb, .thumbeb => 13, | |
| 59 | .aarch64, .aarch64_be => 31, | |
| 60 | else => unreachable, | |
| 61 | }; | |
| 62 | } | |
| 63 | ||
| 64 | pub const RegisterContext = struct { | |
| 65 | eh_frame: bool, | |
| 66 | is_macho: bool, | |
| 67 | }; | |
| 68 | ||
| 69 | pub const RegBytesError = error{ | |
| 70 | InvalidRegister, | |
| 71 | UnimplementedArch, | |
| 72 | UnimplementedOs, | |
| 73 | RegisterContextRequired, | |
| 74 | ThreadContextNotSupported, | |
| 75 | }; | |
| 76 | ||
| 77 | /// Returns a slice containing the backing storage for `reg_number`. | |
| 78 | /// | |
| 79 | /// This function assumes the Dwarf information corresponds not necessarily to | |
| 80 | /// the current executable, but at least with a matching CPU architecture and | |
| 81 | /// OS. It is planned to lift this limitation with a future enhancement. | |
| 82 | /// | |
| 83 | /// `reg_context` describes in what context the register number is used, as it can have different | |
| 84 | /// meanings depending on the DWARF container. It is only required when getting the stack or | |
| 85 | /// frame pointer register on some architectures. | |
| 86 | pub fn regBytes( | |
| 87 | thread_context_ptr: *std.debug.ThreadContext, | |
| 88 | reg_number: u8, | |
| 89 | reg_context: ?RegisterContext, | |
| 90 | ) RegBytesError![]u8 { | |
| 91 | if (builtin.os.tag == .windows) { | |
| 92 | return switch (builtin.cpu.arch) { | |
| 93 | .x86 => switch (reg_number) { | |
| 94 | 0 => mem.asBytes(&thread_context_ptr.Eax), | |
| 95 | 1 => mem.asBytes(&thread_context_ptr.Ecx), | |
| 96 | 2 => mem.asBytes(&thread_context_ptr.Edx), | |
| 97 | 3 => mem.asBytes(&thread_context_ptr.Ebx), | |
| 98 | 4 => mem.asBytes(&thread_context_ptr.Esp), | |
| 99 | 5 => mem.asBytes(&thread_context_ptr.Ebp), | |
| 100 | 6 => mem.asBytes(&thread_context_ptr.Esi), | |
| 101 | 7 => mem.asBytes(&thread_context_ptr.Edi), | |
| 102 | 8 => mem.asBytes(&thread_context_ptr.Eip), | |
| 103 | 9 => mem.asBytes(&thread_context_ptr.EFlags), | |
| 104 | 10 => mem.asBytes(&thread_context_ptr.SegCs), | |
| 105 | 11 => mem.asBytes(&thread_context_ptr.SegSs), | |
| 106 | 12 => mem.asBytes(&thread_context_ptr.SegDs), | |
| 107 | 13 => mem.asBytes(&thread_context_ptr.SegEs), | |
| 108 | 14 => mem.asBytes(&thread_context_ptr.SegFs), | |
| 109 | 15 => mem.asBytes(&thread_context_ptr.SegGs), | |
| 110 | else => error.InvalidRegister, | |
| 111 | }, | |
| 112 | .x86_64 => switch (reg_number) { | |
| 113 | 0 => mem.asBytes(&thread_context_ptr.Rax), | |
| 114 | 1 => mem.asBytes(&thread_context_ptr.Rdx), | |
| 115 | 2 => mem.asBytes(&thread_context_ptr.Rcx), | |
| 116 | 3 => mem.asBytes(&thread_context_ptr.Rbx), | |
| 117 | 4 => mem.asBytes(&thread_context_ptr.Rsi), | |
| 118 | 5 => mem.asBytes(&thread_context_ptr.Rdi), | |
| 119 | 6 => mem.asBytes(&thread_context_ptr.Rbp), | |
| 120 | 7 => mem.asBytes(&thread_context_ptr.Rsp), | |
| 121 | 8 => mem.asBytes(&thread_context_ptr.R8), | |
| 122 | 9 => mem.asBytes(&thread_context_ptr.R9), | |
| 123 | 10 => mem.asBytes(&thread_context_ptr.R10), | |
| 124 | 11 => mem.asBytes(&thread_context_ptr.R11), | |
| 125 | 12 => mem.asBytes(&thread_context_ptr.R12), | |
| 126 | 13 => mem.asBytes(&thread_context_ptr.R13), | |
| 127 | 14 => mem.asBytes(&thread_context_ptr.R14), | |
| 128 | 15 => mem.asBytes(&thread_context_ptr.R15), | |
| 129 | 16 => mem.asBytes(&thread_context_ptr.Rip), | |
| 130 | else => error.InvalidRegister, | |
| 131 | }, | |
| 132 | .aarch64, .aarch64_be => switch (reg_number) { | |
| 133 | 0...30 => mem.asBytes(&thread_context_ptr.DUMMYUNIONNAME.X[reg_number]), | |
| 134 | 31 => mem.asBytes(&thread_context_ptr.Sp), | |
| 135 | 32 => mem.asBytes(&thread_context_ptr.Pc), | |
| 136 | else => error.InvalidRegister, | |
| 137 | }, | |
| 138 | else => error.UnimplementedArch, | |
| 139 | }; | |
| 140 | } | |
| 141 | ||
| 142 | if (!std.debug.have_ucontext) return error.ThreadContextNotSupported; | |
| 143 | ||
| 144 | const ucontext_ptr = thread_context_ptr; | |
| 145 | return switch (builtin.cpu.arch) { | |
| 146 | .x86 => switch (builtin.os.tag) { | |
| 147 | .linux, .netbsd, .solaris, .illumos => switch (reg_number) { | |
| 148 | 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EAX]), | |
| 149 | 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ECX]), | |
| 150 | 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EDX]), | |
| 151 | 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBX]), | |
| 152 | 4...5 => if (reg_context) |r| bytes: { | |
| 153 | if (reg_number == 4) { | |
| 154 | break :bytes if (r.eh_frame and r.is_macho) | |
| 155 | mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBP]) | |
| 156 | else | |
| 157 | mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESP]); | |
| 158 | } else { | |
| 159 | break :bytes if (r.eh_frame and r.is_macho) | |
| 160 | mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESP]) | |
| 161 | else | |
| 162 | mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBP]); | |
| 163 | } | |
| 164 | } else error.RegisterContextRequired, | |
| 165 | 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESI]), | |
| 166 | 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EDI]), | |
| 167 | 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EIP]), | |
| 168 | 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EFL]), | |
| 169 | 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.CS]), | |
| 170 | 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.SS]), | |
| 171 | 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.DS]), | |
| 172 | 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ES]), | |
| 173 | 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.FS]), | |
| 174 | 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.GS]), | |
| 175 | 16...23 => error.InvalidRegister, // TODO: Support loading ST0-ST7 from mcontext.fpregs | |
| 176 | 32...39 => error.InvalidRegister, // TODO: Support loading XMM0-XMM7 from mcontext.fpregs | |
| 177 | else => error.InvalidRegister, | |
| 178 | }, | |
| 179 | else => error.UnimplementedOs, | |
| 180 | }, | |
| 181 | .x86_64 => switch (builtin.os.tag) { | |
| 182 | .linux, .solaris, .illumos => switch (reg_number) { | |
| 183 | 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RAX]), | |
| 184 | 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDX]), | |
| 185 | 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RCX]), | |
| 186 | 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RBX]), | |
| 187 | 4 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RSI]), | |
| 188 | 5 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDI]), | |
| 189 | 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RBP]), | |
| 190 | 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RSP]), | |
| 191 | 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R8]), | |
| 192 | 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R9]), | |
| 193 | 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R10]), | |
| 194 | 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R11]), | |
| 195 | 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R12]), | |
| 196 | 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R13]), | |
| 197 | 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R14]), | |
| 198 | 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R15]), | |
| 199 | 16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RIP]), | |
| 200 | 17...32 => |i| if (builtin.os.tag.isSolarish()) | |
| 201 | mem.asBytes(&ucontext_ptr.mcontext.fpregs.chip_state.xmm[i - 17]) | |
| 202 | else | |
| 203 | mem.asBytes(&ucontext_ptr.mcontext.fpregs.xmm[i - 17]), | |
| 204 | else => error.InvalidRegister, | |
| 205 | }, | |
| 206 | .freebsd => switch (reg_number) { | |
| 207 | 0 => mem.asBytes(&ucontext_ptr.mcontext.rax), | |
| 208 | 1 => mem.asBytes(&ucontext_ptr.mcontext.rdx), | |
| 209 | 2 => mem.asBytes(&ucontext_ptr.mcontext.rcx), | |
| 210 | 3 => mem.asBytes(&ucontext_ptr.mcontext.rbx), | |
| 211 | 4 => mem.asBytes(&ucontext_ptr.mcontext.rsi), | |
| 212 | 5 => mem.asBytes(&ucontext_ptr.mcontext.rdi), | |
| 213 | 6 => mem.asBytes(&ucontext_ptr.mcontext.rbp), | |
| 214 | 7 => mem.asBytes(&ucontext_ptr.mcontext.rsp), | |
| 215 | 8 => mem.asBytes(&ucontext_ptr.mcontext.r8), | |
| 216 | 9 => mem.asBytes(&ucontext_ptr.mcontext.r9), | |
| 217 | 10 => mem.asBytes(&ucontext_ptr.mcontext.r10), | |
| 218 | 11 => mem.asBytes(&ucontext_ptr.mcontext.r11), | |
| 219 | 12 => mem.asBytes(&ucontext_ptr.mcontext.r12), | |
| 220 | 13 => mem.asBytes(&ucontext_ptr.mcontext.r13), | |
| 221 | 14 => mem.asBytes(&ucontext_ptr.mcontext.r14), | |
| 222 | 15 => mem.asBytes(&ucontext_ptr.mcontext.r15), | |
| 223 | 16 => mem.asBytes(&ucontext_ptr.mcontext.rip), | |
| 224 | // TODO: Extract xmm state from mcontext.fpstate? | |
| 225 | else => error.InvalidRegister, | |
| 226 | }, | |
| 227 | .openbsd => switch (reg_number) { | |
| 228 | 0 => mem.asBytes(&ucontext_ptr.sc_rax), | |
| 229 | 1 => mem.asBytes(&ucontext_ptr.sc_rdx), | |
| 230 | 2 => mem.asBytes(&ucontext_ptr.sc_rcx), | |
| 231 | 3 => mem.asBytes(&ucontext_ptr.sc_rbx), | |
| 232 | 4 => mem.asBytes(&ucontext_ptr.sc_rsi), | |
| 233 | 5 => mem.asBytes(&ucontext_ptr.sc_rdi), | |
| 234 | 6 => mem.asBytes(&ucontext_ptr.sc_rbp), | |
| 235 | 7 => mem.asBytes(&ucontext_ptr.sc_rsp), | |
| 236 | 8 => mem.asBytes(&ucontext_ptr.sc_r8), | |
| 237 | 9 => mem.asBytes(&ucontext_ptr.sc_r9), | |
| 238 | 10 => mem.asBytes(&ucontext_ptr.sc_r10), | |
| 239 | 11 => mem.asBytes(&ucontext_ptr.sc_r11), | |
| 240 | 12 => mem.asBytes(&ucontext_ptr.sc_r12), | |
| 241 | 13 => mem.asBytes(&ucontext_ptr.sc_r13), | |
| 242 | 14 => mem.asBytes(&ucontext_ptr.sc_r14), | |
| 243 | 15 => mem.asBytes(&ucontext_ptr.sc_r15), | |
| 244 | 16 => mem.asBytes(&ucontext_ptr.sc_rip), | |
| 245 | // TODO: Extract xmm state from sc_fpstate? | |
| 246 | else => error.InvalidRegister, | |
| 247 | }, | |
| 248 | .macos, .ios => switch (reg_number) { | |
| 249 | 0 => mem.asBytes(&ucontext_ptr.mcontext.ss.rax), | |
| 250 | 1 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdx), | |
| 251 | 2 => mem.asBytes(&ucontext_ptr.mcontext.ss.rcx), | |
| 252 | 3 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbx), | |
| 253 | 4 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsi), | |
| 254 | 5 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdi), | |
| 255 | 6 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbp), | |
| 256 | 7 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsp), | |
| 257 | 8 => mem.asBytes(&ucontext_ptr.mcontext.ss.r8), | |
| 258 | 9 => mem.asBytes(&ucontext_ptr.mcontext.ss.r9), | |
| 259 | 10 => mem.asBytes(&ucontext_ptr.mcontext.ss.r10), | |
| 260 | 11 => mem.asBytes(&ucontext_ptr.mcontext.ss.r11), | |
| 261 | 12 => mem.asBytes(&ucontext_ptr.mcontext.ss.r12), | |
| 262 | 13 => mem.asBytes(&ucontext_ptr.mcontext.ss.r13), | |
| 263 | 14 => mem.asBytes(&ucontext_ptr.mcontext.ss.r14), | |
| 264 | 15 => mem.asBytes(&ucontext_ptr.mcontext.ss.r15), | |
| 265 | 16 => mem.asBytes(&ucontext_ptr.mcontext.ss.rip), | |
| 266 | else => error.InvalidRegister, | |
| 267 | }, | |
| 268 | else => error.UnimplementedOs, | |
| 269 | }, | |
| 270 | .arm, .armeb, .thumb, .thumbeb => switch (builtin.os.tag) { | |
| 271 | .linux => switch (reg_number) { | |
| 272 | 0 => mem.asBytes(&ucontext_ptr.mcontext.arm_r0), | |
| 273 | 1 => mem.asBytes(&ucontext_ptr.mcontext.arm_r1), | |
| 274 | 2 => mem.asBytes(&ucontext_ptr.mcontext.arm_r2), | |
| 275 | 3 => mem.asBytes(&ucontext_ptr.mcontext.arm_r3), | |
| 276 | 4 => mem.asBytes(&ucontext_ptr.mcontext.arm_r4), | |
| 277 | 5 => mem.asBytes(&ucontext_ptr.mcontext.arm_r5), | |
| 278 | 6 => mem.asBytes(&ucontext_ptr.mcontext.arm_r6), | |
| 279 | 7 => mem.asBytes(&ucontext_ptr.mcontext.arm_r7), | |
| 280 | 8 => mem.asBytes(&ucontext_ptr.mcontext.arm_r8), | |
| 281 | 9 => mem.asBytes(&ucontext_ptr.mcontext.arm_r9), | |
| 282 | 10 => mem.asBytes(&ucontext_ptr.mcontext.arm_r10), | |
| 283 | 11 => mem.asBytes(&ucontext_ptr.mcontext.arm_fp), | |
| 284 | 12 => mem.asBytes(&ucontext_ptr.mcontext.arm_ip), | |
| 285 | 13 => mem.asBytes(&ucontext_ptr.mcontext.arm_sp), | |
| 286 | 14 => mem.asBytes(&ucontext_ptr.mcontext.arm_lr), | |
| 287 | 15 => mem.asBytes(&ucontext_ptr.mcontext.arm_pc), | |
| 288 | // CPSR is not allocated a register number (See: https://github.com/ARM-software/abi-aa/blob/main/aadwarf32/aadwarf32.rst, Section 4.1) | |
| 289 | else => error.InvalidRegister, | |
| 290 | }, | |
| 291 | else => error.UnimplementedOs, | |
| 292 | }, | |
| 293 | .aarch64, .aarch64_be => switch (builtin.os.tag) { | |
| 294 | .macos, .ios, .watchos => switch (reg_number) { | |
| 295 | 0...28 => mem.asBytes(&ucontext_ptr.mcontext.ss.regs[reg_number]), | |
| 296 | 29 => mem.asBytes(&ucontext_ptr.mcontext.ss.fp), | |
| 297 | 30 => mem.asBytes(&ucontext_ptr.mcontext.ss.lr), | |
| 298 | 31 => mem.asBytes(&ucontext_ptr.mcontext.ss.sp), | |
| 299 | 32 => mem.asBytes(&ucontext_ptr.mcontext.ss.pc), | |
| 300 | ||
| 301 | // TODO: Find storage for this state | |
| 302 | //34 => mem.asBytes(&ucontext_ptr.ra_sign_state), | |
| 303 | ||
| 304 | // V0-V31 | |
| 305 | 64...95 => mem.asBytes(&ucontext_ptr.mcontext.ns.q[reg_number - 64]), | |
| 306 | else => error.InvalidRegister, | |
| 307 | }, | |
| 308 | .netbsd => switch (reg_number) { | |
| 309 | 0...34 => mem.asBytes(&ucontext_ptr.mcontext.gregs[reg_number]), | |
| 310 | else => error.InvalidRegister, | |
| 311 | }, | |
| 312 | .freebsd => switch (reg_number) { | |
| 313 | 0...29 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.x[reg_number]), | |
| 314 | 30 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.lr), | |
| 315 | 31 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.sp), | |
| 316 | ||
| 317 | // TODO: This seems wrong, but it was in the previous debug.zig code for mapping PC, check this | |
| 318 | 32 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.elr), | |
| 319 | ||
| 320 | else => error.InvalidRegister, | |
| 321 | }, | |
| 322 | .openbsd => switch (reg_number) { | |
| 323 | 0...30 => mem.asBytes(&ucontext_ptr.sc_x[reg_number]), | |
| 324 | 31 => mem.asBytes(&ucontext_ptr.sc_sp), | |
| 325 | 32 => mem.asBytes(&ucontext_ptr.sc_lr), | |
| 326 | 33 => mem.asBytes(&ucontext_ptr.sc_elr), | |
| 327 | 34 => mem.asBytes(&ucontext_ptr.sc_spsr), | |
| 328 | else => error.InvalidRegister, | |
| 329 | }, | |
| 330 | else => switch (reg_number) { | |
| 331 | 0...30 => mem.asBytes(&ucontext_ptr.mcontext.regs[reg_number]), | |
| 332 | 31 => mem.asBytes(&ucontext_ptr.mcontext.sp), | |
| 333 | 32 => mem.asBytes(&ucontext_ptr.mcontext.pc), | |
| 334 | else => error.InvalidRegister, | |
| 335 | }, | |
| 336 | }, | |
| 337 | else => error.UnimplementedArch, | |
| 338 | }; | |
| 339 | } | |
| 340 | ||
| 341 | /// Returns a pointer to a register stored in a ThreadContext, preserving the | |
| 342 | /// pointer attributes of the context. | |
| 343 | pub fn regValueNative( | |
| 344 | thread_context_ptr: *std.debug.ThreadContext, | |
| 345 | reg_number: u8, | |
| 346 | reg_context: ?RegisterContext, | |
| 347 | ) !*align(1) usize { | |
| 348 | const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context); | |
| 349 | if (@sizeOf(usize) != reg_bytes.len) return error.IncompatibleRegisterSize; | |
| 350 | return mem.bytesAsValue(usize, reg_bytes[0..@sizeOf(usize)]); | |
| 351 | } |
lib/std/debug/Dwarf/call_frame.zig deleted-292| ... | ... | @@ -1,292 +0,0 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const std = @import("../../std.zig"); | |
| 3 | const mem = std.mem; | |
| 4 | const debug = std.debug; | |
| 5 | const leb = std.leb; | |
| 6 | const DW = std.dwarf; | |
| 7 | const abi = std.debug.Dwarf.abi; | |
| 8 | const assert = std.debug.assert; | |
| 9 | const native_endian = builtin.cpu.arch.endian(); | |
| 10 | ||
| 11 | /// TODO merge with std.dwarf.CFA | |
| 12 | const Opcode = enum(u8) { | |
| 13 | advance_loc = 0x1 << 6, | |
| 14 | offset = 0x2 << 6, | |
| 15 | restore = 0x3 << 6, | |
| 16 | ||
| 17 | nop = 0x00, | |
| 18 | set_loc = 0x01, | |
| 19 | advance_loc1 = 0x02, | |
| 20 | advance_loc2 = 0x03, | |
| 21 | advance_loc4 = 0x04, | |
| 22 | offset_extended = 0x05, | |
| 23 | restore_extended = 0x06, | |
| 24 | undefined = 0x07, | |
| 25 | same_value = 0x08, | |
| 26 | register = 0x09, | |
| 27 | remember_state = 0x0a, | |
| 28 | restore_state = 0x0b, | |
| 29 | def_cfa = 0x0c, | |
| 30 | def_cfa_register = 0x0d, | |
| 31 | def_cfa_offset = 0x0e, | |
| 32 | def_cfa_expression = 0x0f, | |
| 33 | expression = 0x10, | |
| 34 | offset_extended_sf = 0x11, | |
| 35 | def_cfa_sf = 0x12, | |
| 36 | def_cfa_offset_sf = 0x13, | |
| 37 | val_offset = 0x14, | |
| 38 | val_offset_sf = 0x15, | |
| 39 | val_expression = 0x16, | |
| 40 | ||
| 41 | // These opcodes encode an operand in the lower 6 bits of the opcode itself | |
| 42 | pub const lo_inline = @intFromEnum(Opcode.advance_loc); | |
| 43 | pub const hi_inline = @intFromEnum(Opcode.restore) | 0b111111; | |
| 44 | ||
| 45 | // These opcodes are trailed by zero or more operands | |
| 46 | pub const lo_reserved = @intFromEnum(Opcode.nop); | |
| 47 | pub const hi_reserved = @intFromEnum(Opcode.val_expression); | |
| 48 | ||
| 49 | // Vendor-specific opcodes | |
| 50 | pub const lo_user = 0x1c; | |
| 51 | pub const hi_user = 0x3f; | |
| 52 | }; | |
| 53 | ||
| 54 | fn readBlock(reader: *std.Io.Reader) ![]const u8 { | |
| 55 | const block_len = try reader.takeLeb128(usize); | |
| 56 | return reader.take(block_len); | |
| 57 | } | |
| 58 | ||
| 59 | pub const Instruction = union(Opcode) { | |
| 60 | advance_loc: struct { | |
| 61 | delta: u8, | |
| 62 | }, | |
| 63 | offset: struct { | |
| 64 | register: u8, | |
| 65 | offset: u64, | |
| 66 | }, | |
| 67 | restore: struct { | |
| 68 | register: u8, | |
| 69 | }, | |
| 70 | nop: void, | |
| 71 | set_loc: struct { | |
| 72 | address: u64, | |
| 73 | }, | |
| 74 | advance_loc1: struct { | |
| 75 | delta: u8, | |
| 76 | }, | |
| 77 | advance_loc2: struct { | |
| 78 | delta: u16, | |
| 79 | }, | |
| 80 | advance_loc4: struct { | |
| 81 | delta: u32, | |
| 82 | }, | |
| 83 | offset_extended: struct { | |
| 84 | register: u8, | |
| 85 | offset: u64, | |
| 86 | }, | |
| 87 | restore_extended: struct { | |
| 88 | register: u8, | |
| 89 | }, | |
| 90 | undefined: struct { | |
| 91 | register: u8, | |
| 92 | }, | |
| 93 | same_value: struct { | |
| 94 | register: u8, | |
| 95 | }, | |
| 96 | register: struct { | |
| 97 | register: u8, | |
| 98 | target_register: u8, | |
| 99 | }, | |
| 100 | remember_state: void, | |
| 101 | restore_state: void, | |
| 102 | def_cfa: struct { | |
| 103 | register: u8, | |
| 104 | offset: u64, | |
| 105 | }, | |
| 106 | def_cfa_register: struct { | |
| 107 | register: u8, | |
| 108 | }, | |
| 109 | def_cfa_offset: struct { | |
| 110 | offset: u64, | |
| 111 | }, | |
| 112 | def_cfa_expression: struct { | |
| 113 | block: []const u8, | |
| 114 | }, | |
| 115 | expression: struct { | |
| 116 | register: u8, | |
| 117 | block: []const u8, | |
| 118 | }, | |
| 119 | offset_extended_sf: struct { | |
| 120 | register: u8, | |
| 121 | offset: i64, | |
| 122 | }, | |
| 123 | def_cfa_sf: struct { | |
| 124 | register: u8, | |
| 125 | offset: i64, | |
| 126 | }, | |
| 127 | def_cfa_offset_sf: struct { | |
| 128 | offset: i64, | |
| 129 | }, | |
| 130 | val_offset: struct { | |
| 131 | register: u8, | |
| 132 | offset: u64, | |
| 133 | }, | |
| 134 | val_offset_sf: struct { | |
| 135 | register: u8, | |
| 136 | offset: i64, | |
| 137 | }, | |
| 138 | val_expression: struct { | |
| 139 | register: u8, | |
| 140 | block: []const u8, | |
| 141 | }, | |
| 142 | ||
| 143 | pub fn read( | |
| 144 | reader: *std.Io.Reader, | |
| 145 | addr_size_bytes: u8, | |
| 146 | endian: std.builtin.Endian, | |
| 147 | ) !Instruction { | |
| 148 | switch (try reader.takeByte()) { | |
| 149 | Opcode.lo_inline...Opcode.hi_inline => |opcode| { | |
| 150 | const e: Opcode = @enumFromInt(opcode & 0b11000000); | |
| 151 | const value: u6 = @intCast(opcode & 0b111111); | |
| 152 | return switch (e) { | |
| 153 | .advance_loc => .{ | |
| 154 | .advance_loc = .{ .delta = value }, | |
| 155 | }, | |
| 156 | .offset => .{ | |
| 157 | .offset = .{ | |
| 158 | .register = value, | |
| 159 | .offset = try reader.takeLeb128(u64), | |
| 160 | }, | |
| 161 | }, | |
| 162 | .restore => .{ | |
| 163 | .restore = .{ .register = value }, | |
| 164 | }, | |
| 165 | else => unreachable, | |
| 166 | }; | |
| 167 | }, | |
| 168 | Opcode.lo_reserved...Opcode.hi_reserved => |opcode| { | |
| 169 | const e: Opcode = @enumFromInt(opcode); | |
| 170 | return switch (e) { | |
| 171 | .advance_loc, | |
| 172 | .offset, | |
| 173 | .restore, | |
| 174 | => unreachable, | |
| 175 | .nop => .{ .nop = {} }, | |
| 176 | .set_loc => .{ | |
| 177 | .set_loc = .{ | |
| 178 | .address = switch (addr_size_bytes) { | |
| 179 | 2 => try reader.takeInt(u16, endian), | |
| 180 | 4 => try reader.takeInt(u32, endian), | |
| 181 | 8 => try reader.takeInt(u64, endian), | |
| 182 | else => return error.InvalidAddrSize, | |
| 183 | }, | |
| 184 | }, | |
| 185 | }, | |
| 186 | .advance_loc1 => .{ | |
| 187 | .advance_loc1 = .{ .delta = try reader.takeByte() }, | |
| 188 | }, | |
| 189 | .advance_loc2 => .{ | |
| 190 | .advance_loc2 = .{ .delta = try reader.takeInt(u16, endian) }, | |
| 191 | }, | |
| 192 | .advance_loc4 => .{ | |
| 193 | .advance_loc4 = .{ .delta = try reader.takeInt(u32, endian) }, | |
| 194 | }, | |
| 195 | .offset_extended => .{ | |
| 196 | .offset_extended = .{ | |
| 197 | .register = try reader.takeLeb128(u8), | |
| 198 | .offset = try reader.takeLeb128(u64), | |
| 199 | }, | |
| 200 | }, | |
| 201 | .restore_extended => .{ | |
| 202 | .restore_extended = .{ | |
| 203 | .register = try reader.takeLeb128(u8), | |
| 204 | }, | |
| 205 | }, | |
| 206 | .undefined => .{ | |
| 207 | .undefined = .{ | |
| 208 | .register = try reader.takeLeb128(u8), | |
| 209 | }, | |
| 210 | }, | |
| 211 | .same_value => .{ | |
| 212 | .same_value = .{ | |
| 213 | .register = try reader.takeLeb128(u8), | |
| 214 | }, | |
| 215 | }, | |
| 216 | .register => .{ | |
| 217 | .register = .{ | |
| 218 | .register = try reader.takeLeb128(u8), | |
| 219 | .target_register = try reader.takeLeb128(u8), | |
| 220 | }, | |
| 221 | }, | |
| 222 | .remember_state => .{ .remember_state = {} }, | |
| 223 | .restore_state => .{ .restore_state = {} }, | |
| 224 | .def_cfa => .{ | |
| 225 | .def_cfa = .{ | |
| 226 | .register = try reader.takeLeb128(u8), | |
| 227 | .offset = try reader.takeLeb128(u64), | |
| 228 | }, | |
| 229 | }, | |
| 230 | .def_cfa_register => .{ | |
| 231 | .def_cfa_register = .{ | |
| 232 | .register = try reader.takeLeb128(u8), | |
| 233 | }, | |
| 234 | }, | |
| 235 | .def_cfa_offset => .{ | |
| 236 | .def_cfa_offset = .{ | |
| 237 | .offset = try reader.takeLeb128(u64), | |
| 238 | }, | |
| 239 | }, | |
| 240 | .def_cfa_expression => .{ | |
| 241 | .def_cfa_expression = .{ | |
| 242 | .block = try readBlock(reader), | |
| 243 | }, | |
| 244 | }, | |
| 245 | .expression => .{ | |
| 246 | .expression = .{ | |
| 247 | .register = try reader.takeLeb128(u8), | |
| 248 | .block = try readBlock(reader), | |
| 249 | }, | |
| 250 | }, | |
| 251 | .offset_extended_sf => .{ | |
| 252 | .offset_extended_sf = .{ | |
| 253 | .register = try reader.takeLeb128(u8), | |
| 254 | .offset = try reader.takeLeb128(i64), | |
| 255 | }, | |
| 256 | }, | |
| 257 | .def_cfa_sf => .{ | |
| 258 | .def_cfa_sf = .{ | |
| 259 | .register = try reader.takeLeb128(u8), | |
| 260 | .offset = try reader.takeLeb128(i64), | |
| 261 | }, | |
| 262 | }, | |
| 263 | .def_cfa_offset_sf => .{ | |
| 264 | .def_cfa_offset_sf = .{ | |
| 265 | .offset = try reader.takeLeb128(i64), | |
| 266 | }, | |
| 267 | }, | |
| 268 | .val_offset => .{ | |
| 269 | .val_offset = .{ | |
| 270 | .register = try reader.takeLeb128(u8), | |
| 271 | .offset = try reader.takeLeb128(u64), | |
| 272 | }, | |
| 273 | }, | |
| 274 | .val_offset_sf => .{ | |
| 275 | .val_offset_sf = .{ | |
| 276 | .register = try reader.takeLeb128(u8), | |
| 277 | .offset = try reader.takeLeb128(i64), | |
| 278 | }, | |
| 279 | }, | |
| 280 | .val_expression => .{ | |
| 281 | .val_expression = .{ | |
| 282 | .register = try reader.takeLeb128(u8), | |
| 283 | .block = try readBlock(reader), | |
| 284 | }, | |
| 285 | }, | |
| 286 | }; | |
| 287 | }, | |
| 288 | Opcode.lo_user...Opcode.hi_user => return error.UnimplementedUserOpcode, | |
| 289 | else => return error.InvalidOpcode, | |
| 290 | } | |
| 291 | } | |
| 292 | }; |
lib/std/debug/Dwarf/expression.zig+62-99| ... | ... | @@ -5,12 +5,17 @@ const native_endian = native_arch.endian(); |
| 5 | 5 | const std = @import("std"); |
| 6 | 6 | const leb = std.leb; |
| 7 | 7 | const OP = std.dwarf.OP; |
| 8 | const abi = std.debug.Dwarf.abi; | |
| 9 | 8 | const mem = std.mem; |
| 10 | 9 | const assert = std.debug.assert; |
| 11 | 10 | const testing = std.testing; |
| 12 | 11 | const Writer = std.Io.Writer; |
| 13 | 12 | |
| 13 | const regNative = std.debug.Dwarf.SelfUnwinder.regNative; | |
| 14 | ||
| 15 | const ip_reg_num = std.debug.Dwarf.ipRegNum(native_arch).?; | |
| 16 | const fp_reg_num = std.debug.Dwarf.fpRegNum(native_arch); | |
| 17 | const sp_reg_num = std.debug.Dwarf.spRegNum(native_arch); | |
| 18 | ||
| 14 | 19 | /// Expressions can be evaluated in different contexts, each requiring its own set of inputs. |
| 15 | 20 | /// Callers should specify all the fields relevant to their context. If a field is required |
| 16 | 21 | /// by the expression and it isn't in the context, error.IncompleteExpressionContext is returned. |
| ... | ... | @@ -23,9 +28,7 @@ pub const Context = struct { |
| 23 | 28 | object_address: ?*const anyopaque = null, |
| 24 | 29 | /// .debug_addr section |
| 25 | 30 | debug_addr: ?[]const u8 = null, |
| 26 | /// Thread context | |
| 27 | thread_context: ?*std.debug.ThreadContext = null, | |
| 28 | reg_context: ?abi.RegisterContext = null, | |
| 31 | cpu_context: ?*std.debug.cpu_context.Native = null, | |
| 29 | 32 | /// Call frame address, if in a CFI context |
| 30 | 33 | cfa: ?usize = null, |
| 31 | 34 | /// This expression is a sub-expression from an OP.entry_value instruction |
| ... | ... | @@ -62,7 +65,9 @@ pub const Error = error{ |
| 62 | 65 | InvalidTypeLength, |
| 63 | 66 | |
| 64 | 67 | TruncatedIntegralType, |
| 65 | } || abi.RegBytesError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero, ReadFailed }; | |
| 68 | ||
| 69 | IncompatibleRegisterSize, | |
| 70 | } || std.debug.cpu_context.DwarfRegisterError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero, ReadFailed }; | |
| 66 | 71 | |
| 67 | 72 | /// A stack machine that can decode and run DWARF expressions. |
| 68 | 73 | /// Expressions can be decoded for non-native address size and endianness, |
| ... | ... | @@ -369,29 +374,20 @@ pub fn StackMachine(comptime options: Options) type { |
| 369 | 374 | OP.breg0...OP.breg31, |
| 370 | 375 | OP.bregx, |
| 371 | 376 | => { |
| 372 | if (context.thread_context == null) return error.IncompleteExpressionContext; | |
| 373 | ||
| 374 | const base_register = operand.?.base_register; | |
| 375 | var value: i64 = @intCast(mem.readInt(usize, (try abi.regBytes( | |
| 376 | context.thread_context.?, | |
| 377 | base_register.base_register, | |
| 378 | context.reg_context, | |
| 379 | ))[0..@sizeOf(usize)], native_endian)); | |
| 380 | value += base_register.offset; | |
| 381 | try self.stack.append(allocator, .{ .generic = @intCast(value) }); | |
| 377 | const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext; | |
| 378 | ||
| 379 | const br = operand.?.base_register; | |
| 380 | const value: i64 = @intCast((try regNative(cpu_context, br.base_register)).*); | |
| 381 | try self.stack.append(allocator, .{ .generic = @intCast(value + br.offset) }); | |
| 382 | 382 | }, |
| 383 | 383 | OP.regval_type => { |
| 384 | const register_type = operand.?.register_type; | |
| 385 | const value = mem.readInt(usize, (try abi.regBytes( | |
| 386 | context.thread_context.?, | |
| 387 | register_type.register, | |
| 388 | context.reg_context, | |
| 389 | ))[0..@sizeOf(usize)], native_endian); | |
| 384 | const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext; | |
| 385 | const rt = operand.?.register_type; | |
| 390 | 386 | try self.stack.append(allocator, .{ |
| 391 | 387 | .regval_type = .{ |
| 392 | .type_offset = register_type.type_offset, | |
| 388 | .type_offset = rt.type_offset, | |
| 393 | 389 | .type_size = @sizeOf(addr_type), |
| 394 | .value = value, | |
| 390 | .value = (try regNative(cpu_context, rt.register)).*, | |
| 395 | 391 | }, |
| 396 | 392 | }); |
| 397 | 393 | }, |
| ... | ... | @@ -734,14 +730,14 @@ pub fn StackMachine(comptime options: Options) type { |
| 734 | 730 | |
| 735 | 731 | // TODO: The spec states that this sub-expression needs to observe the state (ie. registers) |
| 736 | 732 | // as it was upon entering the current subprogram. If this isn't being called at the |
| 737 | // end of a frame unwind operation, an additional ThreadContext with this state will be needed. | |
| 733 | // end of a frame unwind operation, an additional cpu_context.Native with this state will be needed. | |
| 738 | 734 | |
| 739 | 735 | if (isOpcodeRegisterLocation(block[0])) { |
| 740 | if (context.thread_context == null) return error.IncompleteExpressionContext; | |
| 736 | const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext; | |
| 741 | 737 | |
| 742 | 738 | var block_stream: std.Io.Reader = .fixed(block); |
| 743 | 739 | const register = (try readOperand(&block_stream, block[0], context)).?.register; |
| 744 | const value = mem.readInt(usize, (try abi.regBytes(context.thread_context.?, register, context.reg_context))[0..@sizeOf(usize)], native_endian); | |
| 740 | const value = (try regNative(cpu_context, register)).*; | |
| 745 | 741 | try self.stack.append(allocator, .{ .generic = value }); |
| 746 | 742 | } else { |
| 747 | 743 | var stack_machine: Self = .{}; |
| ... | ... | @@ -1149,55 +1145,39 @@ test "basics" { |
| 1149 | 1145 | } |
| 1150 | 1146 | |
| 1151 | 1147 | // Register values |
| 1152 | if (@sizeOf(std.debug.ThreadContext) != 0) { | |
| 1148 | if (std.debug.cpu_context.Native != noreturn) { | |
| 1153 | 1149 | stack_machine.reset(); |
| 1154 | 1150 | program.clearRetainingCapacity(); |
| 1155 | 1151 | |
| 1156 | const reg_context = abi.RegisterContext{ | |
| 1157 | .eh_frame = true, | |
| 1158 | .is_macho = builtin.os.tag == .macos, | |
| 1159 | }; | |
| 1160 | var thread_context: std.debug.ThreadContext = undefined; | |
| 1161 | std.debug.relocateContext(&thread_context); | |
| 1152 | var cpu_context: std.debug.cpu_context.Native = undefined; | |
| 1162 | 1153 | const context = Context{ |
| 1163 | .thread_context = &thread_context, | |
| 1164 | .reg_context = reg_context, | |
| 1154 | .cpu_context = &cpu_context, | |
| 1165 | 1155 | }; |
| 1166 | 1156 | |
| 1167 | // Only test register operations on arch / os that have them implemented | |
| 1168 | if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| { | |
| 1169 | ||
| 1170 | // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it | |
| 1171 | ||
| 1172 | mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); | |
| 1173 | (try abi.regValueNative(&thread_context, abi.fpRegNum(native_arch, reg_context), reg_context)).* = 1; | |
| 1174 | (try abi.regValueNative(&thread_context, abi.spRegNum(native_arch, reg_context), reg_context)).* = 2; | |
| 1175 | (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch).?, reg_context)).* = 3; | |
| 1176 | ||
| 1177 | try b.writeBreg(writer, abi.fpRegNum(native_arch, reg_context), @as(usize, 100)); | |
| 1178 | try b.writeBreg(writer, abi.spRegNum(native_arch, reg_context), @as(usize, 200)); | |
| 1179 | try b.writeBregx(writer, abi.ipRegNum(native_arch).?, @as(usize, 300)); | |
| 1180 | try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400)); | |
| 1181 | ||
| 1182 | _ = try stack_machine.run(program.written(), allocator, context, 0); | |
| 1183 | ||
| 1184 | const regval_type = stack_machine.stack.pop().?.regval_type; | |
| 1185 | try testing.expectEqual(@as(usize, 400), regval_type.type_offset); | |
| 1186 | try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size); | |
| 1187 | try testing.expectEqual(@as(usize, 0xee), regval_type.value); | |
| 1188 | ||
| 1189 | try testing.expectEqual(@as(usize, 303), stack_machine.stack.pop().?.generic); | |
| 1190 | try testing.expectEqual(@as(usize, 202), stack_machine.stack.pop().?.generic); | |
| 1191 | try testing.expectEqual(@as(usize, 101), stack_machine.stack.pop().?.generic); | |
| 1192 | } else |err| { | |
| 1193 | switch (err) { | |
| 1194 | error.UnimplementedArch, | |
| 1195 | error.UnimplementedOs, | |
| 1196 | error.ThreadContextNotSupported, | |
| 1197 | => {}, | |
| 1198 | else => return err, | |
| 1199 | } | |
| 1200 | } | |
| 1157 | const reg_bytes = try cpu_context.dwarfRegisterBytes(0); | |
| 1158 | ||
| 1159 | // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it | |
| 1160 | ||
| 1161 | mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); | |
| 1162 | (try regNative(&cpu_context, fp_reg_num)).* = 1; | |
| 1163 | (try regNative(&cpu_context, sp_reg_num)).* = 2; | |
| 1164 | (try regNative(&cpu_context, ip_reg_num)).* = 3; | |
| 1165 | ||
| 1166 | try b.writeBreg(writer, fp_reg_num, @as(usize, 100)); | |
| 1167 | try b.writeBreg(writer, sp_reg_num, @as(usize, 200)); | |
| 1168 | try b.writeBregx(writer, ip_reg_num, @as(usize, 300)); | |
| 1169 | try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400)); | |
| 1170 | ||
| 1171 | _ = try stack_machine.run(program.written(), allocator, context, 0); | |
| 1172 | ||
| 1173 | const regval_type = stack_machine.stack.pop().?.regval_type; | |
| 1174 | try testing.expectEqual(@as(usize, 400), regval_type.type_offset); | |
| 1175 | try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size); | |
| 1176 | try testing.expectEqual(@as(usize, 0xee), regval_type.value); | |
| 1177 | ||
| 1178 | try testing.expectEqual(@as(usize, 303), stack_machine.stack.pop().?.generic); | |
| 1179 | try testing.expectEqual(@as(usize, 202), stack_machine.stack.pop().?.generic); | |
| 1180 | try testing.expectEqual(@as(usize, 101), stack_machine.stack.pop().?.generic); | |
| 1201 | 1181 | } |
| 1202 | 1182 | |
| 1203 | 1183 | // Stack operations |
| ... | ... | @@ -1585,38 +1565,21 @@ test "basics" { |
| 1585 | 1565 | } |
| 1586 | 1566 | |
| 1587 | 1567 | // Register location description |
| 1588 | const reg_context = abi.RegisterContext{ | |
| 1589 | .eh_frame = true, | |
| 1590 | .is_macho = builtin.os.tag == .macos, | |
| 1591 | }; | |
| 1592 | var thread_context: std.debug.ThreadContext = undefined; | |
| 1593 | std.debug.relocateContext(&thread_context); | |
| 1594 | context = Context{ | |
| 1595 | .thread_context = &thread_context, | |
| 1596 | .reg_context = reg_context, | |
| 1597 | }; | |
| 1568 | var cpu_context: std.debug.cpu_context.Native = undefined; | |
| 1569 | context = .{ .cpu_context = &cpu_context }; | |
| 1598 | 1570 | |
| 1599 | if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| { | |
| 1600 | mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); | |
| 1571 | const reg_bytes = try cpu_context.dwarfRegisterBytes(0); | |
| 1572 | mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); | |
| 1601 | 1573 | |
| 1602 | var sub_program: std.Io.Writer.Allocating = .init(allocator); | |
| 1603 | defer sub_program.deinit(); | |
| 1604 | const sub_writer = &sub_program.writer; | |
| 1605 | try b.writeReg(sub_writer, 0); | |
| 1574 | var sub_program: std.Io.Writer.Allocating = .init(allocator); | |
| 1575 | defer sub_program.deinit(); | |
| 1576 | const sub_writer = &sub_program.writer; | |
| 1577 | try b.writeReg(sub_writer, 0); | |
| 1606 | 1578 | |
| 1607 | stack_machine.reset(); | |
| 1608 | program.clearRetainingCapacity(); | |
| 1609 | try b.writeEntryValue(writer, sub_program.written()); | |
| 1610 | _ = try stack_machine.run(program.written(), allocator, context, null); | |
| 1611 | try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.pop().?.generic); | |
| 1612 | } else |err| { | |
| 1613 | switch (err) { | |
| 1614 | error.UnimplementedArch, | |
| 1615 | error.UnimplementedOs, | |
| 1616 | error.ThreadContextNotSupported, | |
| 1617 | => {}, | |
| 1618 | else => return err, | |
| 1619 | } | |
| 1620 | } | |
| 1579 | stack_machine.reset(); | |
| 1580 | program.clearRetainingCapacity(); | |
| 1581 | try b.writeEntryValue(writer, sub_program.written()); | |
| 1582 | _ = try stack_machine.run(program.written(), allocator, context, null); | |
| 1583 | try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.pop().?.generic); | |
| 1621 | 1584 | } |
| 1622 | 1585 | } |
lib/std/debug/ElfFile.zig created+536| ... | ... | @@ -0,0 +1,536 @@ |
| 1 | //! A helper type for loading an ELF file and collecting its DWARF debug information, unwind | |
| 2 | //! information, and symbol table. | |
| 3 | ||
| 4 | is_64: bool, | |
| 5 | endian: Endian, | |
| 6 | ||
| 7 | /// This is `null` iff any of the required DWARF sections were missing. `ElfFile.load` does *not* | |
| 8 | /// call `Dwarf.open`, `Dwarf.scanAllFunctions`, etc; that is the caller's responsibility. | |
| 9 | dwarf: ?Dwarf, | |
| 10 | ||
| 11 | /// If non-`null`, describes the `.eh_frame` section, which can be used with `Dwarf.Unwind`. | |
| 12 | eh_frame: ?UnwindSection, | |
| 13 | /// If non-`null`, describes the `.debug_frame` section, which can be used with `Dwarf.Unwind`. | |
| 14 | debug_frame: ?UnwindSection, | |
| 15 | ||
| 16 | /// If non-`null`, this is the contents of the `.strtab` section. | |
| 17 | strtab: ?[]const u8, | |
| 18 | /// If non-`null`, describes the `.symtab` section. | |
| 19 | symtab: ?SymtabSection, | |
| 20 | ||
| 21 | /// Binary search table lazily populated by `searchSymtab`. | |
| 22 | symbol_search_table: ?[]usize, | |
| 23 | ||
| 24 | /// The memory-mapped ELF file, which is referenced by `dwarf`. This field is here only so that | |
| 25 | /// this memory can be unmapped by `ElfFile.deinit`. | |
| 26 | mapped_file: []align(std.heap.page_size_min) const u8, | |
| 27 | /// Sometimes, debug info is stored separately to the main ELF file. In that case, `mapped_file` | |
| 28 | /// is the mapped ELF binary, and `mapped_debug_file` is the mapped debug info file. Both must | |
| 29 | /// be unmapped by `ElfFile.deinit`. | |
| 30 | mapped_debug_file: ?[]align(std.heap.page_size_min) const u8, | |
| 31 | ||
| 32 | arena: std.heap.ArenaAllocator.State, | |
| 33 | ||
| 34 | pub const UnwindSection = struct { | |
| 35 | vaddr: u64, | |
| 36 | bytes: []const u8, | |
| 37 | }; | |
| 38 | pub const SymtabSection = struct { | |
| 39 | entry_size: u64, | |
| 40 | bytes: []const u8, | |
| 41 | }; | |
| 42 | ||
| 43 | pub const DebugInfoSearchPaths = struct { | |
| 44 | /// The location of a debuginfod client directory, which acts as a search path for build IDs. If | |
| 45 | /// given, we can load from this directory opportunistically, but make no effort to populate it. | |
| 46 | /// To avoid allocation when building the search paths, this is given as two components which | |
| 47 | /// will be concatenated. | |
| 48 | debuginfod_client: ?[2][]const u8, | |
| 49 | /// All "global debug directories" on the system. These are used as search paths for both debug | |
| 50 | /// links and build IDs. On typical systems this is just "/usr/lib/debug". | |
| 51 | global_debug: []const []const u8, | |
| 52 | /// The path to the dirname of the ELF file, which acts as a search path for debug links. | |
| 53 | exe_dir: ?[]const u8, | |
| 54 | ||
| 55 | pub const none: DebugInfoSearchPaths = .{ | |
| 56 | .debuginfod_client = null, | |
| 57 | .global_debug = &.{}, | |
| 58 | .exe_dir = null, | |
| 59 | }; | |
| 60 | ||
| 61 | pub fn native(exe_path: []const u8) DebugInfoSearchPaths { | |
| 62 | return .{ | |
| 63 | .debuginfod_client = p: { | |
| 64 | if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |p| { | |
| 65 | break :p .{ p, "" }; | |
| 66 | } | |
| 67 | if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| { | |
| 68 | break :p .{ cache_path, "/debuginfod_client" }; | |
| 69 | } | |
| 70 | if (std.posix.getenv("HOME")) |home_path| { | |
| 71 | break :p .{ home_path, "/.cache/debuginfod_client" }; | |
| 72 | } | |
| 73 | break :p null; | |
| 74 | }, | |
| 75 | .global_debug = &.{ | |
| 76 | "/usr/lib/debug", | |
| 77 | }, | |
| 78 | .exe_dir = std.fs.path.dirname(exe_path) orelse ".", | |
| 79 | }; | |
| 80 | } | |
| 81 | }; | |
| 82 | ||
| 83 | pub fn deinit(ef: *ElfFile, gpa: Allocator) void { | |
| 84 | if (ef.dwarf) |*dwarf| dwarf.deinit(gpa); | |
| 85 | if (ef.symbol_search_table) |t| gpa.free(t); | |
| 86 | var arena = ef.arena.promote(gpa); | |
| 87 | arena.deinit(); | |
| 88 | ||
| 89 | std.posix.munmap(ef.mapped_file); | |
| 90 | if (ef.mapped_debug_file) |m| std.posix.munmap(m); | |
| 91 | ||
| 92 | ef.* = undefined; | |
| 93 | } | |
| 94 | ||
| 95 | pub const LoadError = error{ | |
| 96 | OutOfMemory, | |
| 97 | Overflow, | |
| 98 | TruncatedElfFile, | |
| 99 | InvalidCompressedSection, | |
| 100 | InvalidElfMagic, | |
| 101 | InvalidElfVersion, | |
| 102 | InvalidElfClass, | |
| 103 | InvalidElfEndian, | |
| 104 | // The remaining errors all occur when attemping to stat or mmap a file. | |
| 105 | SystemResources, | |
| 106 | MemoryMappingNotSupported, | |
| 107 | AccessDenied, | |
| 108 | LockedMemoryLimitExceeded, | |
| 109 | ProcessFdQuotaExceeded, | |
| 110 | SystemFdQuotaExceeded, | |
| 111 | Unexpected, | |
| 112 | }; | |
| 113 | ||
| 114 | pub fn load( | |
| 115 | gpa: Allocator, | |
| 116 | elf_file: std.fs.File, | |
| 117 | opt_build_id: ?[]const u8, | |
| 118 | di_search_paths: *const DebugInfoSearchPaths, | |
| 119 | ) LoadError!ElfFile { | |
| 120 | var arena_instance: std.heap.ArenaAllocator = .init(gpa); | |
| 121 | errdefer arena_instance.deinit(); | |
| 122 | const arena = arena_instance.allocator(); | |
| 123 | ||
| 124 | var result = loadInner(arena, elf_file, null) catch |err| switch (err) { | |
| 125 | error.CrcMismatch => unreachable, // we passed crc as null | |
| 126 | else => |e| return e, | |
| 127 | }; | |
| 128 | errdefer std.posix.munmap(result.mapped_mem); | |
| 129 | ||
| 130 | // `loadInner` did most of the work, but we might need to load an external debug info file | |
| 131 | ||
| 132 | const di_mapped_mem: ?[]align(std.heap.page_size_min) const u8 = load_di: { | |
| 133 | if (result.sections.get(.debug_info) != null and | |
| 134 | result.sections.get(.debug_abbrev) != null and | |
| 135 | result.sections.get(.debug_str) != null and | |
| 136 | result.sections.get(.debug_line) != null) | |
| 137 | { | |
| 138 | // The info is already loaded from this file alone! | |
| 139 | break :load_di null; | |
| 140 | } | |
| 141 | ||
| 142 | // We're missing some debug info---let's try and load it from a separate file. | |
| 143 | ||
| 144 | build_id: { | |
| 145 | const build_id = opt_build_id orelse break :build_id; | |
| 146 | if (build_id.len < 3) break :build_id; | |
| 147 | ||
| 148 | for (di_search_paths.global_debug) |global_debug| { | |
| 149 | if (try loadSeparateDebugFile(arena, &result, null, "{s}/.build-id/{x}/{x}.debug", .{ | |
| 150 | global_debug, | |
| 151 | build_id[0..1], | |
| 152 | build_id[1..], | |
| 153 | })) |mapped| break :load_di mapped; | |
| 154 | } | |
| 155 | ||
| 156 | if (di_search_paths.debuginfod_client) |components| { | |
| 157 | if (try loadSeparateDebugFile(arena, &result, null, "{s}{s}/{x}/debuginfo", .{ | |
| 158 | components[0], | |
| 159 | components[1], | |
| 160 | build_id, | |
| 161 | })) |mapped| break :load_di mapped; | |
| 162 | } | |
| 163 | } | |
| 164 | ||
| 165 | debug_link: { | |
| 166 | const section = result.sections.get(.gnu_debuglink) orelse break :debug_link; | |
| 167 | const debug_filename = std.mem.sliceTo(section.bytes, 0); | |
| 168 | const crc_offset = std.mem.alignForward(usize, debug_filename.len + 1, 4); | |
| 169 | if (section.bytes.len < crc_offset + 4) break :debug_link; | |
| 170 | const debug_crc = std.mem.readInt(u32, section.bytes[crc_offset..][0..4], result.endian); | |
| 171 | ||
| 172 | const exe_dir = di_search_paths.exe_dir orelse break :debug_link; | |
| 173 | ||
| 174 | if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/{s}", .{ | |
| 175 | exe_dir, | |
| 176 | debug_filename, | |
| 177 | })) |mapped| break :load_di mapped; | |
| 178 | if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/.debug/{s}", .{ | |
| 179 | exe_dir, | |
| 180 | debug_filename, | |
| 181 | })) |mapped| break :load_di mapped; | |
| 182 | for (di_search_paths.global_debug) |global_debug| { | |
| 183 | // This looks like a bug; it isn't. They really do embed the absolute path to the | |
| 184 | // exe's dirname, *under* the global debug path. | |
| 185 | if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/{s}/{s}", .{ | |
| 186 | global_debug, | |
| 187 | exe_dir, | |
| 188 | debug_filename, | |
| 189 | })) |mapped| break :load_di mapped; | |
| 190 | } | |
| 191 | } | |
| 192 | ||
| 193 | break :load_di null; | |
| 194 | }; | |
| 195 | errdefer comptime unreachable; | |
| 196 | ||
| 197 | return .{ | |
| 198 | .is_64 = result.is_64, | |
| 199 | .endian = result.endian, | |
| 200 | .dwarf = dwarf: { | |
| 201 | if (result.sections.get(.debug_info) == null or | |
| 202 | result.sections.get(.debug_abbrev) == null or | |
| 203 | result.sections.get(.debug_str) == null or | |
| 204 | result.sections.get(.debug_line) == null) | |
| 205 | { | |
| 206 | break :dwarf null; // debug info not present | |
| 207 | } | |
| 208 | var sections: Dwarf.SectionArray = @splat(null); | |
| 209 | inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields) |f| { | |
| 210 | if (result.sections.get(@field(Section.Id, f.name))) |s| { | |
| 211 | sections[f.value] = .{ .data = s.bytes, .owned = false }; | |
| 212 | } | |
| 213 | } | |
| 214 | break :dwarf .{ .sections = sections }; | |
| 215 | }, | |
| 216 | .eh_frame = if (result.sections.get(.eh_frame)) |s| .{ | |
| 217 | .vaddr = s.header.sh_addr, | |
| 218 | .bytes = s.bytes, | |
| 219 | } else null, | |
| 220 | .debug_frame = if (result.sections.get(.debug_frame)) |s| .{ | |
| 221 | .vaddr = s.header.sh_addr, | |
| 222 | .bytes = s.bytes, | |
| 223 | } else null, | |
| 224 | .strtab = if (result.sections.get(.strtab)) |s| s.bytes else null, | |
| 225 | .symtab = if (result.sections.get(.symtab)) |s| .{ | |
| 226 | .entry_size = s.header.sh_entsize, | |
| 227 | .bytes = s.bytes, | |
| 228 | } else null, | |
| 229 | .symbol_search_table = null, | |
| 230 | .mapped_file = result.mapped_mem, | |
| 231 | .mapped_debug_file = di_mapped_mem, | |
| 232 | .arena = arena_instance.state, | |
| 233 | }; | |
| 234 | } | |
| 235 | ||
| 236 | pub fn searchSymtab(ef: *ElfFile, gpa: Allocator, vaddr: u64) error{ | |
| 237 | NoSymtab, | |
| 238 | NoStrtab, | |
| 239 | BadSymtab, | |
| 240 | OutOfMemory, | |
| 241 | }!std.debug.Symbol { | |
| 242 | const symtab = ef.symtab orelse return error.NoSymtab; | |
| 243 | const strtab = ef.strtab orelse return error.NoStrtab; | |
| 244 | ||
| 245 | if (symtab.bytes.len % symtab.entry_size != 0) return error.BadSymtab; | |
| 246 | ||
| 247 | const swap_endian = ef.endian != @import("builtin").cpu.arch.endian(); | |
| 248 | ||
| 249 | switch (ef.is_64) { | |
| 250 | inline true, false => |is_64| { | |
| 251 | const Sym = if (is_64) elf.Elf64_Sym else elf.Elf32_Sym; | |
| 252 | if (symtab.entry_size != @sizeOf(Sym)) return error.BadSymtab; | |
| 253 | const symbols: []align(1) const Sym = @ptrCast(symtab.bytes); | |
| 254 | if (ef.symbol_search_table == null) { | |
| 255 | ef.symbol_search_table = try buildSymbolSearchTable(gpa, ef.endian, Sym, symbols); | |
| 256 | } | |
| 257 | const search_table = ef.symbol_search_table.?; | |
| 258 | const SearchContext = struct { | |
| 259 | swap_endian: bool, | |
| 260 | target: u64, | |
| 261 | symbols: []align(1) const Sym, | |
| 262 | fn predicate(ctx: @This(), sym_index: usize) bool { | |
| 263 | // We need to return `true` for the first N items, then `false` for the rest -- | |
| 264 | // the index we'll get out is the first `false` one. So, we'll return `true` iff | |
| 265 | // the target address is after the *end* of this symbol. This synchronizes with | |
| 266 | // the logic in `buildSymbolSearchTable` which sorts by *end* address. | |
| 267 | var sym = ctx.symbols[sym_index]; | |
| 268 | if (ctx.swap_endian) std.mem.byteSwapAllFields(Sym, &sym); | |
| 269 | const sym_end = sym.st_value + sym.st_size; | |
| 270 | return ctx.target >= sym_end; | |
| 271 | } | |
| 272 | }; | |
| 273 | const sym_index_index = std.sort.partitionPoint(usize, search_table, @as(SearchContext, .{ | |
| 274 | .swap_endian = swap_endian, | |
| 275 | .target = vaddr, | |
| 276 | .symbols = symbols, | |
| 277 | }), SearchContext.predicate); | |
| 278 | if (sym_index_index == search_table.len) return .unknown; | |
| 279 | var sym = symbols[search_table[sym_index_index]]; | |
| 280 | if (swap_endian) std.mem.byteSwapAllFields(Sym, &sym); | |
| 281 | if (vaddr < sym.st_value or vaddr >= sym.st_value + sym.st_size) return .unknown; | |
| 282 | return .{ | |
| 283 | .name = std.mem.sliceTo(strtab[sym.st_name..], 0), | |
| 284 | .compile_unit_name = null, | |
| 285 | .source_location = null, | |
| 286 | }; | |
| 287 | }, | |
| 288 | } | |
| 289 | } | |
| 290 | ||
| 291 | fn buildSymbolSearchTable(gpa: Allocator, endian: Endian, comptime Sym: type, symbols: []align(1) const Sym) error{ | |
| 292 | OutOfMemory, | |
| 293 | BadSymtab, | |
| 294 | }![]usize { | |
| 295 | var result: std.ArrayList(usize) = .empty; | |
| 296 | defer result.deinit(gpa); | |
| 297 | ||
| 298 | const swap_endian = endian != @import("builtin").cpu.arch.endian(); | |
| 299 | ||
| 300 | for (symbols, 0..) |sym_orig, sym_index| { | |
| 301 | var sym = sym_orig; | |
| 302 | if (swap_endian) std.mem.byteSwapAllFields(Sym, &sym); | |
| 303 | if (sym.st_name == 0) continue; | |
| 304 | if (sym.st_shndx == elf.SHN_UNDEF) continue; | |
| 305 | try result.append(gpa, sym_index); | |
| 306 | } | |
| 307 | ||
| 308 | const SortContext = struct { | |
| 309 | swap_endian: bool, | |
| 310 | symbols: []align(1) const Sym, | |
| 311 | fn lessThan(ctx: @This(), lhs_sym_index: usize, rhs_sym_index: usize) bool { | |
| 312 | // We sort by *end* address, not start address. This matches up with logic in `searchSymtab`. | |
| 313 | var lhs_sym = ctx.symbols[lhs_sym_index]; | |
| 314 | var rhs_sym = ctx.symbols[rhs_sym_index]; | |
| 315 | if (ctx.swap_endian) { | |
| 316 | std.mem.byteSwapAllFields(Sym, &lhs_sym); | |
| 317 | std.mem.byteSwapAllFields(Sym, &rhs_sym); | |
| 318 | } | |
| 319 | const lhs_val = lhs_sym.st_value + lhs_sym.st_size; | |
| 320 | const rhs_val = rhs_sym.st_value + rhs_sym.st_size; | |
| 321 | return lhs_val < rhs_val; | |
| 322 | } | |
| 323 | }; | |
| 324 | std.mem.sort(usize, result.items, @as(SortContext, .{ | |
| 325 | .swap_endian = swap_endian, | |
| 326 | .symbols = symbols, | |
| 327 | }), SortContext.lessThan); | |
| 328 | ||
| 329 | return result.toOwnedSlice(gpa); | |
| 330 | } | |
| 331 | ||
| 332 | /// Only used locally, during `load`. | |
| 333 | const Section = struct { | |
| 334 | header: elf.Elf64_Shdr, | |
| 335 | bytes: []const u8, | |
| 336 | const Id = enum { | |
| 337 | // DWARF sections: see `Dwarf.Section.Id`. | |
| 338 | debug_info, | |
| 339 | debug_abbrev, | |
| 340 | debug_str, | |
| 341 | debug_str_offsets, | |
| 342 | debug_line, | |
| 343 | debug_line_str, | |
| 344 | debug_ranges, | |
| 345 | debug_loclists, | |
| 346 | debug_rnglists, | |
| 347 | debug_addr, | |
| 348 | debug_names, | |
| 349 | // Then anything else we're interested in. | |
| 350 | gnu_debuglink, | |
| 351 | eh_frame, | |
| 352 | debug_frame, | |
| 353 | symtab, | |
| 354 | strtab, | |
| 355 | }; | |
| 356 | const Array = std.enums.EnumArray(Section.Id, ?Section); | |
| 357 | }; | |
| 358 | ||
| 359 | fn loadSeparateDebugFile(arena: Allocator, main_loaded: *LoadInnerResult, opt_crc: ?u32, comptime fmt: []const u8, args: anytype) Allocator.Error!?[]align(std.heap.page_size_min) const u8 { | |
| 360 | const path = try std.fmt.allocPrint(arena, fmt, args); | |
| 361 | const elf_file = std.fs.cwd().openFile(path, .{}) catch return null; | |
| 362 | defer elf_file.close(); | |
| 363 | ||
| 364 | const result = loadInner(arena, elf_file, opt_crc) catch |err| switch (err) { | |
| 365 | error.OutOfMemory => |e| return e, | |
| 366 | error.CrcMismatch => return null, | |
| 367 | else => return null, | |
| 368 | }; | |
| 369 | errdefer comptime unreachable; | |
| 370 | ||
| 371 | const have_debug_sections = inline for (@as([]const []const u8, &.{ | |
| 372 | "debug_info", | |
| 373 | "debug_abbrev", | |
| 374 | "debug_str", | |
| 375 | "debug_line", | |
| 376 | })) |name| { | |
| 377 | const s = @field(Section.Id, name); | |
| 378 | if (main_loaded.sections.get(s) == null and result.sections.get(s) != null) { | |
| 379 | break false; | |
| 380 | } | |
| 381 | } else true; | |
| 382 | ||
| 383 | if (result.is_64 != main_loaded.is_64 or | |
| 384 | result.endian != main_loaded.endian or | |
| 385 | !have_debug_sections) | |
| 386 | { | |
| 387 | std.posix.munmap(result.mapped_mem); | |
| 388 | return null; | |
| 389 | } | |
| 390 | ||
| 391 | inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields) |f| { | |
| 392 | const id = @field(Section.Id, f.name); | |
| 393 | if (main_loaded.sections.get(id) == null) { | |
| 394 | main_loaded.sections.set(id, result.sections.get(id)); | |
| 395 | } | |
| 396 | } | |
| 397 | ||
| 398 | return result.mapped_mem; | |
| 399 | } | |
| 400 | ||
| 401 | const LoadInnerResult = struct { | |
| 402 | is_64: bool, | |
| 403 | endian: Endian, | |
| 404 | sections: Section.Array, | |
| 405 | mapped_mem: []align(std.heap.page_size_min) const u8, | |
| 406 | }; | |
| 407 | fn loadInner( | |
| 408 | arena: Allocator, | |
| 409 | elf_file: std.fs.File, | |
| 410 | opt_crc: ?u32, | |
| 411 | ) (LoadError || error{CrcMismatch})!LoadInnerResult { | |
| 412 | const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: { | |
| 413 | const file_len = std.math.cast( | |
| 414 | usize, | |
| 415 | elf_file.getEndPos() catch |err| switch (err) { | |
| 416 | error.PermissionDenied => unreachable, // not asking for PROT_EXEC | |
| 417 | else => |e| return e, | |
| 418 | }, | |
| 419 | ) orelse return error.Overflow; | |
| 420 | ||
| 421 | break :mapped std.posix.mmap( | |
| 422 | null, | |
| 423 | file_len, | |
| 424 | std.posix.PROT.READ, | |
| 425 | .{ .TYPE = .SHARED }, | |
| 426 | elf_file.handle, | |
| 427 | 0, | |
| 428 | ) catch |err| switch (err) { | |
| 429 | error.MappingAlreadyExists => unreachable, // not using FIXED_NOREPLACE | |
| 430 | error.PermissionDenied => unreachable, // not asking for PROT_EXEC | |
| 431 | else => |e| return e, | |
| 432 | }; | |
| 433 | }; | |
| 434 | ||
| 435 | if (opt_crc) |crc| { | |
| 436 | if (std.hash.crc.Crc32.hash(mapped_mem) != crc) { | |
| 437 | return error.CrcMismatch; | |
| 438 | } | |
| 439 | } | |
| 440 | errdefer std.posix.munmap(mapped_mem); | |
| 441 | ||
| 442 | var fr: std.Io.Reader = .fixed(mapped_mem); | |
| 443 | ||
| 444 | const header = elf.Header.read(&fr) catch |err| switch (err) { | |
| 445 | error.ReadFailed => unreachable, | |
| 446 | error.EndOfStream => return error.TruncatedElfFile, | |
| 447 | ||
| 448 | error.InvalidElfMagic, | |
| 449 | error.InvalidElfVersion, | |
| 450 | error.InvalidElfClass, | |
| 451 | error.InvalidElfEndian, | |
| 452 | => |e| return e, | |
| 453 | }; | |
| 454 | const endian = header.endian; | |
| 455 | ||
| 456 | const shstrtab_shdr_off = try std.math.add( | |
| 457 | u64, | |
| 458 | header.shoff, | |
| 459 | try std.math.mul(u64, header.shstrndx, header.shentsize), | |
| 460 | ); | |
| 461 | fr.seek = std.math.cast(usize, shstrtab_shdr_off) orelse return error.Overflow; | |
| 462 | const shstrtab: []const u8 = if (header.is_64) shstrtab: { | |
| 463 | const shdr = fr.takeStruct(elf.Elf64_Shdr, endian) catch return error.TruncatedElfFile; | |
| 464 | if (shdr.sh_offset + shdr.sh_size > mapped_mem.len) return error.TruncatedElfFile; | |
| 465 | break :shstrtab mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)]; | |
| 466 | } else shstrtab: { | |
| 467 | const shdr = fr.takeStruct(elf.Elf32_Shdr, endian) catch return error.TruncatedElfFile; | |
| 468 | if (shdr.sh_offset + shdr.sh_size > mapped_mem.len) return error.TruncatedElfFile; | |
| 469 | break :shstrtab mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)]; | |
| 470 | }; | |
| 471 | ||
| 472 | var sections: Section.Array = .initFill(null); | |
| 473 | ||
| 474 | var it = header.iterateSectionHeadersBuffer(mapped_mem); | |
| 475 | while (it.next() catch return error.TruncatedElfFile) |shdr| { | |
| 476 | if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; | |
| 477 | if (shdr.sh_name > shstrtab.len) return error.TruncatedElfFile; | |
| 478 | const name = std.mem.sliceTo(shstrtab[@intCast(shdr.sh_name)..], 0); | |
| 479 | ||
| 480 | const section_id: Section.Id = inline for (@typeInfo(Section.Id).@"enum".fields) |s| { | |
| 481 | if (std.mem.eql(u8, "." ++ s.name, name)) { | |
| 482 | break @enumFromInt(s.value); | |
| 483 | } | |
| 484 | } else continue; | |
| 485 | ||
| 486 | if (sections.get(section_id) != null) continue; | |
| 487 | ||
| 488 | if (shdr.sh_offset + shdr.sh_size > mapped_mem.len) return error.TruncatedElfFile; | |
| 489 | const raw_section_bytes = mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)]; | |
| 490 | const section_bytes: []const u8 = bytes: { | |
| 491 | if ((shdr.sh_flags & elf.SHF_COMPRESSED) == 0) break :bytes raw_section_bytes; | |
| 492 | ||
| 493 | var section_reader: std.Io.Reader = .fixed(raw_section_bytes); | |
| 494 | const ch_type: elf.COMPRESS, const ch_size: u64 = if (header.is_64) ch: { | |
| 495 | const chdr = section_reader.takeStruct(elf.Elf64_Chdr, endian) catch return error.InvalidCompressedSection; | |
| 496 | break :ch .{ chdr.ch_type, chdr.ch_size }; | |
| 497 | } else ch: { | |
| 498 | const chdr = section_reader.takeStruct(elf.Elf32_Chdr, endian) catch return error.InvalidCompressedSection; | |
| 499 | break :ch .{ chdr.ch_type, chdr.ch_size }; | |
| 500 | }; | |
| 501 | if (ch_type != .ZLIB) { | |
| 502 | // The compression algorithm is unsupported, but don't make that a hard error; the | |
| 503 | // file might still be valid, and we might still be okay without this section. | |
| 504 | continue; | |
| 505 | } | |
| 506 | ||
| 507 | const buf = try arena.alloc(u8, std.math.cast(usize, ch_size) orelse return error.Overflow); | |
| 508 | var fw: std.Io.Writer = .fixed(buf); | |
| 509 | var decompress: std.compress.flate.Decompress = .init(&section_reader, .zlib, &.{}); | |
| 510 | const n = decompress.reader.streamRemaining(&fw) catch |err| switch (err) { | |
| 511 | // If a write failed, then `buf` filled up, so `ch_size` was incorrect | |
| 512 | error.WriteFailed => return error.InvalidCompressedSection, | |
| 513 | // If a read failed, flate expected the section to have more data | |
| 514 | error.ReadFailed => return error.InvalidCompressedSection, | |
| 515 | }; | |
| 516 | // It's also an error if the data is shorter than expected. | |
| 517 | if (n != buf.len) return error.InvalidCompressedSection; | |
| 518 | break :bytes buf; | |
| 519 | }; | |
| 520 | sections.set(section_id, .{ .header = shdr, .bytes = section_bytes }); | |
| 521 | } | |
| 522 | ||
| 523 | return .{ | |
| 524 | .is_64 = header.is_64, | |
| 525 | .endian = endian, | |
| 526 | .sections = sections, | |
| 527 | .mapped_mem = mapped_mem, | |
| 528 | }; | |
| 529 | } | |
| 530 | ||
| 531 | const std = @import("std"); | |
| 532 | const Endian = std.builtin.Endian; | |
| 533 | const Dwarf = std.debug.Dwarf; | |
| 534 | const ElfFile = @This(); | |
| 535 | const Allocator = std.mem.Allocator; | |
| 536 | const elf = std.elf; |
lib/std/debug/Info.zig+19-11| ... | ... | @@ -9,7 +9,7 @@ |
| 9 | 9 | const std = @import("../std.zig"); |
| 10 | 10 | const Allocator = std.mem.Allocator; |
| 11 | 11 | const Path = std.Build.Cache.Path; |
| 12 | const Dwarf = std.debug.Dwarf; | |
| 12 | const ElfFile = std.debug.ElfFile; | |
| 13 | 13 | const assert = std.debug.assert; |
| 14 | 14 | const Coverage = std.debug.Coverage; |
| 15 | 15 | const SourceLocation = std.debug.Coverage.SourceLocation; |
| ... | ... | @@ -17,27 +17,35 @@ const SourceLocation = std.debug.Coverage.SourceLocation; |
| 17 | 17 | const Info = @This(); |
| 18 | 18 | |
| 19 | 19 | /// Sorted by key, ascending. |
| 20 | address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule), | |
| 20 | address_map: std.AutoArrayHashMapUnmanaged(u64, ElfFile), | |
| 21 | 21 | /// Externally managed, outlives this `Info` instance. |
| 22 | 22 | coverage: *Coverage, |
| 23 | 23 | |
| 24 | pub const LoadError = Dwarf.ElfModule.LoadError; | |
| 24 | pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || std.debug.Dwarf.ScanError || error{MissingDebugInfo}; | |
| 25 | 25 | |
| 26 | 26 | pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info { |
| 27 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | |
| 28 | var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, &sections, null); | |
| 29 | try elf_module.dwarf.populateRanges(gpa); | |
| 27 | var file = try path.root_dir.handle.openFile(path.sub_path, .{}); | |
| 28 | defer file.close(); | |
| 29 | ||
| 30 | var elf_file: ElfFile = try .load(gpa, file, null, &.none); | |
| 31 | errdefer elf_file.deinit(gpa); | |
| 32 | ||
| 33 | if (elf_file.dwarf == null) return error.MissingDebugInfo; | |
| 34 | try elf_file.dwarf.?.open(gpa, elf_file.endian); | |
| 35 | try elf_file.dwarf.?.populateRanges(gpa, elf_file.endian); | |
| 36 | ||
| 30 | 37 | var info: Info = .{ |
| 31 | 38 | .address_map = .{}, |
| 32 | 39 | .coverage = coverage, |
| 33 | 40 | }; |
| 34 | try info.address_map.put(gpa, elf_module.base_address, elf_module); | |
| 41 | try info.address_map.put(gpa, 0, elf_file); | |
| 42 | errdefer comptime unreachable; // elf_file is owned by the map now | |
| 35 | 43 | return info; |
| 36 | 44 | } |
| 37 | 45 | |
| 38 | 46 | pub fn deinit(info: *Info, gpa: Allocator) void { |
| 39 | for (info.address_map.values()) |*elf_module| { | |
| 40 | elf_module.dwarf.deinit(gpa); | |
| 47 | for (info.address_map.values()) |*elf_file| { | |
| 48 | elf_file.dwarf.?.deinit(gpa); | |
| 41 | 49 | } |
| 42 | 50 | info.address_map.deinit(gpa); |
| 43 | 51 | info.* = undefined; |
| ... | ... | @@ -57,6 +65,6 @@ pub fn resolveAddresses( |
| 57 | 65 | ) ResolveAddressesError!void { |
| 58 | 66 | assert(sorted_pc_addrs.len == output.len); |
| 59 | 67 | if (info.address_map.entries.len != 1) @panic("TODO"); |
| 60 | const elf_module = &info.address_map.values()[0]; | |
| 61 | return info.coverage.resolveAddressesDwarf(gpa, sorted_pc_addrs, output, &elf_module.dwarf); | |
| 68 | const elf_file = &info.address_map.values()[0]; | |
| 69 | return info.coverage.resolveAddressesDwarf(gpa, elf_file.endian, sorted_pc_addrs, output, &elf_file.dwarf.?); | |
| 62 | 70 | } |
lib/std/debug/Pdb.zig+19-18| ... | ... | @@ -171,6 +171,7 @@ pub fn parseInfoStream(self: *Pdb) !void { |
| 171 | 171 | const string_table_index = str_tab_index: { |
| 172 | 172 | const name_bytes_len = try reader.takeInt(u32, .little); |
| 173 | 173 | const name_bytes = try reader.readAlloc(gpa, name_bytes_len); |
| 174 | defer gpa.free(name_bytes); | |
| 174 | 175 | |
| 175 | 176 | const HashTableHeader = extern struct { |
| 176 | 177 | size: u32, |
| ... | ... | @@ -412,8 +413,7 @@ const Msf = struct { |
| 412 | 413 | return error.InvalidDebugInfo; |
| 413 | 414 | if (superblock.free_block_map_block != 1 and superblock.free_block_map_block != 2) |
| 414 | 415 | return error.InvalidDebugInfo; |
| 415 | const file_len = try file_reader.getSize(); | |
| 416 | if (superblock.num_blocks * superblock.block_size != file_len) | |
| 416 | if (superblock.num_blocks * superblock.block_size != try file_reader.getSize()) | |
| 417 | 417 | return error.InvalidDebugInfo; |
| 418 | 418 | switch (superblock.block_size) { |
| 419 | 419 | // llvm only supports 4096 but we can handle any of these values |
| ... | ... | @@ -427,6 +427,7 @@ const Msf = struct { |
| 427 | 427 | |
| 428 | 428 | try file_reader.seekTo(superblock.block_size * superblock.block_map_addr); |
| 429 | 429 | const dir_blocks = try gpa.alloc(u32, dir_block_count); |
| 430 | errdefer gpa.free(dir_blocks); | |
| 430 | 431 | for (dir_blocks) |*b| { |
| 431 | 432 | b.* = try file_reader.interface.takeInt(u32, .little); |
| 432 | 433 | } |
| ... | ... | @@ -450,25 +451,25 @@ const Msf = struct { |
| 450 | 451 | const streams = try gpa.alloc(MsfStream, stream_count); |
| 451 | 452 | errdefer gpa.free(streams); |
| 452 | 453 | |
| 453 | for (streams, 0..) |*stream, i| { | |
| 454 | const size = stream_sizes[i]; | |
| 454 | for (streams, stream_sizes) |*stream, size| { | |
| 455 | 455 | if (size == 0) { |
| 456 | 456 | stream.* = .empty; |
| 457 | } else { | |
| 458 | const blocks = try gpa.alloc(u32, size); | |
| 459 | errdefer gpa.free(blocks); | |
| 460 | for (blocks) |*block| { | |
| 461 | const block_id = try directory.interface.takeInt(u32, .little); | |
| 462 | const n = (block_id % superblock.block_size); | |
| 463 | // 0 is for pdb.SuperBlock, 1 and 2 for FPMs. | |
| 464 | if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.block_size > file_len) | |
| 465 | return error.InvalidBlockIndex; | |
| 466 | block.* = block_id; | |
| 467 | } | |
| 468 | const buffer = try gpa.alloc(u8, 64); | |
| 469 | errdefer gpa.free(buffer); | |
| 470 | stream.* = .init(superblock.block_size, file_reader, blocks, buffer); | |
| 457 | continue; | |
| 458 | } | |
| 459 | const blocks = try gpa.alloc(u32, size); | |
| 460 | errdefer gpa.free(blocks); | |
| 461 | for (blocks) |*block| { | |
| 462 | const block_id = try directory.interface.takeInt(u32, .little); | |
| 463 | // Index 0 is reserved for the superblock. | |
| 464 | // In theory, every page which is `n * block_size + 1` or `n * block_size + 2` | |
| 465 | // is also reserved, for one of the FPMs. However, LLVM has been observed to map | |
| 466 | // these into actual streams, so allow it for compatibility. | |
| 467 | if (block_id == 0 or block_id >= superblock.num_blocks) return error.InvalidBlockIndex; | |
| 468 | block.* = block_id; | |
| 471 | 469 | } |
| 470 | const buffer = try gpa.alloc(u8, 64); | |
| 471 | errdefer gpa.free(buffer); | |
| 472 | stream.* = .init(superblock.block_size, file_reader, blocks, buffer); | |
| 472 | 473 | } |
| 473 | 474 | |
| 474 | 475 | const end = directory.logicalPos(); |
lib/std/debug/SelfInfo.zig deleted-2238| ... | ... | @@ -1,2238 +0,0 @@ |
| 1 | //! Cross-platform abstraction for this binary's own debug information, with a | |
| 2 | //! goal of minimal code bloat and compilation speed penalty. | |
| 3 | ||
| 4 | const builtin = @import("builtin"); | |
| 5 | const native_os = builtin.os.tag; | |
| 6 | const native_endian = native_arch.endian(); | |
| 7 | const native_arch = builtin.cpu.arch; | |
| 8 | ||
| 9 | const std = @import("../std.zig"); | |
| 10 | const mem = std.mem; | |
| 11 | const Allocator = std.mem.Allocator; | |
| 12 | const windows = std.os.windows; | |
| 13 | const macho = std.macho; | |
| 14 | const fs = std.fs; | |
| 15 | const coff = std.coff; | |
| 16 | const pdb = std.pdb; | |
| 17 | const assert = std.debug.assert; | |
| 18 | const posix = std.posix; | |
| 19 | const elf = std.elf; | |
| 20 | const Dwarf = std.debug.Dwarf; | |
| 21 | const Pdb = std.debug.Pdb; | |
| 22 | const File = std.fs.File; | |
| 23 | const math = std.math; | |
| 24 | const testing = std.testing; | |
| 25 | const StackIterator = std.debug.StackIterator; | |
| 26 | const regBytes = Dwarf.abi.regBytes; | |
| 27 | const regValueNative = Dwarf.abi.regValueNative; | |
| 28 | ||
| 29 | const SelfInfo = @This(); | |
| 30 | ||
| 31 | const root = @import("root"); | |
| 32 | ||
| 33 | allocator: Allocator, | |
| 34 | address_map: std.AutoHashMap(usize, *Module), | |
| 35 | modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void, | |
| 36 | ||
| 37 | pub const OpenError = error{ | |
| 38 | MissingDebugInfo, | |
| 39 | UnsupportedOperatingSystem, | |
| 40 | } || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).@"fn".return_type.?).error_union.error_set; | |
| 41 | ||
| 42 | pub fn open(allocator: Allocator) OpenError!SelfInfo { | |
| 43 | nosuspend { | |
| 44 | if (builtin.strip_debug_info) | |
| 45 | return error.MissingDebugInfo; | |
| 46 | switch (native_os) { | |
| 47 | .linux, | |
| 48 | .freebsd, | |
| 49 | .netbsd, | |
| 50 | .dragonfly, | |
| 51 | .openbsd, | |
| 52 | .macos, | |
| 53 | .solaris, | |
| 54 | .illumos, | |
| 55 | .windows, | |
| 56 | => return try SelfInfo.init(allocator), | |
| 57 | else => return error.UnsupportedOperatingSystem, | |
| 58 | } | |
| 59 | } | |
| 60 | } | |
| 61 | ||
| 62 | pub fn init(allocator: Allocator) !SelfInfo { | |
| 63 | var debug_info: SelfInfo = .{ | |
| 64 | .allocator = allocator, | |
| 65 | .address_map = std.AutoHashMap(usize, *Module).init(allocator), | |
| 66 | .modules = if (native_os == .windows) .{} else {}, | |
| 67 | }; | |
| 68 | ||
| 69 | if (native_os == .windows) { | |
| 70 | errdefer debug_info.modules.deinit(allocator); | |
| 71 | ||
| 72 | const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); | |
| 73 | if (handle == windows.INVALID_HANDLE_VALUE) { | |
| 74 | switch (windows.GetLastError()) { | |
| 75 | else => |err| return windows.unexpectedError(err), | |
| 76 | } | |
| 77 | } | |
| 78 | defer windows.CloseHandle(handle); | |
| 79 | ||
| 80 | var module_entry: windows.MODULEENTRY32 = undefined; | |
| 81 | module_entry.dwSize = @sizeOf(windows.MODULEENTRY32); | |
| 82 | if (windows.kernel32.Module32First(handle, &module_entry) == 0) { | |
| 83 | return error.MissingDebugInfo; | |
| 84 | } | |
| 85 | ||
| 86 | var module_valid = true; | |
| 87 | while (module_valid) { | |
| 88 | const module_info = try debug_info.modules.addOne(allocator); | |
| 89 | const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{}; | |
| 90 | errdefer allocator.free(name); | |
| 91 | ||
| 92 | module_info.* = .{ | |
| 93 | .base_address = @intFromPtr(module_entry.modBaseAddr), | |
| 94 | .size = module_entry.modBaseSize, | |
| 95 | .name = name, | |
| 96 | .handle = module_entry.hModule, | |
| 97 | }; | |
| 98 | ||
| 99 | module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1; | |
| 100 | } | |
| 101 | } | |
| 102 | ||
| 103 | return debug_info; | |
| 104 | } | |
| 105 | ||
| 106 | pub fn deinit(self: *SelfInfo) void { | |
| 107 | var it = self.address_map.iterator(); | |
| 108 | while (it.next()) |entry| { | |
| 109 | const mdi = entry.value_ptr.*; | |
| 110 | mdi.deinit(self.allocator); | |
| 111 | self.allocator.destroy(mdi); | |
| 112 | } | |
| 113 | self.address_map.deinit(); | |
| 114 | if (native_os == .windows) { | |
| 115 | for (self.modules.items) |module| { | |
| 116 | self.allocator.free(module.name); | |
| 117 | if (module.mapped_file) |mapped_file| mapped_file.deinit(); | |
| 118 | } | |
| 119 | self.modules.deinit(self.allocator); | |
| 120 | } | |
| 121 | } | |
| 122 | ||
| 123 | pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module { | |
| 124 | if (builtin.target.os.tag.isDarwin()) { | |
| 125 | return self.lookupModuleDyld(address); | |
| 126 | } else if (native_os == .windows) { | |
| 127 | return self.lookupModuleWin32(address); | |
| 128 | } else if (native_os == .haiku) { | |
| 129 | return self.lookupModuleHaiku(address); | |
| 130 | } else if (builtin.target.cpu.arch.isWasm()) { | |
| 131 | return self.lookupModuleWasm(address); | |
| 132 | } else { | |
| 133 | return self.lookupModuleDl(address); | |
| 134 | } | |
| 135 | } | |
| 136 | ||
| 137 | // Returns the module name for a given address. | |
| 138 | // This can be called when getModuleForAddress fails, so implementations should provide | |
| 139 | // a path that doesn't rely on any side-effects of a prior successful module lookup. | |
| 140 | pub fn getModuleNameForAddress(self: *SelfInfo, address: usize) ?[]const u8 { | |
| 141 | if (builtin.target.os.tag.isDarwin()) { | |
| 142 | return self.lookupModuleNameDyld(address); | |
| 143 | } else if (native_os == .windows) { | |
| 144 | return self.lookupModuleNameWin32(address); | |
| 145 | } else if (native_os == .haiku) { | |
| 146 | return null; | |
| 147 | } else if (builtin.target.cpu.arch.isWasm()) { | |
| 148 | return null; | |
| 149 | } else { | |
| 150 | return self.lookupModuleNameDl(address); | |
| 151 | } | |
| 152 | } | |
| 153 | ||
| 154 | fn lookupModuleDyld(self: *SelfInfo, address: usize) !*Module { | |
| 155 | const image_count = std.c._dyld_image_count(); | |
| 156 | ||
| 157 | var i: u32 = 0; | |
| 158 | while (i < image_count) : (i += 1) { | |
| 159 | const header = std.c._dyld_get_image_header(i) orelse continue; | |
| 160 | const base_address = @intFromPtr(header); | |
| 161 | if (address < base_address) continue; | |
| 162 | const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); | |
| 163 | ||
| 164 | var it = macho.LoadCommandIterator{ | |
| 165 | .ncmds = header.ncmds, | |
| 166 | .buffer = @alignCast(@as( | |
| 167 | [*]u8, | |
| 168 | @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), | |
| 169 | )[0..header.sizeofcmds]), | |
| 170 | }; | |
| 171 | ||
| 172 | var unwind_info: ?[]const u8 = null; | |
| 173 | var eh_frame: ?[]const u8 = null; | |
| 174 | while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 175 | .SEGMENT_64 => { | |
| 176 | const segment_cmd = cmd.cast(macho.segment_command_64).?; | |
| 177 | if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; | |
| 178 | ||
| 179 | const seg_start = segment_cmd.vmaddr + vmaddr_slide; | |
| 180 | const seg_end = seg_start + segment_cmd.vmsize; | |
| 181 | if (address >= seg_start and address < seg_end) { | |
| 182 | if (self.address_map.get(base_address)) |obj_di| { | |
| 183 | return obj_di; | |
| 184 | } | |
| 185 | ||
| 186 | for (cmd.getSections()) |sect| { | |
| 187 | const sect_addr: usize = @intCast(sect.addr); | |
| 188 | const sect_size: usize = @intCast(sect.size); | |
| 189 | if (mem.eql(u8, "__unwind_info", sect.sectName())) { | |
| 190 | unwind_info = @as([*]const u8, @ptrFromInt(sect_addr + vmaddr_slide))[0..sect_size]; | |
| 191 | } else if (mem.eql(u8, "__eh_frame", sect.sectName())) { | |
| 192 | eh_frame = @as([*]const u8, @ptrFromInt(sect_addr + vmaddr_slide))[0..sect_size]; | |
| 193 | } | |
| 194 | } | |
| 195 | ||
| 196 | const obj_di = try self.allocator.create(Module); | |
| 197 | errdefer self.allocator.destroy(obj_di); | |
| 198 | ||
| 199 | const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0); | |
| 200 | const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) { | |
| 201 | error.FileNotFound => return error.MissingDebugInfo, | |
| 202 | else => return err, | |
| 203 | }; | |
| 204 | obj_di.* = try readMachODebugInfo(self.allocator, macho_file); | |
| 205 | obj_di.base_address = base_address; | |
| 206 | obj_di.vmaddr_slide = vmaddr_slide; | |
| 207 | obj_di.unwind_info = unwind_info; | |
| 208 | obj_di.eh_frame = eh_frame; | |
| 209 | ||
| 210 | try self.address_map.putNoClobber(base_address, obj_di); | |
| 211 | ||
| 212 | return obj_di; | |
| 213 | } | |
| 214 | }, | |
| 215 | else => {}, | |
| 216 | }; | |
| 217 | } | |
| 218 | ||
| 219 | return error.MissingDebugInfo; | |
| 220 | } | |
| 221 | ||
| 222 | fn lookupModuleNameDyld(self: *SelfInfo, address: usize) ?[]const u8 { | |
| 223 | _ = self; | |
| 224 | const image_count = std.c._dyld_image_count(); | |
| 225 | ||
| 226 | var i: u32 = 0; | |
| 227 | while (i < image_count) : (i += 1) { | |
| 228 | const header = std.c._dyld_get_image_header(i) orelse continue; | |
| 229 | const base_address = @intFromPtr(header); | |
| 230 | if (address < base_address) continue; | |
| 231 | const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); | |
| 232 | ||
| 233 | var it = macho.LoadCommandIterator{ | |
| 234 | .ncmds = header.ncmds, | |
| 235 | .buffer = @alignCast(@as( | |
| 236 | [*]u8, | |
| 237 | @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), | |
| 238 | )[0..header.sizeofcmds]), | |
| 239 | }; | |
| 240 | ||
| 241 | while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 242 | .SEGMENT_64 => { | |
| 243 | const segment_cmd = cmd.cast(macho.segment_command_64).?; | |
| 244 | if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; | |
| 245 | ||
| 246 | const original_address = address - vmaddr_slide; | |
| 247 | const seg_start = segment_cmd.vmaddr; | |
| 248 | const seg_end = seg_start + segment_cmd.vmsize; | |
| 249 | if (original_address >= seg_start and original_address < seg_end) { | |
| 250 | return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0)); | |
| 251 | } | |
| 252 | }, | |
| 253 | else => {}, | |
| 254 | }; | |
| 255 | } | |
| 256 | ||
| 257 | return null; | |
| 258 | } | |
| 259 | ||
| 260 | fn lookupModuleWin32(self: *SelfInfo, address: usize) !*Module { | |
| 261 | for (self.modules.items) |*module| { | |
| 262 | if (address >= module.base_address and address < module.base_address + module.size) { | |
| 263 | if (self.address_map.get(module.base_address)) |obj_di| { | |
| 264 | return obj_di; | |
| 265 | } | |
| 266 | ||
| 267 | const obj_di = try self.allocator.create(Module); | |
| 268 | errdefer self.allocator.destroy(obj_di); | |
| 269 | ||
| 270 | const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size]; | |
| 271 | var coff_obj = try coff.Coff.init(mapped_module, true); | |
| 272 | ||
| 273 | // The string table is not mapped into memory by the loader, so if a section name is in the | |
| 274 | // string table then we have to map the full image file from disk. This can happen when | |
| 275 | // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. | |
| 276 | if (coff_obj.strtabRequired()) { | |
| 277 | var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; | |
| 278 | // openFileAbsoluteW requires the prefix to be present | |
| 279 | @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' }); | |
| 280 | ||
| 281 | const process_handle = windows.GetCurrentProcess(); | |
| 282 | const len = windows.kernel32.GetModuleFileNameExW( | |
| 283 | process_handle, | |
| 284 | module.handle, | |
| 285 | @ptrCast(&name_buffer[4]), | |
| 286 | windows.PATH_MAX_WIDE, | |
| 287 | ); | |
| 288 | ||
| 289 | if (len == 0) return error.MissingDebugInfo; | |
| 290 | const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { | |
| 291 | error.FileNotFound => return error.MissingDebugInfo, | |
| 292 | else => return err, | |
| 293 | }; | |
| 294 | errdefer coff_file.close(); | |
| 295 | ||
| 296 | var section_handle: windows.HANDLE = undefined; | |
| 297 | const create_section_rc = windows.ntdll.NtCreateSection( | |
| 298 | &section_handle, | |
| 299 | windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, | |
| 300 | null, | |
| 301 | null, | |
| 302 | windows.PAGE_READONLY, | |
| 303 | // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. | |
| 304 | // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. | |
| 305 | windows.SEC_COMMIT, | |
| 306 | coff_file.handle, | |
| 307 | ); | |
| 308 | if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; | |
| 309 | errdefer windows.CloseHandle(section_handle); | |
| 310 | ||
| 311 | var coff_len: usize = 0; | |
| 312 | var base_ptr: usize = 0; | |
| 313 | const map_section_rc = windows.ntdll.NtMapViewOfSection( | |
| 314 | section_handle, | |
| 315 | process_handle, | |
| 316 | @ptrCast(&base_ptr), | |
| 317 | null, | |
| 318 | 0, | |
| 319 | null, | |
| 320 | &coff_len, | |
| 321 | .ViewUnmap, | |
| 322 | 0, | |
| 323 | windows.PAGE_READONLY, | |
| 324 | ); | |
| 325 | if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; | |
| 326 | errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS); | |
| 327 | ||
| 328 | const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len]; | |
| 329 | coff_obj = try coff.Coff.init(section_view, false); | |
| 330 | ||
| 331 | module.mapped_file = .{ | |
| 332 | .file = coff_file, | |
| 333 | .section_handle = section_handle, | |
| 334 | .section_view = section_view, | |
| 335 | }; | |
| 336 | } | |
| 337 | errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit(); | |
| 338 | ||
| 339 | obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj); | |
| 340 | obj_di.base_address = module.base_address; | |
| 341 | ||
| 342 | try self.address_map.putNoClobber(module.base_address, obj_di); | |
| 343 | return obj_di; | |
| 344 | } | |
| 345 | } | |
| 346 | ||
| 347 | return error.MissingDebugInfo; | |
| 348 | } | |
| 349 | ||
| 350 | fn lookupModuleNameWin32(self: *SelfInfo, address: usize) ?[]const u8 { | |
| 351 | for (self.modules.items) |module| { | |
| 352 | if (address >= module.base_address and address < module.base_address + module.size) { | |
| 353 | return module.name; | |
| 354 | } | |
| 355 | } | |
| 356 | return null; | |
| 357 | } | |
| 358 | ||
| 359 | fn lookupModuleNameDl(self: *SelfInfo, address: usize) ?[]const u8 { | |
| 360 | _ = self; | |
| 361 | ||
| 362 | var ctx: struct { | |
| 363 | // Input | |
| 364 | address: usize, | |
| 365 | // Output | |
| 366 | name: []const u8 = "", | |
| 367 | } = .{ .address = address }; | |
| 368 | const CtxTy = @TypeOf(ctx); | |
| 369 | ||
| 370 | if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { | |
| 371 | fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { | |
| 372 | _ = size; | |
| 373 | if (context.address < info.addr) return; | |
| 374 | const phdrs = info.phdr[0..info.phnum]; | |
| 375 | for (phdrs) |*phdr| { | |
| 376 | if (phdr.p_type != elf.PT_LOAD) continue; | |
| 377 | ||
| 378 | const seg_start = info.addr +% phdr.p_vaddr; | |
| 379 | const seg_end = seg_start + phdr.p_memsz; | |
| 380 | if (context.address >= seg_start and context.address < seg_end) { | |
| 381 | context.name = mem.sliceTo(info.name, 0) orelse ""; | |
| 382 | break; | |
| 383 | } | |
| 384 | } else return; | |
| 385 | ||
| 386 | return error.Found; | |
| 387 | } | |
| 388 | }.callback)) { | |
| 389 | return null; | |
| 390 | } else |err| switch (err) { | |
| 391 | error.Found => return fs.path.basename(ctx.name), | |
| 392 | } | |
| 393 | ||
| 394 | return null; | |
| 395 | } | |
| 396 | ||
| 397 | fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module { | |
| 398 | var ctx: struct { | |
| 399 | // Input | |
| 400 | address: usize, | |
| 401 | // Output | |
| 402 | base_address: usize = undefined, | |
| 403 | name: []const u8 = undefined, | |
| 404 | build_id: ?[]const u8 = null, | |
| 405 | gnu_eh_frame: ?[]const u8 = null, | |
| 406 | } = .{ .address = address }; | |
| 407 | const CtxTy = @TypeOf(ctx); | |
| 408 | ||
| 409 | if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { | |
| 410 | fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { | |
| 411 | _ = size; | |
| 412 | // The base address is too high | |
| 413 | if (context.address < info.addr) | |
| 414 | return; | |
| 415 | ||
| 416 | const phdrs = info.phdr[0..info.phnum]; | |
| 417 | for (phdrs) |*phdr| { | |
| 418 | if (phdr.p_type != elf.PT_LOAD) continue; | |
| 419 | ||
| 420 | // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000 | |
| 421 | const seg_start = info.addr +% phdr.p_vaddr; | |
| 422 | const seg_end = seg_start + phdr.p_memsz; | |
| 423 | if (context.address >= seg_start and context.address < seg_end) { | |
| 424 | // Android libc uses NULL instead of an empty string to mark the | |
| 425 | // main program | |
| 426 | context.name = mem.sliceTo(info.name, 0) orelse ""; | |
| 427 | context.base_address = info.addr; | |
| 428 | break; | |
| 429 | } | |
| 430 | } else return; | |
| 431 | ||
| 432 | for (info.phdr[0..info.phnum]) |phdr| { | |
| 433 | switch (phdr.p_type) { | |
| 434 | elf.PT_NOTE => { | |
| 435 | // Look for .note.gnu.build-id | |
| 436 | const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; | |
| 437 | const name_size = mem.readInt(u32, note_bytes[0..4], native_endian); | |
| 438 | if (name_size != 4) continue; | |
| 439 | const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian); | |
| 440 | const note_type = mem.readInt(u32, note_bytes[8..12], native_endian); | |
| 441 | if (note_type != elf.NT_GNU_BUILD_ID) continue; | |
| 442 | if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue; | |
| 443 | context.build_id = note_bytes[16..][0..desc_size]; | |
| 444 | }, | |
| 445 | elf.PT_GNU_EH_FRAME => { | |
| 446 | context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; | |
| 447 | }, | |
| 448 | else => {}, | |
| 449 | } | |
| 450 | } | |
| 451 | ||
| 452 | // Stop the iteration | |
| 453 | return error.Found; | |
| 454 | } | |
| 455 | }.callback)) { | |
| 456 | return error.MissingDebugInfo; | |
| 457 | } else |err| switch (err) { | |
| 458 | error.Found => {}, | |
| 459 | } | |
| 460 | ||
| 461 | if (self.address_map.get(ctx.base_address)) |obj_di| { | |
| 462 | return obj_di; | |
| 463 | } | |
| 464 | ||
| 465 | const obj_di = try self.allocator.create(Module); | |
| 466 | errdefer self.allocator.destroy(obj_di); | |
| 467 | ||
| 468 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | |
| 469 | if (ctx.gnu_eh_frame) |eh_frame_hdr| { | |
| 470 | // This is a special case - pointer offsets inside .eh_frame_hdr | |
| 471 | // are encoded relative to its base address, so we must use the | |
| 472 | // version that is already memory mapped, and not the one that | |
| 473 | // will be mapped separately from the ELF file. | |
| 474 | sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{ | |
| 475 | .data = eh_frame_hdr, | |
| 476 | .owned = false, | |
| 477 | }; | |
| 478 | } | |
| 479 | ||
| 480 | obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null); | |
| 481 | obj_di.base_address = ctx.base_address; | |
| 482 | ||
| 483 | // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding | |
| 484 | obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {}; | |
| 485 | ||
| 486 | try self.address_map.putNoClobber(ctx.base_address, obj_di); | |
| 487 | ||
| 488 | return obj_di; | |
| 489 | } | |
| 490 | ||
| 491 | fn lookupModuleHaiku(self: *SelfInfo, address: usize) !*Module { | |
| 492 | _ = self; | |
| 493 | _ = address; | |
| 494 | @panic("TODO implement lookup module for Haiku"); | |
| 495 | } | |
| 496 | ||
| 497 | fn lookupModuleWasm(self: *SelfInfo, address: usize) !*Module { | |
| 498 | _ = self; | |
| 499 | _ = address; | |
| 500 | @panic("TODO implement lookup module for Wasm"); | |
| 501 | } | |
| 502 | ||
| 503 | pub const Module = switch (native_os) { | |
| 504 | .macos, .ios, .watchos, .tvos, .visionos => struct { | |
| 505 | base_address: usize, | |
| 506 | vmaddr_slide: usize, | |
| 507 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 508 | symbols: []const MachoSymbol, | |
| 509 | strings: [:0]const u8, | |
| 510 | ofiles: OFileTable, | |
| 511 | ||
| 512 | // Backed by the in-memory sections mapped by the loader | |
| 513 | unwind_info: ?[]const u8 = null, | |
| 514 | eh_frame: ?[]const u8 = null, | |
| 515 | ||
| 516 | const OFileTable = std.StringHashMap(OFileInfo); | |
| 517 | const OFileInfo = struct { | |
| 518 | di: Dwarf, | |
| 519 | addr_table: std.StringHashMap(u64), | |
| 520 | }; | |
| 521 | ||
| 522 | pub fn deinit(self: *@This(), allocator: Allocator) void { | |
| 523 | var it = self.ofiles.iterator(); | |
| 524 | while (it.next()) |entry| { | |
| 525 | const ofile = entry.value_ptr; | |
| 526 | ofile.di.deinit(allocator); | |
| 527 | ofile.addr_table.deinit(); | |
| 528 | } | |
| 529 | self.ofiles.deinit(); | |
| 530 | allocator.free(self.symbols); | |
| 531 | posix.munmap(self.mapped_memory); | |
| 532 | } | |
| 533 | ||
| 534 | fn loadOFile(self: *@This(), allocator: Allocator, o_file_path: []const u8) !*OFileInfo { | |
| 535 | const o_file = try fs.cwd().openFile(o_file_path, .{}); | |
| 536 | const mapped_mem = try mapWholeFile(o_file); | |
| 537 | ||
| 538 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); | |
| 539 | if (hdr.magic != std.macho.MH_MAGIC_64) | |
| 540 | return error.InvalidDebugInfo; | |
| 541 | ||
| 542 | var segcmd: ?macho.LoadCommandIterator.LoadCommand = null; | |
| 543 | var symtabcmd: ?macho.symtab_command = null; | |
| 544 | var it = macho.LoadCommandIterator{ | |
| 545 | .ncmds = hdr.ncmds, | |
| 546 | .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | |
| 547 | }; | |
| 548 | while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 549 | .SEGMENT_64 => segcmd = cmd, | |
| 550 | .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?, | |
| 551 | else => {}, | |
| 552 | }; | |
| 553 | ||
| 554 | if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo; | |
| 555 | ||
| 556 | // Parse symbols | |
| 557 | const strtab = @as( | |
| 558 | [*]const u8, | |
| 559 | @ptrCast(&mapped_mem[symtabcmd.?.stroff]), | |
| 560 | )[0 .. symtabcmd.?.strsize - 1 :0]; | |
| 561 | const symtab = @as( | |
| 562 | [*]const macho.nlist_64, | |
| 563 | @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])), | |
| 564 | )[0..symtabcmd.?.nsyms]; | |
| 565 | ||
| 566 | // TODO handle tentative (common) symbols | |
| 567 | var addr_table = std.StringHashMap(u64).init(allocator); | |
| 568 | try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len))); | |
| 569 | for (symtab) |sym| { | |
| 570 | if (sym.n_strx == 0) continue; | |
| 571 | if (sym.undf() or sym.tentative() or sym.abs()) continue; | |
| 572 | const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); | |
| 573 | // TODO is it possible to have a symbol collision? | |
| 574 | addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value); | |
| 575 | } | |
| 576 | ||
| 577 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | |
| 578 | if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ | |
| 579 | .data = eh_frame, | |
| 580 | .owned = false, | |
| 581 | }; | |
| 582 | ||
| 583 | for (segcmd.?.getSections()) |sect| { | |
| 584 | if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; | |
| 585 | ||
| 586 | var section_index: ?usize = null; | |
| 587 | inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { | |
| 588 | if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i; | |
| 589 | } | |
| 590 | if (section_index == null) continue; | |
| 591 | ||
| 592 | const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size); | |
| 593 | sections[section_index.?] = .{ | |
| 594 | .data = section_bytes, | |
| 595 | .virtual_address = @intCast(sect.addr), | |
| 596 | .owned = false, | |
| 597 | }; | |
| 598 | } | |
| 599 | ||
| 600 | const missing_debug_info = | |
| 601 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | |
| 602 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | |
| 603 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | |
| 604 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; | |
| 605 | if (missing_debug_info) return error.MissingDebugInfo; | |
| 606 | ||
| 607 | var di: Dwarf = .{ | |
| 608 | .endian = .little, | |
| 609 | .sections = sections, | |
| 610 | .is_macho = true, | |
| 611 | }; | |
| 612 | ||
| 613 | try Dwarf.open(&di, allocator); | |
| 614 | const info = OFileInfo{ | |
| 615 | .di = di, | |
| 616 | .addr_table = addr_table, | |
| 617 | }; | |
| 618 | ||
| 619 | // Add the debug info to the cache | |
| 620 | const result = try self.ofiles.getOrPut(o_file_path); | |
| 621 | assert(!result.found_existing); | |
| 622 | result.value_ptr.* = info; | |
| 623 | ||
| 624 | return result.value_ptr; | |
| 625 | } | |
| 626 | ||
| 627 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { | |
| 628 | nosuspend { | |
| 629 | const result = try self.getOFileInfoForAddress(allocator, address); | |
| 630 | if (result.symbol == null) return .{}; | |
| 631 | ||
| 632 | // Take the symbol name from the N_FUN STAB entry, we're going to | |
| 633 | // use it if we fail to find the DWARF infos | |
| 634 | const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0); | |
| 635 | if (result.o_file_info == null) return .{ .name = stab_symbol }; | |
| 636 | ||
| 637 | // Translate again the address, this time into an address inside the | |
| 638 | // .o file | |
| 639 | const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{ | |
| 640 | .name = "???", | |
| 641 | }; | |
| 642 | ||
| 643 | const addr_off = result.relocated_address - result.symbol.?.addr; | |
| 644 | const o_file_di = &result.o_file_info.?.di; | |
| 645 | if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { | |
| 646 | return .{ | |
| 647 | .name = o_file_di.getSymbolName(relocated_address_o) orelse "???", | |
| 648 | .compile_unit_name = compile_unit.die.getAttrString( | |
| 649 | o_file_di, | |
| 650 | std.dwarf.AT.name, | |
| 651 | o_file_di.section(.debug_str), | |
| 652 | compile_unit.*, | |
| 653 | ) catch |err| switch (err) { | |
| 654 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", | |
| 655 | }, | |
| 656 | .source_location = o_file_di.getLineNumberInfo( | |
| 657 | allocator, | |
| 658 | compile_unit, | |
| 659 | relocated_address_o + addr_off, | |
| 660 | ) catch |err| switch (err) { | |
| 661 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | |
| 662 | else => return err, | |
| 663 | }, | |
| 664 | }; | |
| 665 | } else |err| switch (err) { | |
| 666 | error.MissingDebugInfo, error.InvalidDebugInfo => { | |
| 667 | return .{ .name = stab_symbol }; | |
| 668 | }, | |
| 669 | else => return err, | |
| 670 | } | |
| 671 | } | |
| 672 | } | |
| 673 | ||
| 674 | pub fn getOFileInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !struct { | |
| 675 | relocated_address: usize, | |
| 676 | symbol: ?*const MachoSymbol = null, | |
| 677 | o_file_info: ?*OFileInfo = null, | |
| 678 | } { | |
| 679 | nosuspend { | |
| 680 | // Translate the VA into an address into this object | |
| 681 | const relocated_address = address - self.vmaddr_slide; | |
| 682 | ||
| 683 | // Find the .o file where this symbol is defined | |
| 684 | const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{ | |
| 685 | .relocated_address = relocated_address, | |
| 686 | }; | |
| 687 | ||
| 688 | // Check if its debug infos are already in the cache | |
| 689 | const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0); | |
| 690 | const o_file_info = self.ofiles.getPtr(o_file_path) orelse | |
| 691 | (self.loadOFile(allocator, o_file_path) catch |err| switch (err) { | |
| 692 | error.FileNotFound, | |
| 693 | error.MissingDebugInfo, | |
| 694 | error.InvalidDebugInfo, | |
| 695 | => return .{ | |
| 696 | .relocated_address = relocated_address, | |
| 697 | .symbol = symbol, | |
| 698 | }, | |
| 699 | else => return err, | |
| 700 | }); | |
| 701 | ||
| 702 | return .{ | |
| 703 | .relocated_address = relocated_address, | |
| 704 | .symbol = symbol, | |
| 705 | .o_file_info = o_file_info, | |
| 706 | }; | |
| 707 | } | |
| 708 | } | |
| 709 | ||
| 710 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf { | |
| 711 | return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null; | |
| 712 | } | |
| 713 | }, | |
| 714 | .uefi, .windows => struct { | |
| 715 | base_address: usize, | |
| 716 | pdb: ?Pdb, | |
| 717 | dwarf: ?Dwarf, | |
| 718 | coff_image_base: u64, | |
| 719 | ||
| 720 | /// Only used if pdb is non-null | |
| 721 | coff_section_headers: []coff.SectionHeader, | |
| 722 | ||
| 723 | pub fn deinit(self: *@This(), gpa: Allocator) void { | |
| 724 | if (self.dwarf) |*dwarf| { | |
| 725 | dwarf.deinit(gpa); | |
| 726 | } | |
| 727 | ||
| 728 | if (self.pdb) |*p| { | |
| 729 | gpa.free(p.file_reader.interface.buffer); | |
| 730 | gpa.destroy(p.file_reader); | |
| 731 | p.deinit(); | |
| 732 | gpa.free(self.coff_section_headers); | |
| 733 | } | |
| 734 | ||
| 735 | self.* = undefined; | |
| 736 | } | |
| 737 | ||
| 738 | fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?std.debug.Symbol { | |
| 739 | var coff_section: *align(1) const coff.SectionHeader = undefined; | |
| 740 | const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| { | |
| 741 | if (sect_contrib.section > self.coff_section_headers.len) continue; | |
| 742 | // Remember that SectionContribEntry.Section is 1-based. | |
| 743 | coff_section = &self.coff_section_headers[sect_contrib.section - 1]; | |
| 744 | ||
| 745 | const vaddr_start = coff_section.virtual_address + sect_contrib.offset; | |
| 746 | const vaddr_end = vaddr_start + sect_contrib.size; | |
| 747 | if (relocated_address >= vaddr_start and relocated_address < vaddr_end) { | |
| 748 | break sect_contrib.module_index; | |
| 749 | } | |
| 750 | } else { | |
| 751 | // we have no information to add to the address | |
| 752 | return null; | |
| 753 | }; | |
| 754 | ||
| 755 | const module = (try self.pdb.?.getModule(mod_index)) orelse | |
| 756 | return error.InvalidDebugInfo; | |
| 757 | const obj_basename = fs.path.basename(module.obj_file_name); | |
| 758 | ||
| 759 | const symbol_name = self.pdb.?.getSymbolName( | |
| 760 | module, | |
| 761 | relocated_address - coff_section.virtual_address, | |
| 762 | ) orelse "???"; | |
| 763 | const opt_line_info = try self.pdb.?.getLineNumberInfo( | |
| 764 | module, | |
| 765 | relocated_address - coff_section.virtual_address, | |
| 766 | ); | |
| 767 | ||
| 768 | return .{ | |
| 769 | .name = symbol_name, | |
| 770 | .compile_unit_name = obj_basename, | |
| 771 | .source_location = opt_line_info, | |
| 772 | }; | |
| 773 | } | |
| 774 | ||
| 775 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { | |
| 776 | // Translate the VA into an address into this object | |
| 777 | const relocated_address = address - self.base_address; | |
| 778 | ||
| 779 | if (self.pdb != null) { | |
| 780 | if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol; | |
| 781 | } | |
| 782 | ||
| 783 | if (self.dwarf) |*dwarf| { | |
| 784 | const dwarf_address = relocated_address + self.coff_image_base; | |
| 785 | return dwarf.getSymbol(allocator, dwarf_address); | |
| 786 | } | |
| 787 | ||
| 788 | return .{}; | |
| 789 | } | |
| 790 | ||
| 791 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf { | |
| 792 | _ = allocator; | |
| 793 | _ = address; | |
| 794 | ||
| 795 | return switch (self.debug_data) { | |
| 796 | .dwarf => |*dwarf| dwarf, | |
| 797 | else => null, | |
| 798 | }; | |
| 799 | } | |
| 800 | }, | |
| 801 | .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => Dwarf.ElfModule, | |
| 802 | .wasi, .emscripten => struct { | |
| 803 | pub fn deinit(self: *@This(), allocator: Allocator) void { | |
| 804 | _ = self; | |
| 805 | _ = allocator; | |
| 806 | } | |
| 807 | ||
| 808 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { | |
| 809 | _ = self; | |
| 810 | _ = allocator; | |
| 811 | _ = address; | |
| 812 | return .{}; | |
| 813 | } | |
| 814 | ||
| 815 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf { | |
| 816 | _ = self; | |
| 817 | _ = allocator; | |
| 818 | _ = address; | |
| 819 | return null; | |
| 820 | } | |
| 821 | }, | |
| 822 | else => Dwarf, | |
| 823 | }; | |
| 824 | ||
| 825 | /// How is this different than `Module` when the host is Windows? | |
| 826 | /// Why are both stored in the `SelfInfo` struct? | |
| 827 | /// Boy, it sure would be nice if someone added documentation comments for this | |
| 828 | /// struct explaining it. | |
| 829 | pub const WindowsModule = struct { | |
| 830 | base_address: usize, | |
| 831 | size: u32, | |
| 832 | name: []const u8, | |
| 833 | handle: windows.HMODULE, | |
| 834 | ||
| 835 | // Set when the image file needed to be mapped from disk | |
| 836 | mapped_file: ?struct { | |
| 837 | file: File, | |
| 838 | section_handle: windows.HANDLE, | |
| 839 | section_view: []const u8, | |
| 840 | ||
| 841 | pub fn deinit(self: @This()) void { | |
| 842 | const process_handle = windows.GetCurrentProcess(); | |
| 843 | assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrCast(@constCast(self.section_view.ptr))) == .SUCCESS); | |
| 844 | windows.CloseHandle(self.section_handle); | |
| 845 | self.file.close(); | |
| 846 | } | |
| 847 | } = null, | |
| 848 | }; | |
| 849 | ||
| 850 | /// This takes ownership of macho_file: users of this function should not close | |
| 851 | /// it themselves, even on error. | |
| 852 | /// TODO it's weird to take ownership even on error, rework this code. | |
| 853 | fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module { | |
| 854 | const mapped_mem = try mapWholeFile(macho_file); | |
| 855 | ||
| 856 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); | |
| 857 | if (hdr.magic != macho.MH_MAGIC_64) | |
| 858 | return error.InvalidDebugInfo; | |
| 859 | ||
| 860 | var it = macho.LoadCommandIterator{ | |
| 861 | .ncmds = hdr.ncmds, | |
| 862 | .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | |
| 863 | }; | |
| 864 | const symtab = while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 865 | .SYMTAB => break cmd.cast(macho.symtab_command).?, | |
| 866 | else => {}, | |
| 867 | } else return error.MissingDebugInfo; | |
| 868 | ||
| 869 | const syms = @as( | |
| 870 | [*]const macho.nlist_64, | |
| 871 | @ptrCast(@alignCast(&mapped_mem[symtab.symoff])), | |
| 872 | )[0..symtab.nsyms]; | |
| 873 | const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0]; | |
| 874 | ||
| 875 | const symbols_buf = try allocator.alloc(MachoSymbol, syms.len); | |
| 876 | ||
| 877 | var ofile: u32 = undefined; | |
| 878 | var last_sym: MachoSymbol = undefined; | |
| 879 | var symbol_index: usize = 0; | |
| 880 | var state: enum { | |
| 881 | init, | |
| 882 | oso_open, | |
| 883 | oso_close, | |
| 884 | bnsym, | |
| 885 | fun_strx, | |
| 886 | fun_size, | |
| 887 | ensym, | |
| 888 | } = .init; | |
| 889 | ||
| 890 | for (syms) |*sym| { | |
| 891 | if (!sym.stab()) continue; | |
| 892 | ||
| 893 | // TODO handle globals N_GSYM, and statics N_STSYM | |
| 894 | switch (sym.n_type) { | |
| 895 | macho.N_OSO => { | |
| 896 | switch (state) { | |
| 897 | .init, .oso_close => { | |
| 898 | state = .oso_open; | |
| 899 | ofile = sym.n_strx; | |
| 900 | }, | |
| 901 | else => return error.InvalidDebugInfo, | |
| 902 | } | |
| 903 | }, | |
| 904 | macho.N_BNSYM => { | |
| 905 | switch (state) { | |
| 906 | .oso_open, .ensym => { | |
| 907 | state = .bnsym; | |
| 908 | last_sym = .{ | |
| 909 | .strx = 0, | |
| 910 | .addr = sym.n_value, | |
| 911 | .size = 0, | |
| 912 | .ofile = ofile, | |
| 913 | }; | |
| 914 | }, | |
| 915 | else => return error.InvalidDebugInfo, | |
| 916 | } | |
| 917 | }, | |
| 918 | macho.N_FUN => { | |
| 919 | switch (state) { | |
| 920 | .bnsym => { | |
| 921 | state = .fun_strx; | |
| 922 | last_sym.strx = sym.n_strx; | |
| 923 | }, | |
| 924 | .fun_strx => { | |
| 925 | state = .fun_size; | |
| 926 | last_sym.size = @as(u32, @intCast(sym.n_value)); | |
| 927 | }, | |
| 928 | else => return error.InvalidDebugInfo, | |
| 929 | } | |
| 930 | }, | |
| 931 | macho.N_ENSYM => { | |
| 932 | switch (state) { | |
| 933 | .fun_size => { | |
| 934 | state = .ensym; | |
| 935 | symbols_buf[symbol_index] = last_sym; | |
| 936 | symbol_index += 1; | |
| 937 | }, | |
| 938 | else => return error.InvalidDebugInfo, | |
| 939 | } | |
| 940 | }, | |
| 941 | macho.N_SO => { | |
| 942 | switch (state) { | |
| 943 | .init, .oso_close => {}, | |
| 944 | .oso_open, .ensym => { | |
| 945 | state = .oso_close; | |
| 946 | }, | |
| 947 | else => return error.InvalidDebugInfo, | |
| 948 | } | |
| 949 | }, | |
| 950 | else => {}, | |
| 951 | } | |
| 952 | } | |
| 953 | ||
| 954 | switch (state) { | |
| 955 | .init => return error.MissingDebugInfo, | |
| 956 | .oso_close => {}, | |
| 957 | else => return error.InvalidDebugInfo, | |
| 958 | } | |
| 959 | ||
| 960 | const symbols = try allocator.realloc(symbols_buf, symbol_index); | |
| 961 | ||
| 962 | // Even though lld emits symbols in ascending order, this debug code | |
| 963 | // should work for programs linked in any valid way. | |
| 964 | // This sort is so that we can binary search later. | |
| 965 | mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan); | |
| 966 | ||
| 967 | return .{ | |
| 968 | .base_address = undefined, | |
| 969 | .vmaddr_slide = undefined, | |
| 970 | .mapped_memory = mapped_mem, | |
| 971 | .ofiles = Module.OFileTable.init(allocator), | |
| 972 | .symbols = symbols, | |
| 973 | .strings = strings, | |
| 974 | }; | |
| 975 | } | |
| 976 | ||
| 977 | fn readCoffDebugInfo(gpa: Allocator, coff_obj: *coff.Coff) !Module { | |
| 978 | nosuspend { | |
| 979 | var di: Module = .{ | |
| 980 | .base_address = undefined, | |
| 981 | .coff_image_base = coff_obj.getImageBase(), | |
| 982 | .coff_section_headers = undefined, | |
| 983 | .pdb = null, | |
| 984 | .dwarf = null, | |
| 985 | }; | |
| 986 | ||
| 987 | if (coff_obj.getSectionByName(".debug_info")) |_| { | |
| 988 | // This coff file has embedded DWARF debug info | |
| 989 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | |
| 990 | errdefer for (sections) |section| if (section) |s| if (s.owned) gpa.free(s.data); | |
| 991 | ||
| 992 | inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { | |
| 993 | sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { | |
| 994 | break :blk .{ | |
| 995 | .data = try coff_obj.getSectionDataAlloc(section_header, gpa), | |
| 996 | .virtual_address = section_header.virtual_address, | |
| 997 | .owned = true, | |
| 998 | }; | |
| 999 | } else null; | |
| 1000 | } | |
| 1001 | ||
| 1002 | var dwarf: Dwarf = .{ | |
| 1003 | .endian = native_endian, | |
| 1004 | .sections = sections, | |
| 1005 | .is_macho = false, | |
| 1006 | }; | |
| 1007 | ||
| 1008 | try Dwarf.open(&dwarf, gpa); | |
| 1009 | di.dwarf = dwarf; | |
| 1010 | } | |
| 1011 | ||
| 1012 | const raw_path = try coff_obj.getPdbPath() orelse return di; | |
| 1013 | const path = blk: { | |
| 1014 | if (fs.path.isAbsolute(raw_path)) { | |
| 1015 | break :blk raw_path; | |
| 1016 | } else { | |
| 1017 | const self_dir = try fs.selfExeDirPathAlloc(gpa); | |
| 1018 | defer gpa.free(self_dir); | |
| 1019 | break :blk try fs.path.join(gpa, &.{ self_dir, raw_path }); | |
| 1020 | } | |
| 1021 | }; | |
| 1022 | defer if (path.ptr != raw_path.ptr) gpa.free(path); | |
| 1023 | ||
| 1024 | const pdb_file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { | |
| 1025 | error.FileNotFound, error.IsDir => { | |
| 1026 | if (di.dwarf == null) return error.MissingDebugInfo; | |
| 1027 | return di; | |
| 1028 | }, | |
| 1029 | else => |e| return e, | |
| 1030 | }; | |
| 1031 | errdefer pdb_file.close(); | |
| 1032 | ||
| 1033 | const pdb_file_reader_buffer = try gpa.alloc(u8, 4096); | |
| 1034 | errdefer gpa.free(pdb_file_reader_buffer); | |
| 1035 | ||
| 1036 | const pdb_file_reader = try gpa.create(File.Reader); | |
| 1037 | errdefer gpa.destroy(pdb_file_reader); | |
| 1038 | ||
| 1039 | pdb_file_reader.* = pdb_file.reader(pdb_file_reader_buffer); | |
| 1040 | ||
| 1041 | di.pdb = try Pdb.init(gpa, pdb_file_reader); | |
| 1042 | try di.pdb.?.parseInfoStream(); | |
| 1043 | try di.pdb.?.parseDbiStream(); | |
| 1044 | ||
| 1045 | if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age) | |
| 1046 | return error.InvalidDebugInfo; | |
| 1047 | ||
| 1048 | // Only used by the pdb path | |
| 1049 | di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa); | |
| 1050 | errdefer gpa.free(di.coff_section_headers); | |
| 1051 | ||
| 1052 | return di; | |
| 1053 | } | |
| 1054 | } | |
| 1055 | ||
| 1056 | /// Reads debug info from an ELF file, or the current binary if none in specified. | |
| 1057 | /// If the required sections aren't present but a reference to external debug info is, | |
| 1058 | /// then this this function will recurse to attempt to load the debug sections from | |
| 1059 | /// an external file. | |
| 1060 | pub fn readElfDebugInfo( | |
| 1061 | allocator: Allocator, | |
| 1062 | elf_filename: ?[]const u8, | |
| 1063 | build_id: ?[]const u8, | |
| 1064 | expected_crc: ?u32, | |
| 1065 | parent_sections: *Dwarf.SectionArray, | |
| 1066 | parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8, | |
| 1067 | ) !Dwarf.ElfModule { | |
| 1068 | nosuspend { | |
| 1069 | const elf_file = (if (elf_filename) |filename| blk: { | |
| 1070 | break :blk fs.cwd().openFile(filename, .{}); | |
| 1071 | } else fs.openSelfExe(.{})) catch |err| switch (err) { | |
| 1072 | error.FileNotFound => return error.MissingDebugInfo, | |
| 1073 | else => return err, | |
| 1074 | }; | |
| 1075 | ||
| 1076 | const mapped_mem = try mapWholeFile(elf_file); | |
| 1077 | return Dwarf.ElfModule.load( | |
| 1078 | allocator, | |
| 1079 | mapped_mem, | |
| 1080 | build_id, | |
| 1081 | expected_crc, | |
| 1082 | parent_sections, | |
| 1083 | parent_mapped_mem, | |
| 1084 | elf_filename, | |
| 1085 | ); | |
| 1086 | } | |
| 1087 | } | |
| 1088 | ||
| 1089 | const MachoSymbol = struct { | |
| 1090 | strx: u32, | |
| 1091 | addr: u64, | |
| 1092 | size: u32, | |
| 1093 | ofile: u32, | |
| 1094 | ||
| 1095 | /// Returns the address from the macho file | |
| 1096 | fn address(self: MachoSymbol) u64 { | |
| 1097 | return self.addr; | |
| 1098 | } | |
| 1099 | ||
| 1100 | fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool { | |
| 1101 | _ = context; | |
| 1102 | return lhs.addr < rhs.addr; | |
| 1103 | } | |
| 1104 | }; | |
| 1105 | ||
| 1106 | /// Takes ownership of file, even on error. | |
| 1107 | /// TODO it's weird to take ownership even on error, rework this code. | |
| 1108 | fn mapWholeFile(file: File) ![]align(std.heap.page_size_min) const u8 { | |
| 1109 | nosuspend { | |
| 1110 | defer file.close(); | |
| 1111 | ||
| 1112 | const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize); | |
| 1113 | const mapped_mem = try posix.mmap( | |
| 1114 | null, | |
| 1115 | file_len, | |
| 1116 | posix.PROT.READ, | |
| 1117 | .{ .TYPE = .SHARED }, | |
| 1118 | file.handle, | |
| 1119 | 0, | |
| 1120 | ); | |
| 1121 | errdefer posix.munmap(mapped_mem); | |
| 1122 | ||
| 1123 | return mapped_mem; | |
| 1124 | } | |
| 1125 | } | |
| 1126 | ||
| 1127 | fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { | |
| 1128 | var min: usize = 0; | |
| 1129 | var max: usize = symbols.len - 1; | |
| 1130 | while (min < max) { | |
| 1131 | const mid = min + (max - min) / 2; | |
| 1132 | const curr = &symbols[mid]; | |
| 1133 | const next = &symbols[mid + 1]; | |
| 1134 | if (address >= next.address()) { | |
| 1135 | min = mid + 1; | |
| 1136 | } else if (address < curr.address()) { | |
| 1137 | max = mid; | |
| 1138 | } else { | |
| 1139 | return curr; | |
| 1140 | } | |
| 1141 | } | |
| 1142 | ||
| 1143 | const max_sym = &symbols[symbols.len - 1]; | |
| 1144 | if (address >= max_sym.address()) | |
| 1145 | return max_sym; | |
| 1146 | ||
| 1147 | return null; | |
| 1148 | } | |
| 1149 | ||
| 1150 | test machoSearchSymbols { | |
| 1151 | const symbols = [_]MachoSymbol{ | |
| 1152 | .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined }, | |
| 1153 | .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined }, | |
| 1154 | .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined }, | |
| 1155 | }; | |
| 1156 | ||
| 1157 | try testing.expectEqual(null, machoSearchSymbols(&symbols, 0)); | |
| 1158 | try testing.expectEqual(null, machoSearchSymbols(&symbols, 99)); | |
| 1159 | try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?); | |
| 1160 | try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?); | |
| 1161 | try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?); | |
| 1162 | ||
| 1163 | try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?); | |
| 1164 | try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?); | |
| 1165 | try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?); | |
| 1166 | ||
| 1167 | try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?); | |
| 1168 | try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?); | |
| 1169 | try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?); | |
| 1170 | } | |
| 1171 | ||
| 1172 | /// Unwind a frame using MachO compact unwind info (from __unwind_info). | |
| 1173 | /// If the compact encoding can't encode a way to unwind a frame, it will | |
| 1174 | /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. | |
| 1175 | pub fn unwindFrameMachO( | |
| 1176 | allocator: Allocator, | |
| 1177 | base_address: usize, | |
| 1178 | context: *UnwindContext, | |
| 1179 | unwind_info: []const u8, | |
| 1180 | eh_frame: ?[]const u8, | |
| 1181 | ) !usize { | |
| 1182 | const header = std.mem.bytesAsValue( | |
| 1183 | macho.unwind_info_section_header, | |
| 1184 | unwind_info[0..@sizeOf(macho.unwind_info_section_header)], | |
| 1185 | ); | |
| 1186 | const indices = std.mem.bytesAsSlice( | |
| 1187 | macho.unwind_info_section_header_index_entry, | |
| 1188 | unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], | |
| 1189 | ); | |
| 1190 | if (indices.len == 0) return error.MissingUnwindInfo; | |
| 1191 | ||
| 1192 | const mapped_pc = context.pc - base_address; | |
| 1193 | const second_level_index = blk: { | |
| 1194 | var left: usize = 0; | |
| 1195 | var len: usize = indices.len; | |
| 1196 | ||
| 1197 | while (len > 1) { | |
| 1198 | const mid = left + len / 2; | |
| 1199 | const offset = indices[mid].functionOffset; | |
| 1200 | if (mapped_pc < offset) { | |
| 1201 | len /= 2; | |
| 1202 | } else { | |
| 1203 | left = mid; | |
| 1204 | if (mapped_pc == offset) break; | |
| 1205 | len -= len / 2; | |
| 1206 | } | |
| 1207 | } | |
| 1208 | ||
| 1209 | // Last index is a sentinel containing the highest address as its functionOffset | |
| 1210 | if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; | |
| 1211 | break :blk &indices[left]; | |
| 1212 | }; | |
| 1213 | ||
| 1214 | const common_encodings = std.mem.bytesAsSlice( | |
| 1215 | macho.compact_unwind_encoding_t, | |
| 1216 | unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], | |
| 1217 | ); | |
| 1218 | ||
| 1219 | const start_offset = second_level_index.secondLevelPagesSectionOffset; | |
| 1220 | const kind = std.mem.bytesAsValue( | |
| 1221 | macho.UNWIND_SECOND_LEVEL, | |
| 1222 | unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], | |
| 1223 | ); | |
| 1224 | ||
| 1225 | const entry: struct { | |
| 1226 | function_offset: usize, | |
| 1227 | raw_encoding: u32, | |
| 1228 | } = switch (kind.*) { | |
| 1229 | .REGULAR => blk: { | |
| 1230 | const page_header = std.mem.bytesAsValue( | |
| 1231 | macho.unwind_info_regular_second_level_page_header, | |
| 1232 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], | |
| 1233 | ); | |
| 1234 | ||
| 1235 | const entries = std.mem.bytesAsSlice( | |
| 1236 | macho.unwind_info_regular_second_level_entry, | |
| 1237 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], | |
| 1238 | ); | |
| 1239 | if (entries.len == 0) return error.InvalidUnwindInfo; | |
| 1240 | ||
| 1241 | var left: usize = 0; | |
| 1242 | var len: usize = entries.len; | |
| 1243 | while (len > 1) { | |
| 1244 | const mid = left + len / 2; | |
| 1245 | const offset = entries[mid].functionOffset; | |
| 1246 | if (mapped_pc < offset) { | |
| 1247 | len /= 2; | |
| 1248 | } else { | |
| 1249 | left = mid; | |
| 1250 | if (mapped_pc == offset) break; | |
| 1251 | len -= len / 2; | |
| 1252 | } | |
| 1253 | } | |
| 1254 | ||
| 1255 | break :blk .{ | |
| 1256 | .function_offset = entries[left].functionOffset, | |
| 1257 | .raw_encoding = entries[left].encoding, | |
| 1258 | }; | |
| 1259 | }, | |
| 1260 | .COMPRESSED => blk: { | |
| 1261 | const page_header = std.mem.bytesAsValue( | |
| 1262 | macho.unwind_info_compressed_second_level_page_header, | |
| 1263 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], | |
| 1264 | ); | |
| 1265 | ||
| 1266 | const entries = std.mem.bytesAsSlice( | |
| 1267 | macho.UnwindInfoCompressedEntry, | |
| 1268 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], | |
| 1269 | ); | |
| 1270 | if (entries.len == 0) return error.InvalidUnwindInfo; | |
| 1271 | ||
| 1272 | var left: usize = 0; | |
| 1273 | var len: usize = entries.len; | |
| 1274 | while (len > 1) { | |
| 1275 | const mid = left + len / 2; | |
| 1276 | const offset = second_level_index.functionOffset + entries[mid].funcOffset; | |
| 1277 | if (mapped_pc < offset) { | |
| 1278 | len /= 2; | |
| 1279 | } else { | |
| 1280 | left = mid; | |
| 1281 | if (mapped_pc == offset) break; | |
| 1282 | len -= len / 2; | |
| 1283 | } | |
| 1284 | } | |
| 1285 | ||
| 1286 | const entry = entries[left]; | |
| 1287 | const function_offset = second_level_index.functionOffset + entry.funcOffset; | |
| 1288 | if (entry.encodingIndex < header.commonEncodingsArrayCount) { | |
| 1289 | if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; | |
| 1290 | break :blk .{ | |
| 1291 | .function_offset = function_offset, | |
| 1292 | .raw_encoding = common_encodings[entry.encodingIndex], | |
| 1293 | }; | |
| 1294 | } else { | |
| 1295 | const local_index = try math.sub( | |
| 1296 | u8, | |
| 1297 | entry.encodingIndex, | |
| 1298 | math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, | |
| 1299 | ); | |
| 1300 | const local_encodings = std.mem.bytesAsSlice( | |
| 1301 | macho.compact_unwind_encoding_t, | |
| 1302 | unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], | |
| 1303 | ); | |
| 1304 | if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; | |
| 1305 | break :blk .{ | |
| 1306 | .function_offset = function_offset, | |
| 1307 | .raw_encoding = local_encodings[local_index], | |
| 1308 | }; | |
| 1309 | } | |
| 1310 | }, | |
| 1311 | else => return error.InvalidUnwindInfo, | |
| 1312 | }; | |
| 1313 | ||
| 1314 | if (entry.raw_encoding == 0) return error.NoUnwindInfo; | |
| 1315 | const reg_context = Dwarf.abi.RegisterContext{ | |
| 1316 | .eh_frame = false, | |
| 1317 | .is_macho = true, | |
| 1318 | }; | |
| 1319 | ||
| 1320 | const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); | |
| 1321 | const new_ip = switch (builtin.cpu.arch) { | |
| 1322 | .x86_64 => switch (encoding.mode.x86_64) { | |
| 1323 | .OLD => return error.UnimplementedUnwindEncoding, | |
| 1324 | .RBP_FRAME => blk: { | |
| 1325 | const regs: [5]u3 = .{ | |
| 1326 | encoding.value.x86_64.frame.reg0, | |
| 1327 | encoding.value.x86_64.frame.reg1, | |
| 1328 | encoding.value.x86_64.frame.reg2, | |
| 1329 | encoding.value.x86_64.frame.reg3, | |
| 1330 | encoding.value.x86_64.frame.reg4, | |
| 1331 | }; | |
| 1332 | ||
| 1333 | const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); | |
| 1334 | var max_reg: usize = 0; | |
| 1335 | inline for (regs, 0..) |reg, i| { | |
| 1336 | if (reg > 0) max_reg = i; | |
| 1337 | } | |
| 1338 | ||
| 1339 | const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*; | |
| 1340 | const new_sp = fp + 2 * @sizeOf(usize); | |
| 1341 | ||
| 1342 | const ip_ptr = fp + @sizeOf(usize); | |
| 1343 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 1344 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | |
| 1345 | ||
| 1346 | (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp; | |
| 1347 | (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp; | |
| 1348 | (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; | |
| 1349 | ||
| 1350 | for (regs, 0..) |reg, i| { | |
| 1351 | if (reg == 0) continue; | |
| 1352 | const addr = fp - frame_offset + i * @sizeOf(usize); | |
| 1353 | const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg); | |
| 1354 | (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; | |
| 1355 | } | |
| 1356 | ||
| 1357 | break :blk new_ip; | |
| 1358 | }, | |
| 1359 | .STACK_IMMD, | |
| 1360 | .STACK_IND, | |
| 1361 | => blk: { | |
| 1362 | const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*; | |
| 1363 | const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) | |
| 1364 | @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) | |
| 1365 | else stack_size: { | |
| 1366 | // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. | |
| 1367 | const sub_offset_addr = | |
| 1368 | base_address + | |
| 1369 | entry.function_offset + | |
| 1370 | encoding.value.x86_64.frameless.stack.indirect.sub_offset; | |
| 1371 | ||
| 1372 | // `sub_offset_addr` points to the offset of the literal within the instruction | |
| 1373 | const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; | |
| 1374 | break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); | |
| 1375 | }; | |
| 1376 | ||
| 1377 | // Decode the Lehmer-coded sequence of registers. | |
| 1378 | // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h | |
| 1379 | ||
| 1380 | // Decode the variable-based permutation number into its digits. Each digit represents | |
| 1381 | // an index into the list of register numbers that weren't yet used in the sequence at | |
| 1382 | // the time the digit was added. | |
| 1383 | const reg_count = encoding.value.x86_64.frameless.stack_reg_count; | |
| 1384 | const ip_ptr = if (reg_count > 0) reg_blk: { | |
| 1385 | var digits: [6]u3 = undefined; | |
| 1386 | var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; | |
| 1387 | var base: usize = 2; | |
| 1388 | for (0..reg_count) |i| { | |
| 1389 | const div = accumulator / base; | |
| 1390 | digits[digits.len - 1 - i] = @intCast(accumulator - base * div); | |
| 1391 | accumulator = div; | |
| 1392 | base += 1; | |
| 1393 | } | |
| 1394 | ||
| 1395 | const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; | |
| 1396 | var registers: [reg_numbers.len]u3 = undefined; | |
| 1397 | var used_indices = [_]bool{false} ** reg_numbers.len; | |
| 1398 | for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { | |
| 1399 | var unused_count: u8 = 0; | |
| 1400 | const unused_index = for (used_indices, 0..) |used, index| { | |
| 1401 | if (!used) { | |
| 1402 | if (target_unused_index == unused_count) break index; | |
| 1403 | unused_count += 1; | |
| 1404 | } | |
| 1405 | } else unreachable; | |
| 1406 | ||
| 1407 | registers[i] = reg_numbers[unused_index]; | |
| 1408 | used_indices[unused_index] = true; | |
| 1409 | } | |
| 1410 | ||
| 1411 | var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); | |
| 1412 | for (0..reg_count) |i| { | |
| 1413 | const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]); | |
| 1414 | (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 1415 | reg_addr += @sizeOf(usize); | |
| 1416 | } | |
| 1417 | ||
| 1418 | break :reg_blk reg_addr; | |
| 1419 | } else sp + stack_size - @sizeOf(usize); | |
| 1420 | ||
| 1421 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 1422 | const new_sp = ip_ptr + @sizeOf(usize); | |
| 1423 | ||
| 1424 | (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp; | |
| 1425 | (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; | |
| 1426 | ||
| 1427 | break :blk new_ip; | |
| 1428 | }, | |
| 1429 | .DWARF => { | |
| 1430 | return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); | |
| 1431 | }, | |
| 1432 | }, | |
| 1433 | .aarch64, .aarch64_be => switch (encoding.mode.arm64) { | |
| 1434 | .OLD => return error.UnimplementedUnwindEncoding, | |
| 1435 | .FRAMELESS => blk: { | |
| 1436 | const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*; | |
| 1437 | const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; | |
| 1438 | const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*; | |
| 1439 | (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp; | |
| 1440 | break :blk new_ip; | |
| 1441 | }, | |
| 1442 | .DWARF => { | |
| 1443 | return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); | |
| 1444 | }, | |
| 1445 | .FRAME => blk: { | |
| 1446 | const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*; | |
| 1447 | const ip_ptr = fp + @sizeOf(usize); | |
| 1448 | ||
| 1449 | var reg_addr = fp - @sizeOf(usize); | |
| 1450 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| { | |
| 1451 | if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { | |
| 1452 | (try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 1453 | reg_addr += @sizeOf(usize); | |
| 1454 | (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 1455 | reg_addr += @sizeOf(usize); | |
| 1456 | } | |
| 1457 | } | |
| 1458 | ||
| 1459 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| { | |
| 1460 | if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { | |
| 1461 | // Only the lower half of the 128-bit V registers are restored during unwinding | |
| 1462 | @memcpy( | |
| 1463 | try regBytes(context.thread_context, 64 + 8 + i, context.reg_context), | |
| 1464 | std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | |
| 1465 | ); | |
| 1466 | reg_addr += @sizeOf(usize); | |
| 1467 | @memcpy( | |
| 1468 | try regBytes(context.thread_context, 64 + 9 + i, context.reg_context), | |
| 1469 | std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | |
| 1470 | ); | |
| 1471 | reg_addr += @sizeOf(usize); | |
| 1472 | } | |
| 1473 | } | |
| 1474 | ||
| 1475 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 1476 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | |
| 1477 | ||
| 1478 | (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp; | |
| 1479 | (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; | |
| 1480 | ||
| 1481 | break :blk new_ip; | |
| 1482 | }, | |
| 1483 | }, | |
| 1484 | else => return error.UnimplementedArch, | |
| 1485 | }; | |
| 1486 | ||
| 1487 | context.pc = stripInstructionPtrAuthCode(new_ip); | |
| 1488 | if (context.pc > 0) context.pc -= 1; | |
| 1489 | return new_ip; | |
| 1490 | } | |
| 1491 | ||
| 1492 | pub const UnwindContext = struct { | |
| 1493 | allocator: Allocator, | |
| 1494 | cfa: ?usize, | |
| 1495 | pc: usize, | |
| 1496 | thread_context: *std.debug.ThreadContext, | |
| 1497 | reg_context: Dwarf.abi.RegisterContext, | |
| 1498 | vm: VirtualMachine, | |
| 1499 | stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), | |
| 1500 | ||
| 1501 | pub fn init( | |
| 1502 | allocator: Allocator, | |
| 1503 | thread_context: *std.debug.ThreadContext, | |
| 1504 | ) !UnwindContext { | |
| 1505 | comptime assert(supports_unwinding); | |
| 1506 | ||
| 1507 | const pc = stripInstructionPtrAuthCode( | |
| 1508 | (try regValueNative(thread_context, ip_reg_num, null)).*, | |
| 1509 | ); | |
| 1510 | ||
| 1511 | const context_copy = try allocator.create(std.debug.ThreadContext); | |
| 1512 | std.debug.copyContext(thread_context, context_copy); | |
| 1513 | ||
| 1514 | return .{ | |
| 1515 | .allocator = allocator, | |
| 1516 | .cfa = null, | |
| 1517 | .pc = pc, | |
| 1518 | .thread_context = context_copy, | |
| 1519 | .reg_context = undefined, | |
| 1520 | .vm = .{}, | |
| 1521 | .stack_machine = .{}, | |
| 1522 | }; | |
| 1523 | } | |
| 1524 | ||
| 1525 | pub fn deinit(self: *UnwindContext) void { | |
| 1526 | self.vm.deinit(self.allocator); | |
| 1527 | self.stack_machine.deinit(self.allocator); | |
| 1528 | self.allocator.destroy(self.thread_context); | |
| 1529 | self.* = undefined; | |
| 1530 | } | |
| 1531 | ||
| 1532 | pub fn getFp(self: *const UnwindContext) !usize { | |
| 1533 | return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*; | |
| 1534 | } | |
| 1535 | }; | |
| 1536 | ||
| 1537 | /// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature. | |
| 1538 | /// This function clears these signature bits to make the pointer usable. | |
| 1539 | pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { | |
| 1540 | if (native_arch.isAARCH64()) { | |
| 1541 | // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it) | |
| 1542 | // The save / restore is because `xpaclri` operates on x30 (LR) | |
| 1543 | return asm ( | |
| 1544 | \\mov x16, x30 | |
| 1545 | \\mov x30, x15 | |
| 1546 | \\hint 0x07 | |
| 1547 | \\mov x15, x30 | |
| 1548 | \\mov x30, x16 | |
| 1549 | : [ret] "={x15}" (-> usize), | |
| 1550 | : [ptr] "{x15}" (ptr), | |
| 1551 | : .{ .x16 = true }); | |
| 1552 | } | |
| 1553 | ||
| 1554 | return ptr; | |
| 1555 | } | |
| 1556 | ||
| 1557 | /// Unwind a stack frame using DWARF unwinding info, updating the register context. | |
| 1558 | /// | |
| 1559 | /// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE. | |
| 1560 | /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter | |
| 1561 | /// may require lazily loading the data in those sections. | |
| 1562 | /// | |
| 1563 | /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info | |
| 1564 | /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section. | |
| 1565 | pub fn unwindFrameDwarf( | |
| 1566 | allocator: Allocator, | |
| 1567 | di: *Dwarf, | |
| 1568 | base_address: usize, | |
| 1569 | context: *UnwindContext, | |
| 1570 | explicit_fde_offset: ?usize, | |
| 1571 | ) !usize { | |
| 1572 | if (!supports_unwinding) return error.UnsupportedCpuArchitecture; | |
| 1573 | if (context.pc == 0) return 0; | |
| 1574 | ||
| 1575 | const endian = di.endian; | |
| 1576 | ||
| 1577 | // Find the FDE and CIE | |
| 1578 | const cie, const fde = if (explicit_fde_offset) |fde_offset| blk: { | |
| 1579 | const dwarf_section: Dwarf.Section.Id = .eh_frame; | |
| 1580 | const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; | |
| 1581 | if (fde_offset >= frame_section.len) return error.MissingFDE; | |
| 1582 | ||
| 1583 | var fbr: std.Io.Reader = .fixed(frame_section); | |
| 1584 | fbr.seek = fde_offset; | |
| 1585 | ||
| 1586 | const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section, endian); | |
| 1587 | if (fde_entry_header.type != .fde) return error.MissingFDE; | |
| 1588 | ||
| 1589 | const cie_offset = fde_entry_header.type.fde; | |
| 1590 | fbr.seek = @intCast(cie_offset); | |
| 1591 | ||
| 1592 | const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section, endian); | |
| 1593 | if (cie_entry_header.type != .cie) return Dwarf.bad(); | |
| 1594 | ||
| 1595 | const cie = try Dwarf.CommonInformationEntry.parse( | |
| 1596 | cie_entry_header.entry_bytes, | |
| 1597 | 0, | |
| 1598 | true, | |
| 1599 | cie_entry_header.format, | |
| 1600 | dwarf_section, | |
| 1601 | cie_entry_header.length_offset, | |
| 1602 | @sizeOf(usize), | |
| 1603 | native_endian, | |
| 1604 | ); | |
| 1605 | const fde = try Dwarf.FrameDescriptionEntry.parse( | |
| 1606 | fde_entry_header.entry_bytes, | |
| 1607 | 0, | |
| 1608 | true, | |
| 1609 | cie, | |
| 1610 | @sizeOf(usize), | |
| 1611 | native_endian, | |
| 1612 | ); | |
| 1613 | ||
| 1614 | break :blk .{ cie, fde }; | |
| 1615 | } else blk: { | |
| 1616 | // `.eh_frame_hdr` may be incomplete. We'll try it first, but if the lookup fails, we fall | |
| 1617 | // back to loading `.eh_frame`/`.debug_frame` and using those from that point on. | |
| 1618 | ||
| 1619 | if (di.eh_frame_hdr) |header| hdr: { | |
| 1620 | const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else { | |
| 1621 | try di.scanCieFdeInfo(allocator, base_address); | |
| 1622 | di.eh_frame_hdr = null; | |
| 1623 | break :hdr; | |
| 1624 | }; | |
| 1625 | ||
| 1626 | var cie: Dwarf.CommonInformationEntry = undefined; | |
| 1627 | var fde: Dwarf.FrameDescriptionEntry = undefined; | |
| 1628 | ||
| 1629 | header.findEntry( | |
| 1630 | eh_frame_len, | |
| 1631 | @intFromPtr(di.section(.eh_frame_hdr).?.ptr), | |
| 1632 | context.pc, | |
| 1633 | &cie, | |
| 1634 | &fde, | |
| 1635 | endian, | |
| 1636 | ) catch |err| switch (err) { | |
| 1637 | error.MissingDebugInfo => { | |
| 1638 | // `.eh_frame_hdr` appears to be incomplete, so go ahead and populate `cie_map` | |
| 1639 | // and `fde_list`, and fall back to the binary search logic below. | |
| 1640 | try di.scanCieFdeInfo(allocator, base_address); | |
| 1641 | ||
| 1642 | // Since `.eh_frame_hdr` is incomplete, we're very likely to get more lookup | |
| 1643 | // failures using it, and we've just built a complete, sorted list of FDEs | |
| 1644 | // anyway, so just stop using `.eh_frame_hdr` altogether. | |
| 1645 | di.eh_frame_hdr = null; | |
| 1646 | ||
| 1647 | break :hdr; | |
| 1648 | }, | |
| 1649 | else => return err, | |
| 1650 | }; | |
| 1651 | ||
| 1652 | break :blk .{ cie, fde }; | |
| 1653 | } | |
| 1654 | ||
| 1655 | const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, di.fde_list.items, context.pc, struct { | |
| 1656 | pub fn compareFn(pc: usize, item: Dwarf.FrameDescriptionEntry) std.math.Order { | |
| 1657 | if (pc < item.pc_begin) return .lt; | |
| 1658 | ||
| 1659 | const range_end = item.pc_begin + item.pc_range; | |
| 1660 | if (pc < range_end) return .eq; | |
| 1661 | ||
| 1662 | return .gt; | |
| 1663 | } | |
| 1664 | }.compareFn); | |
| 1665 | ||
| 1666 | const fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE; | |
| 1667 | const cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; | |
| 1668 | ||
| 1669 | break :blk .{ cie, fde }; | |
| 1670 | }; | |
| 1671 | ||
| 1672 | var expression_context: Dwarf.expression.Context = .{ | |
| 1673 | .format = cie.format, | |
| 1674 | .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, | |
| 1675 | .thread_context = context.thread_context, | |
| 1676 | .reg_context = context.reg_context, | |
| 1677 | .cfa = context.cfa, | |
| 1678 | }; | |
| 1679 | ||
| 1680 | context.vm.reset(); | |
| 1681 | context.reg_context.eh_frame = cie.version != 4; | |
| 1682 | context.reg_context.is_macho = di.is_macho; | |
| 1683 | ||
| 1684 | const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); | |
| 1685 | context.cfa = switch (row.cfa.rule) { | |
| 1686 | .val_offset => |offset| blk: { | |
| 1687 | const register = row.cfa.register orelse return error.InvalidCFARule; | |
| 1688 | const value = mem.readInt(usize, (try regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian); | |
| 1689 | break :blk try applyOffset(value, offset); | |
| 1690 | }, | |
| 1691 | .expression => |expr| blk: { | |
| 1692 | context.stack_machine.reset(); | |
| 1693 | const value = try context.stack_machine.run( | |
| 1694 | expr, | |
| 1695 | context.allocator, | |
| 1696 | expression_context, | |
| 1697 | context.cfa, | |
| 1698 | ); | |
| 1699 | ||
| 1700 | if (value) |v| { | |
| 1701 | if (v != .generic) return error.InvalidExpressionValue; | |
| 1702 | break :blk v.generic; | |
| 1703 | } else return error.NoExpressionValue; | |
| 1704 | }, | |
| 1705 | else => return error.InvalidCFARule, | |
| 1706 | }; | |
| 1707 | ||
| 1708 | expression_context.cfa = context.cfa; | |
| 1709 | ||
| 1710 | // Buffering the modifications is done because copying the thread context is not portable, | |
| 1711 | // some implementations (ie. darwin) use internal pointers to the mcontext. | |
| 1712 | var arena = std.heap.ArenaAllocator.init(context.allocator); | |
| 1713 | defer arena.deinit(); | |
| 1714 | const update_allocator = arena.allocator(); | |
| 1715 | ||
| 1716 | const RegisterUpdate = struct { | |
| 1717 | // Backed by thread_context | |
| 1718 | dest: []u8, | |
| 1719 | // Backed by arena | |
| 1720 | src: []const u8, | |
| 1721 | prev: ?*@This(), | |
| 1722 | }; | |
| 1723 | ||
| 1724 | var update_tail: ?*RegisterUpdate = null; | |
| 1725 | var has_return_address = true; | |
| 1726 | for (context.vm.rowColumns(row)) |column| { | |
| 1727 | if (column.register) |register| { | |
| 1728 | if (register == cie.return_address_register) { | |
| 1729 | has_return_address = column.rule != .undefined; | |
| 1730 | } | |
| 1731 | ||
| 1732 | const dest = try regBytes(context.thread_context, register, context.reg_context); | |
| 1733 | const src = try update_allocator.alloc(u8, dest.len); | |
| 1734 | ||
| 1735 | const prev = update_tail; | |
| 1736 | update_tail = try update_allocator.create(RegisterUpdate); | |
| 1737 | update_tail.?.* = .{ | |
| 1738 | .dest = dest, | |
| 1739 | .src = src, | |
| 1740 | .prev = prev, | |
| 1741 | }; | |
| 1742 | ||
| 1743 | try column.resolveValue(context, expression_context, src); | |
| 1744 | } | |
| 1745 | } | |
| 1746 | ||
| 1747 | // On all implemented architectures, the CFA is defined as being the previous frame's SP | |
| 1748 | (try regValueNative(context.thread_context, spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?; | |
| 1749 | ||
| 1750 | while (update_tail) |tail| { | |
| 1751 | @memcpy(tail.dest, tail.src); | |
| 1752 | update_tail = tail.prev; | |
| 1753 | } | |
| 1754 | ||
| 1755 | if (has_return_address) { | |
| 1756 | context.pc = stripInstructionPtrAuthCode(mem.readInt(usize, (try regBytes( | |
| 1757 | context.thread_context, | |
| 1758 | cie.return_address_register, | |
| 1759 | context.reg_context, | |
| 1760 | ))[0..@sizeOf(usize)], native_endian)); | |
| 1761 | } else { | |
| 1762 | context.pc = 0; | |
| 1763 | } | |
| 1764 | ||
| 1765 | (try regValueNative(context.thread_context, ip_reg_num, context.reg_context)).* = context.pc; | |
| 1766 | ||
| 1767 | // The call instruction will have pushed the address of the instruction that follows the call as the return address. | |
| 1768 | // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in | |
| 1769 | // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up | |
| 1770 | // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this, | |
| 1771 | // we subtract one so that the next lookup is guaranteed to land inside the | |
| 1772 | // | |
| 1773 | // The exception to this rule is signal frames, where we return execution would be returned to the instruction | |
| 1774 | // that triggered the handler. | |
| 1775 | const return_address = context.pc; | |
| 1776 | if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; | |
| 1777 | ||
| 1778 | return return_address; | |
| 1779 | } | |
| 1780 | ||
| 1781 | fn fpRegNum(reg_context: Dwarf.abi.RegisterContext) u8 { | |
| 1782 | return Dwarf.abi.fpRegNum(native_arch, reg_context); | |
| 1783 | } | |
| 1784 | ||
| 1785 | fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 { | |
| 1786 | return Dwarf.abi.spRegNum(native_arch, reg_context); | |
| 1787 | } | |
| 1788 | ||
| 1789 | const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?; | |
| 1790 | ||
| 1791 | /// Tells whether unwinding for the host is implemented. | |
| 1792 | pub const supports_unwinding = supportsUnwinding(&builtin.target); | |
| 1793 | ||
| 1794 | comptime { | |
| 1795 | if (supports_unwinding) assert(Dwarf.abi.supportsUnwinding(&builtin.target)); | |
| 1796 | } | |
| 1797 | ||
| 1798 | /// Tells whether unwinding for this target is *implemented* here in the Zig | |
| 1799 | /// standard library. | |
| 1800 | /// | |
| 1801 | /// See also `Dwarf.abi.supportsUnwinding` which tells whether Dwarf supports | |
| 1802 | /// unwinding on that target *in theory*. | |
| 1803 | pub fn supportsUnwinding(target: *const std.Target) bool { | |
| 1804 | return switch (target.cpu.arch) { | |
| 1805 | .x86 => switch (target.os.tag) { | |
| 1806 | .linux, .netbsd, .solaris, .illumos => true, | |
| 1807 | else => false, | |
| 1808 | }, | |
| 1809 | .x86_64 => switch (target.os.tag) { | |
| 1810 | .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true, | |
| 1811 | else => false, | |
| 1812 | }, | |
| 1813 | .arm, .armeb, .thumb, .thumbeb => switch (target.os.tag) { | |
| 1814 | .linux => true, | |
| 1815 | else => false, | |
| 1816 | }, | |
| 1817 | .aarch64, .aarch64_be => switch (target.os.tag) { | |
| 1818 | .linux, .netbsd, .freebsd, .macos, .ios => true, | |
| 1819 | else => false, | |
| 1820 | }, | |
| 1821 | // Unwinding is possible on other targets but this implementation does | |
| 1822 | // not support them...yet! | |
| 1823 | else => false, | |
| 1824 | }; | |
| 1825 | } | |
| 1826 | ||
| 1827 | fn unwindFrameMachODwarf( | |
| 1828 | allocator: Allocator, | |
| 1829 | base_address: usize, | |
| 1830 | context: *UnwindContext, | |
| 1831 | eh_frame: []const u8, | |
| 1832 | fde_offset: usize, | |
| 1833 | ) !usize { | |
| 1834 | var di: Dwarf = .{ | |
| 1835 | .endian = native_endian, | |
| 1836 | .is_macho = true, | |
| 1837 | }; | |
| 1838 | defer di.deinit(context.allocator); | |
| 1839 | ||
| 1840 | di.sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ | |
| 1841 | .data = eh_frame, | |
| 1842 | .owned = false, | |
| 1843 | }; | |
| 1844 | ||
| 1845 | return unwindFrameDwarf(allocator, &di, base_address, context, fde_offset); | |
| 1846 | } | |
| 1847 | ||
| 1848 | /// This is a virtual machine that runs DWARF call frame instructions. | |
| 1849 | pub const VirtualMachine = struct { | |
| 1850 | /// See section 6.4.1 of the DWARF5 specification for details on each | |
| 1851 | const RegisterRule = union(enum) { | |
| 1852 | // The spec says that the default rule for each column is the undefined rule. | |
| 1853 | // However, it also allows ABI / compiler authors to specify alternate defaults, so | |
| 1854 | // there is a distinction made here. | |
| 1855 | default: void, | |
| 1856 | undefined: void, | |
| 1857 | same_value: void, | |
| 1858 | // offset(N) | |
| 1859 | offset: i64, | |
| 1860 | // val_offset(N) | |
| 1861 | val_offset: i64, | |
| 1862 | // register(R) | |
| 1863 | register: u8, | |
| 1864 | // expression(E) | |
| 1865 | expression: []const u8, | |
| 1866 | // val_expression(E) | |
| 1867 | val_expression: []const u8, | |
| 1868 | // Augmenter-defined rule | |
| 1869 | architectural: void, | |
| 1870 | }; | |
| 1871 | ||
| 1872 | /// Each row contains unwinding rules for a set of registers. | |
| 1873 | pub const Row = struct { | |
| 1874 | /// Offset from `FrameDescriptionEntry.pc_begin` | |
| 1875 | offset: u64 = 0, | |
| 1876 | /// Special-case column that defines the CFA (Canonical Frame Address) rule. | |
| 1877 | /// The register field of this column defines the register that CFA is derived from. | |
| 1878 | cfa: Column = .{}, | |
| 1879 | /// The register fields in these columns define the register the rule applies to. | |
| 1880 | columns: ColumnRange = .{}, | |
| 1881 | /// Indicates that the next write to any column in this row needs to copy | |
| 1882 | /// the backing column storage first, as it may be referenced by previous rows. | |
| 1883 | copy_on_write: bool = false, | |
| 1884 | }; | |
| 1885 | ||
| 1886 | pub const Column = struct { | |
| 1887 | register: ?u8 = null, | |
| 1888 | rule: RegisterRule = .{ .default = {} }, | |
| 1889 | ||
| 1890 | /// Resolves the register rule and places the result into `out` (see regBytes) | |
| 1891 | pub fn resolveValue( | |
| 1892 | self: Column, | |
| 1893 | context: *SelfInfo.UnwindContext, | |
| 1894 | expression_context: std.debug.Dwarf.expression.Context, | |
| 1895 | out: []u8, | |
| 1896 | ) !void { | |
| 1897 | switch (self.rule) { | |
| 1898 | .default => { | |
| 1899 | const register = self.register orelse return error.InvalidRegister; | |
| 1900 | try getRegDefaultValue(register, context, out); | |
| 1901 | }, | |
| 1902 | .undefined => { | |
| 1903 | @memset(out, undefined); | |
| 1904 | }, | |
| 1905 | .same_value => { | |
| 1906 | // TODO: This copy could be eliminated if callers always copy the state then call this function to update it | |
| 1907 | const register = self.register orelse return error.InvalidRegister; | |
| 1908 | const src = try regBytes(context.thread_context, register, context.reg_context); | |
| 1909 | if (src.len != out.len) return error.RegisterSizeMismatch; | |
| 1910 | @memcpy(out, src); | |
| 1911 | }, | |
| 1912 | .offset => |offset| { | |
| 1913 | if (context.cfa) |cfa| { | |
| 1914 | const addr = try applyOffset(cfa, offset); | |
| 1915 | const ptr: *const usize = @ptrFromInt(addr); | |
| 1916 | mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); | |
| 1917 | } else return error.InvalidCFA; | |
| 1918 | }, | |
| 1919 | .val_offset => |offset| { | |
| 1920 | if (context.cfa) |cfa| { | |
| 1921 | mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian); | |
| 1922 | } else return error.InvalidCFA; | |
| 1923 | }, | |
| 1924 | .register => |register| { | |
| 1925 | const src = try regBytes(context.thread_context, register, context.reg_context); | |
| 1926 | if (src.len != out.len) return error.RegisterSizeMismatch; | |
| 1927 | @memcpy(out, try regBytes(context.thread_context, register, context.reg_context)); | |
| 1928 | }, | |
| 1929 | .expression => |expression| { | |
| 1930 | context.stack_machine.reset(); | |
| 1931 | const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); | |
| 1932 | const addr = if (value) |v| blk: { | |
| 1933 | if (v != .generic) return error.InvalidExpressionValue; | |
| 1934 | break :blk v.generic; | |
| 1935 | } else return error.NoExpressionValue; | |
| 1936 | ||
| 1937 | const ptr: *usize = @ptrFromInt(addr); | |
| 1938 | mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); | |
| 1939 | }, | |
| 1940 | .val_expression => |expression| { | |
| 1941 | context.stack_machine.reset(); | |
| 1942 | const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); | |
| 1943 | if (value) |v| { | |
| 1944 | if (v != .generic) return error.InvalidExpressionValue; | |
| 1945 | mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian); | |
| 1946 | } else return error.NoExpressionValue; | |
| 1947 | }, | |
| 1948 | .architectural => return error.UnimplementedRegisterRule, | |
| 1949 | } | |
| 1950 | } | |
| 1951 | }; | |
| 1952 | ||
| 1953 | const ColumnRange = struct { | |
| 1954 | /// Index into `columns` of the first column in this row. | |
| 1955 | start: usize = undefined, | |
| 1956 | len: u8 = 0, | |
| 1957 | }; | |
| 1958 | ||
| 1959 | columns: std.ArrayListUnmanaged(Column) = .empty, | |
| 1960 | stack: std.ArrayListUnmanaged(ColumnRange) = .empty, | |
| 1961 | current_row: Row = .{}, | |
| 1962 | ||
| 1963 | /// The result of executing the CIE's initial_instructions | |
| 1964 | cie_row: ?Row = null, | |
| 1965 | ||
| 1966 | pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { | |
| 1967 | self.stack.deinit(allocator); | |
| 1968 | self.columns.deinit(allocator); | |
| 1969 | self.* = undefined; | |
| 1970 | } | |
| 1971 | ||
| 1972 | pub fn reset(self: *VirtualMachine) void { | |
| 1973 | self.stack.clearRetainingCapacity(); | |
| 1974 | self.columns.clearRetainingCapacity(); | |
| 1975 | self.current_row = .{}; | |
| 1976 | self.cie_row = null; | |
| 1977 | } | |
| 1978 | ||
| 1979 | /// Return a slice backed by the row's non-CFA columns | |
| 1980 | pub fn rowColumns(self: VirtualMachine, row: Row) []Column { | |
| 1981 | if (row.columns.len == 0) return &.{}; | |
| 1982 | return self.columns.items[row.columns.start..][0..row.columns.len]; | |
| 1983 | } | |
| 1984 | ||
| 1985 | /// Either retrieves or adds a column for `register` (non-CFA) in the current row. | |
| 1986 | fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column { | |
| 1987 | for (self.rowColumns(self.current_row)) |*c| { | |
| 1988 | if (c.register == register) return c; | |
| 1989 | } | |
| 1990 | ||
| 1991 | if (self.current_row.columns.len == 0) { | |
| 1992 | self.current_row.columns.start = self.columns.items.len; | |
| 1993 | } | |
| 1994 | self.current_row.columns.len += 1; | |
| 1995 | ||
| 1996 | const column = try self.columns.addOne(allocator); | |
| 1997 | column.* = .{ | |
| 1998 | .register = register, | |
| 1999 | }; | |
| 2000 | ||
| 2001 | return column; | |
| 2002 | } | |
| 2003 | ||
| 2004 | /// Runs the CIE instructions, then the FDE instructions. Execution halts | |
| 2005 | /// once the row that corresponds to `pc` is known, and the row is returned. | |
| 2006 | pub fn runTo( | |
| 2007 | self: *VirtualMachine, | |
| 2008 | allocator: std.mem.Allocator, | |
| 2009 | pc: u64, | |
| 2010 | cie: std.debug.Dwarf.CommonInformationEntry, | |
| 2011 | fde: std.debug.Dwarf.FrameDescriptionEntry, | |
| 2012 | addr_size_bytes: u8, | |
| 2013 | endian: std.builtin.Endian, | |
| 2014 | ) !Row { | |
| 2015 | assert(self.cie_row == null); | |
| 2016 | if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange; | |
| 2017 | ||
| 2018 | var prev_row: Row = self.current_row; | |
| 2019 | ||
| 2020 | var cie_stream: std.Io.Reader = .fixed(cie.initial_instructions); | |
| 2021 | var fde_stream: std.Io.Reader = .fixed(fde.instructions); | |
| 2022 | const streams = [_]*std.Io.Reader{ &cie_stream, &fde_stream }; | |
| 2023 | ||
| 2024 | for (&streams, 0..) |stream, i| { | |
| 2025 | while (stream.seek < stream.buffer.len) { | |
| 2026 | const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); | |
| 2027 | prev_row = try self.step(allocator, cie, i == 0, instruction); | |
| 2028 | if (pc < fde.pc_begin + self.current_row.offset) return prev_row; | |
| 2029 | } | |
| 2030 | } | |
| 2031 | ||
| 2032 | return self.current_row; | |
| 2033 | } | |
| 2034 | ||
| 2035 | pub fn runToNative( | |
| 2036 | self: *VirtualMachine, | |
| 2037 | allocator: std.mem.Allocator, | |
| 2038 | pc: u64, | |
| 2039 | cie: std.debug.Dwarf.CommonInformationEntry, | |
| 2040 | fde: std.debug.Dwarf.FrameDescriptionEntry, | |
| 2041 | ) !Row { | |
| 2042 | return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), native_endian); | |
| 2043 | } | |
| 2044 | ||
| 2045 | fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void { | |
| 2046 | if (!self.current_row.copy_on_write) return; | |
| 2047 | ||
| 2048 | const new_start = self.columns.items.len; | |
| 2049 | if (self.current_row.columns.len > 0) { | |
| 2050 | try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len); | |
| 2051 | self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row)); | |
| 2052 | self.current_row.columns.start = new_start; | |
| 2053 | } | |
| 2054 | } | |
| 2055 | ||
| 2056 | /// Executes a single instruction. | |
| 2057 | /// If this instruction is from the CIE, `is_initial` should be set. | |
| 2058 | /// Returns the value of `current_row` before executing this instruction. | |
| 2059 | pub fn step( | |
| 2060 | self: *VirtualMachine, | |
| 2061 | allocator: std.mem.Allocator, | |
| 2062 | cie: std.debug.Dwarf.CommonInformationEntry, | |
| 2063 | is_initial: bool, | |
| 2064 | instruction: Dwarf.call_frame.Instruction, | |
| 2065 | ) !Row { | |
| 2066 | // CIE instructions must be run before FDE instructions | |
| 2067 | assert(!is_initial or self.cie_row == null); | |
| 2068 | if (!is_initial and self.cie_row == null) { | |
| 2069 | self.cie_row = self.current_row; | |
| 2070 | self.current_row.copy_on_write = true; | |
| 2071 | } | |
| 2072 | ||
| 2073 | const prev_row = self.current_row; | |
| 2074 | switch (instruction) { | |
| 2075 | .set_loc => |i| { | |
| 2076 | if (i.address <= self.current_row.offset) return error.InvalidOperation; | |
| 2077 | // TODO: Check cie.segment_selector_size != 0 for DWARFV4 | |
| 2078 | self.current_row.offset = i.address; | |
| 2079 | }, | |
| 2080 | inline .advance_loc, | |
| 2081 | .advance_loc1, | |
| 2082 | .advance_loc2, | |
| 2083 | .advance_loc4, | |
| 2084 | => |i| { | |
| 2085 | self.current_row.offset += i.delta * cie.code_alignment_factor; | |
| 2086 | self.current_row.copy_on_write = true; | |
| 2087 | }, | |
| 2088 | inline .offset, | |
| 2089 | .offset_extended, | |
| 2090 | .offset_extended_sf, | |
| 2091 | => |i| { | |
| 2092 | try self.resolveCopyOnWrite(allocator); | |
| 2093 | const column = try self.getOrAddColumn(allocator, i.register); | |
| 2094 | column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor }; | |
| 2095 | }, | |
| 2096 | inline .restore, | |
| 2097 | .restore_extended, | |
| 2098 | => |i| { | |
| 2099 | try self.resolveCopyOnWrite(allocator); | |
| 2100 | if (self.cie_row) |cie_row| { | |
| 2101 | const column = try self.getOrAddColumn(allocator, i.register); | |
| 2102 | column.rule = for (self.rowColumns(cie_row)) |cie_column| { | |
| 2103 | if (cie_column.register == i.register) break cie_column.rule; | |
| 2104 | } else .{ .default = {} }; | |
| 2105 | } else return error.InvalidOperation; | |
| 2106 | }, | |
| 2107 | .nop => {}, | |
| 2108 | .undefined => |i| { | |
| 2109 | try self.resolveCopyOnWrite(allocator); | |
| 2110 | const column = try self.getOrAddColumn(allocator, i.register); | |
| 2111 | column.rule = .{ .undefined = {} }; | |
| 2112 | }, | |
| 2113 | .same_value => |i| { | |
| 2114 | try self.resolveCopyOnWrite(allocator); | |
| 2115 | const column = try self.getOrAddColumn(allocator, i.register); | |
| 2116 | column.rule = .{ .same_value = {} }; | |
| 2117 | }, | |
| 2118 | .register => |i| { | |
| 2119 | try self.resolveCopyOnWrite(allocator); | |
| 2120 | const column = try self.getOrAddColumn(allocator, i.register); | |
| 2121 | column.rule = .{ .register = i.target_register }; | |
| 2122 | }, | |
| 2123 | .remember_state => { | |
| 2124 | try self.stack.append(allocator, self.current_row.columns); | |
| 2125 | self.current_row.copy_on_write = true; | |
| 2126 | }, | |
| 2127 | .restore_state => { | |
| 2128 | const restored_columns = self.stack.pop() orelse return error.InvalidOperation; | |
| 2129 | self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len); | |
| 2130 | try self.columns.ensureUnusedCapacity(allocator, restored_columns.len); | |
| 2131 | ||
| 2132 | self.current_row.columns.start = self.columns.items.len; | |
| 2133 | self.current_row.columns.len = restored_columns.len; | |
| 2134 | self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]); | |
| 2135 | }, | |
| 2136 | .def_cfa => |i| { | |
| 2137 | try self.resolveCopyOnWrite(allocator); | |
| 2138 | self.current_row.cfa = .{ | |
| 2139 | .register = i.register, | |
| 2140 | .rule = .{ .val_offset = @intCast(i.offset) }, | |
| 2141 | }; | |
| 2142 | }, | |
| 2143 | .def_cfa_sf => |i| { | |
| 2144 | try self.resolveCopyOnWrite(allocator); | |
| 2145 | self.current_row.cfa = .{ | |
| 2146 | .register = i.register, | |
| 2147 | .rule = .{ .val_offset = i.offset * cie.data_alignment_factor }, | |
| 2148 | }; | |
| 2149 | }, | |
| 2150 | .def_cfa_register => |i| { | |
| 2151 | try self.resolveCopyOnWrite(allocator); | |
| 2152 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | |
| 2153 | self.current_row.cfa.register = i.register; | |
| 2154 | }, | |
| 2155 | .def_cfa_offset => |i| { | |
| 2156 | try self.resolveCopyOnWrite(allocator); | |
| 2157 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | |
| 2158 | self.current_row.cfa.rule = .{ | |
| 2159 | .val_offset = @intCast(i.offset), | |
| 2160 | }; | |
| 2161 | }, | |
| 2162 | .def_cfa_offset_sf => |i| { | |
| 2163 | try self.resolveCopyOnWrite(allocator); | |
| 2164 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | |
| 2165 | self.current_row.cfa.rule = .{ | |
| 2166 | .val_offset = i.offset * cie.data_alignment_factor, | |
| 2167 | }; | |
| 2168 | }, | |
| 2169 | .def_cfa_expression => |i| { | |
| 2170 | try self.resolveCopyOnWrite(allocator); | |
| 2171 | self.current_row.cfa.register = undefined; | |
| 2172 | self.current_row.cfa.rule = .{ | |
| 2173 | .expression = i.block, | |
| 2174 | }; | |
| 2175 | }, | |
| 2176 | .expression => |i| { | |
| 2177 | try self.resolveCopyOnWrite(allocator); | |
| 2178 | const column = try self.getOrAddColumn(allocator, i.register); | |
| 2179 | column.rule = .{ | |
| 2180 | .expression = i.block, | |
| 2181 | }; | |
| 2182 | }, | |
| 2183 | .val_offset => |i| { | |
| 2184 | try self.resolveCopyOnWrite(allocator); | |
| 2185 | const column = try self.getOrAddColumn(allocator, i.register); | |
| 2186 | column.rule = .{ | |
| 2187 | .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor, | |
| 2188 | }; | |
| 2189 | }, | |
| 2190 | .val_offset_sf => |i| { | |
| 2191 | try self.resolveCopyOnWrite(allocator); | |
| 2192 | const column = try self.getOrAddColumn(allocator, i.register); | |
| 2193 | column.rule = .{ | |
| 2194 | .val_offset = i.offset * cie.data_alignment_factor, | |
| 2195 | }; | |
| 2196 | }, | |
| 2197 | .val_expression => |i| { | |
| 2198 | try self.resolveCopyOnWrite(allocator); | |
| 2199 | const column = try self.getOrAddColumn(allocator, i.register); | |
| 2200 | column.rule = .{ | |
| 2201 | .val_expression = i.block, | |
| 2202 | }; | |
| 2203 | }, | |
| 2204 | } | |
| 2205 | ||
| 2206 | return prev_row; | |
| 2207 | } | |
| 2208 | }; | |
| 2209 | ||
| 2210 | /// Returns the ABI-defined default value this register has in the unwinding table | |
| 2211 | /// before running any of the CIE instructions. The DWARF spec defines these as having | |
| 2212 | /// the .undefined rule by default, but allows ABI authors to override that. | |
| 2213 | fn getRegDefaultValue(reg_number: u8, context: *UnwindContext, out: []u8) !void { | |
| 2214 | switch (builtin.cpu.arch) { | |
| 2215 | .aarch64, .aarch64_be => { | |
| 2216 | // Callee-saved registers are initialized as if they had the .same_value rule | |
| 2217 | if (reg_number >= 19 and reg_number <= 28) { | |
| 2218 | const src = try regBytes(context.thread_context, reg_number, context.reg_context); | |
| 2219 | if (src.len != out.len) return error.RegisterSizeMismatch; | |
| 2220 | @memcpy(out, src); | |
| 2221 | return; | |
| 2222 | } | |
| 2223 | }, | |
| 2224 | else => {}, | |
| 2225 | } | |
| 2226 | ||
| 2227 | @memset(out, undefined); | |
| 2228 | } | |
| 2229 | ||
| 2230 | /// Since register rules are applied (usually) during a panic, | |
| 2231 | /// checked addition / subtraction is used so that we can return | |
| 2232 | /// an error and fall back to FP-based unwinding. | |
| 2233 | fn applyOffset(base: usize, offset: i64) !usize { | |
| 2234 | return if (offset >= 0) | |
| 2235 | try std.math.add(usize, base, @as(usize, @intCast(offset))) | |
| 2236 | else | |
| 2237 | try std.math.sub(usize, base, @as(usize, @intCast(-offset))); | |
| 2238 | } |
lib/std/debug/SelfInfo/Darwin.zig created+993| ... | ... | @@ -0,0 +1,993 @@ |
| 1 | mutex: std.Thread.Mutex, | |
| 2 | /// Accessed through `Module.Adapter`. | |
| 3 | modules: std.ArrayHashMapUnmanaged(Module, void, Module.Context, false), | |
| 4 | ofiles: std.StringArrayHashMapUnmanaged(?OFile), | |
| 5 | ||
| 6 | pub const init: SelfInfo = .{ | |
| 7 | .mutex = .{}, | |
| 8 | .modules = .empty, | |
| 9 | .ofiles = .empty, | |
| 10 | }; | |
| 11 | pub fn deinit(si: *SelfInfo, gpa: Allocator) void { | |
| 12 | for (si.modules.keys()) |*module| { | |
| 13 | unwind: { | |
| 14 | const u = &(module.unwind orelse break :unwind catch break :unwind); | |
| 15 | if (u.dwarf) |*dwarf| dwarf.deinit(gpa); | |
| 16 | } | |
| 17 | loaded: { | |
| 18 | const l = &(module.loaded_macho orelse break :loaded catch break :loaded); | |
| 19 | gpa.free(l.symbols); | |
| 20 | posix.munmap(l.mapped_memory); | |
| 21 | } | |
| 22 | } | |
| 23 | for (si.ofiles.values()) |*opt_ofile| { | |
| 24 | const ofile = &(opt_ofile.* orelse continue); | |
| 25 | ofile.dwarf.deinit(gpa); | |
| 26 | ofile.symbols_by_name.deinit(gpa); | |
| 27 | posix.munmap(ofile.mapped_memory); | |
| 28 | } | |
| 29 | si.modules.deinit(gpa); | |
| 30 | si.ofiles.deinit(gpa); | |
| 31 | } | |
| 32 | ||
| 33 | pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol { | |
| 34 | const module = try si.findModule(gpa, address); | |
| 35 | defer si.mutex.unlock(); | |
| 36 | ||
| 37 | const loaded_macho = try module.getLoadedMachO(gpa); | |
| 38 | ||
| 39 | const vaddr = address - loaded_macho.vaddr_offset; | |
| 40 | const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown; | |
| 41 | ||
| 42 | // offset of `address` from start of `symbol` | |
| 43 | const address_symbol_offset = vaddr - symbol.addr; | |
| 44 | ||
| 45 | // Take the symbol name from the N_FUN STAB entry, we're going to | |
| 46 | // use it if we fail to find the DWARF infos | |
| 47 | const stab_symbol = mem.sliceTo(loaded_macho.strings[symbol.strx..], 0); | |
| 48 | ||
| 49 | // If any information is missing, we can at least return this from now on. | |
| 50 | const sym_only_result: std.debug.Symbol = .{ | |
| 51 | .name = stab_symbol, | |
| 52 | .compile_unit_name = null, | |
| 53 | .source_location = null, | |
| 54 | }; | |
| 55 | ||
| 56 | if (symbol.ofile == MachoSymbol.unknown_ofile) { | |
| 57 | // We don't have STAB info, so can't track down the object file; all we can do is the symbol name. | |
| 58 | return sym_only_result; | |
| 59 | } | |
| 60 | ||
| 61 | const o_file: *OFile = of: { | |
| 62 | const path = mem.sliceTo(loaded_macho.strings[symbol.ofile..], 0); | |
| 63 | const gop = try si.ofiles.getOrPut(gpa, path); | |
| 64 | if (!gop.found_existing) { | |
| 65 | gop.value_ptr.* = loadOFile(gpa, path) catch null; | |
| 66 | } | |
| 67 | if (gop.value_ptr.*) |*o_file| { | |
| 68 | break :of o_file; | |
| 69 | } else { | |
| 70 | return sym_only_result; | |
| 71 | } | |
| 72 | }; | |
| 73 | ||
| 74 | const symbol_index = o_file.symbols_by_name.getKeyAdapted( | |
| 75 | @as([]const u8, stab_symbol), | |
| 76 | @as(OFile.SymbolAdapter, .{ .strtab = o_file.strtab, .symtab = o_file.symtab }), | |
| 77 | ) orelse return sym_only_result; | |
| 78 | const symbol_ofile_vaddr = o_file.symtab[symbol_index].n_value; | |
| 79 | ||
| 80 | const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result; | |
| 81 | ||
| 82 | return .{ | |
| 83 | .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr + address_symbol_offset) orelse stab_symbol, | |
| 84 | .compile_unit_name = compile_unit.die.getAttrString( | |
| 85 | &o_file.dwarf, | |
| 86 | native_endian, | |
| 87 | std.dwarf.AT.name, | |
| 88 | o_file.dwarf.section(.debug_str), | |
| 89 | compile_unit, | |
| 90 | ) catch |err| switch (err) { | |
| 91 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | |
| 92 | }, | |
| 93 | .source_location = o_file.dwarf.getLineNumberInfo( | |
| 94 | gpa, | |
| 95 | native_endian, | |
| 96 | compile_unit, | |
| 97 | symbol_ofile_vaddr + address_symbol_offset, | |
| 98 | ) catch null, | |
| 99 | }; | |
| 100 | } | |
| 101 | pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { | |
| 102 | const module = try si.findModule(gpa, address); | |
| 103 | defer si.mutex.unlock(); | |
| 104 | return module.name; | |
| 105 | } | |
| 106 | ||
| 107 | pub const can_unwind: bool = true; | |
| 108 | pub const UnwindContext = std.debug.Dwarf.SelfUnwinder; | |
| 109 | /// Unwind a frame using MachO compact unwind info (from `__unwind_info`). | |
| 110 | /// If the compact encoding can't encode a way to unwind a frame, it will | |
| 111 | /// defer unwinding to DWARF, in which case `__eh_frame` will be used if available. | |
| 112 | pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { | |
| 113 | return unwindFrameInner(si, gpa, context) catch |err| switch (err) { | |
| 114 | error.InvalidDebugInfo, | |
| 115 | error.MissingDebugInfo, | |
| 116 | error.UnsupportedDebugInfo, | |
| 117 | error.ReadFailed, | |
| 118 | error.OutOfMemory, | |
| 119 | error.Unexpected, | |
| 120 | => |e| return e, | |
| 121 | error.UnsupportedRegister, | |
| 122 | error.UnsupportedAddrSize, | |
| 123 | error.UnimplementedUserOpcode, | |
| 124 | => return error.UnsupportedDebugInfo, | |
| 125 | error.Overflow, | |
| 126 | error.EndOfStream, | |
| 127 | error.StreamTooLong, | |
| 128 | error.InvalidOpcode, | |
| 129 | error.InvalidOperation, | |
| 130 | error.InvalidOperand, | |
| 131 | error.InvalidRegister, | |
| 132 | error.IncompatibleRegisterSize, | |
| 133 | => return error.InvalidDebugInfo, | |
| 134 | }; | |
| 135 | } | |
| 136 | fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize { | |
| 137 | const module = try si.findModule(gpa, context.pc); | |
| 138 | defer si.mutex.unlock(); | |
| 139 | ||
| 140 | const unwind: *Module.Unwind = try module.getUnwindInfo(gpa); | |
| 141 | ||
| 142 | const ip_reg_num = comptime Dwarf.ipRegNum(builtin.target.cpu.arch).?; | |
| 143 | const fp_reg_num = comptime Dwarf.fpRegNum(builtin.target.cpu.arch); | |
| 144 | const sp_reg_num = comptime Dwarf.spRegNum(builtin.target.cpu.arch); | |
| 145 | ||
| 146 | const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo; | |
| 147 | if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo; | |
| 148 | const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info); | |
| 149 | ||
| 150 | const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry); | |
| 151 | if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidDebugInfo; | |
| 152 | const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]); | |
| 153 | if (indices.len == 0) return error.MissingDebugInfo; | |
| 154 | ||
| 155 | // offset of the PC into the `__TEXT` segment | |
| 156 | const pc_text_offset = context.pc - module.text_base; | |
| 157 | ||
| 158 | const start_offset: u32, const first_level_offset: u32 = index: { | |
| 159 | var left: usize = 0; | |
| 160 | var len: usize = indices.len; | |
| 161 | while (len > 1) { | |
| 162 | const mid = left + len / 2; | |
| 163 | if (pc_text_offset < indices[mid].functionOffset) { | |
| 164 | len /= 2; | |
| 165 | } else { | |
| 166 | left = mid; | |
| 167 | len -= len / 2; | |
| 168 | } | |
| 169 | } | |
| 170 | break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset }; | |
| 171 | }; | |
| 172 | // An offset of 0 is a sentinel indicating a range does not have unwind info. | |
| 173 | if (start_offset == 0) return error.MissingDebugInfo; | |
| 174 | ||
| 175 | const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t); | |
| 176 | if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidDebugInfo; | |
| 177 | const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast( | |
| 178 | unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count], | |
| 179 | ); | |
| 180 | ||
| 181 | if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidDebugInfo; | |
| 182 | const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]); | |
| 183 | ||
| 184 | const entry: struct { | |
| 185 | function_offset: usize, | |
| 186 | raw_encoding: u32, | |
| 187 | } = switch (kind.*) { | |
| 188 | .REGULAR => entry: { | |
| 189 | if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidDebugInfo; | |
| 190 | const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]); | |
| 191 | ||
| 192 | const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry); | |
| 193 | if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo; | |
| 194 | const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast( | |
| 195 | unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count], | |
| 196 | ); | |
| 197 | if (entries.len == 0) return error.InvalidDebugInfo; | |
| 198 | ||
| 199 | var left: usize = 0; | |
| 200 | var len: usize = entries.len; | |
| 201 | while (len > 1) { | |
| 202 | const mid = left + len / 2; | |
| 203 | if (pc_text_offset < entries[mid].functionOffset) { | |
| 204 | len /= 2; | |
| 205 | } else { | |
| 206 | left = mid; | |
| 207 | len -= len / 2; | |
| 208 | } | |
| 209 | } | |
| 210 | break :entry .{ | |
| 211 | .function_offset = entries[left].functionOffset, | |
| 212 | .raw_encoding = entries[left].encoding, | |
| 213 | }; | |
| 214 | }, | |
| 215 | .COMPRESSED => entry: { | |
| 216 | if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidDebugInfo; | |
| 217 | const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]); | |
| 218 | ||
| 219 | const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry); | |
| 220 | if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo; | |
| 221 | const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast( | |
| 222 | unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count], | |
| 223 | ); | |
| 224 | if (entries.len == 0) return error.InvalidDebugInfo; | |
| 225 | ||
| 226 | var left: usize = 0; | |
| 227 | var len: usize = entries.len; | |
| 228 | while (len > 1) { | |
| 229 | const mid = left + len / 2; | |
| 230 | if (pc_text_offset < first_level_offset + entries[mid].funcOffset) { | |
| 231 | len /= 2; | |
| 232 | } else { | |
| 233 | left = mid; | |
| 234 | len -= len / 2; | |
| 235 | } | |
| 236 | } | |
| 237 | const entry = entries[left]; | |
| 238 | ||
| 239 | const function_offset = first_level_offset + entry.funcOffset; | |
| 240 | if (entry.encodingIndex < common_encodings.len) { | |
| 241 | break :entry .{ | |
| 242 | .function_offset = function_offset, | |
| 243 | .raw_encoding = common_encodings[entry.encodingIndex], | |
| 244 | }; | |
| 245 | } | |
| 246 | ||
| 247 | const local_index = entry.encodingIndex - common_encodings.len; | |
| 248 | const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t); | |
| 249 | if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidDebugInfo; | |
| 250 | const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast( | |
| 251 | unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count], | |
| 252 | ); | |
| 253 | if (local_index >= local_encodings.len) return error.InvalidDebugInfo; | |
| 254 | break :entry .{ | |
| 255 | .function_offset = function_offset, | |
| 256 | .raw_encoding = local_encodings[local_index], | |
| 257 | }; | |
| 258 | }, | |
| 259 | else => return error.InvalidDebugInfo, | |
| 260 | }; | |
| 261 | ||
| 262 | if (entry.raw_encoding == 0) return error.MissingDebugInfo; | |
| 263 | ||
| 264 | const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); | |
| 265 | const new_ip = switch (builtin.cpu.arch) { | |
| 266 | .x86_64 => switch (encoding.mode.x86_64) { | |
| 267 | .OLD => return error.UnsupportedDebugInfo, | |
| 268 | .RBP_FRAME => ip: { | |
| 269 | const frame = encoding.value.x86_64.frame; | |
| 270 | ||
| 271 | const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*; | |
| 272 | const new_sp = fp + 2 * @sizeOf(usize); | |
| 273 | ||
| 274 | const ip_ptr = fp + @sizeOf(usize); | |
| 275 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 276 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | |
| 277 | ||
| 278 | (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp; | |
| 279 | (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp; | |
| 280 | (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip; | |
| 281 | ||
| 282 | const regs: [5]u3 = .{ | |
| 283 | frame.reg0, | |
| 284 | frame.reg1, | |
| 285 | frame.reg2, | |
| 286 | frame.reg3, | |
| 287 | frame.reg4, | |
| 288 | }; | |
| 289 | for (regs, 0..) |reg, i| { | |
| 290 | if (reg == 0) continue; | |
| 291 | const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize); | |
| 292 | const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg); | |
| 293 | (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(addr)).*; | |
| 294 | } | |
| 295 | ||
| 296 | break :ip new_ip; | |
| 297 | }, | |
| 298 | .STACK_IMMD, | |
| 299 | .STACK_IND, | |
| 300 | => ip: { | |
| 301 | const frameless = encoding.value.x86_64.frameless; | |
| 302 | ||
| 303 | const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*; | |
| 304 | const stack_size: usize = stack_size: { | |
| 305 | if (encoding.mode.x86_64 == .STACK_IMMD) { | |
| 306 | break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize); | |
| 307 | } | |
| 308 | // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. | |
| 309 | const sub_offset_addr = | |
| 310 | module.text_base + | |
| 311 | entry.function_offset + | |
| 312 | frameless.stack.indirect.sub_offset; | |
| 313 | // `sub_offset_addr` points to the offset of the literal within the instruction | |
| 314 | const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; | |
| 315 | break :stack_size sub_operand + @sizeOf(usize) * @as(usize, frameless.stack.indirect.stack_adjust); | |
| 316 | }; | |
| 317 | ||
| 318 | // Decode the Lehmer-coded sequence of registers. | |
| 319 | // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h | |
| 320 | ||
| 321 | // Decode the variable-based permutation number into its digits. Each digit represents | |
| 322 | // an index into the list of register numbers that weren't yet used in the sequence at | |
| 323 | // the time the digit was added. | |
| 324 | const reg_count = frameless.stack_reg_count; | |
| 325 | const ip_ptr = ip_ptr: { | |
| 326 | var digits: [6]u3 = undefined; | |
| 327 | var accumulator: usize = frameless.stack_reg_permutation; | |
| 328 | var base: usize = 2; | |
| 329 | for (0..reg_count) |i| { | |
| 330 | const div = accumulator / base; | |
| 331 | digits[digits.len - 1 - i] = @intCast(accumulator - base * div); | |
| 332 | accumulator = div; | |
| 333 | base += 1; | |
| 334 | } | |
| 335 | ||
| 336 | var registers: [6]u3 = undefined; | |
| 337 | var used_indices: [6]bool = @splat(false); | |
| 338 | for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { | |
| 339 | var unused_count: u8 = 0; | |
| 340 | const unused_index = for (used_indices, 0..) |used, index| { | |
| 341 | if (!used) { | |
| 342 | if (target_unused_index == unused_count) break index; | |
| 343 | unused_count += 1; | |
| 344 | } | |
| 345 | } else unreachable; | |
| 346 | registers[i] = @intCast(unused_index + 1); | |
| 347 | used_indices[unused_index] = true; | |
| 348 | } | |
| 349 | ||
| 350 | var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); | |
| 351 | for (0..reg_count) |i| { | |
| 352 | const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]); | |
| 353 | (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 354 | reg_addr += @sizeOf(usize); | |
| 355 | } | |
| 356 | ||
| 357 | break :ip_ptr reg_addr; | |
| 358 | }; | |
| 359 | ||
| 360 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 361 | const new_sp = ip_ptr + @sizeOf(usize); | |
| 362 | ||
| 363 | (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp; | |
| 364 | (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip; | |
| 365 | ||
| 366 | break :ip new_ip; | |
| 367 | }, | |
| 368 | .DWARF => { | |
| 369 | const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo); | |
| 370 | const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.x86_64.dwarf); | |
| 371 | return context.next(gpa, &rules); | |
| 372 | }, | |
| 373 | }, | |
| 374 | .aarch64, .aarch64_be => switch (encoding.mode.arm64) { | |
| 375 | .OLD => return error.UnsupportedDebugInfo, | |
| 376 | .FRAMELESS => ip: { | |
| 377 | const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*; | |
| 378 | const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; | |
| 379 | const new_ip = (try dwarfRegNative(&context.cpu_state, 30)).*; | |
| 380 | (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp; | |
| 381 | break :ip new_ip; | |
| 382 | }, | |
| 383 | .DWARF => { | |
| 384 | const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo); | |
| 385 | const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.arm64.dwarf); | |
| 386 | return context.next(gpa, &rules); | |
| 387 | }, | |
| 388 | .FRAME => ip: { | |
| 389 | const frame = encoding.value.arm64.frame; | |
| 390 | ||
| 391 | const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*; | |
| 392 | const ip_ptr = fp + @sizeOf(usize); | |
| 393 | ||
| 394 | var reg_addr = fp - @sizeOf(usize); | |
| 395 | inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| { | |
| 396 | if (@field(frame.x_reg_pairs, field.name) != 0) { | |
| 397 | (try dwarfRegNative(&context.cpu_state, 19 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 398 | reg_addr += @sizeOf(usize); | |
| 399 | (try dwarfRegNative(&context.cpu_state, 20 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 400 | reg_addr += @sizeOf(usize); | |
| 401 | } | |
| 402 | } | |
| 403 | ||
| 404 | inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| { | |
| 405 | if (@field(frame.d_reg_pairs, field.name) != 0) { | |
| 406 | // Only the lower half of the 128-bit V registers are restored during unwinding | |
| 407 | { | |
| 408 | const dest: *align(1) usize = @ptrCast(try context.cpu_state.dwarfRegisterBytes(64 + 8 + i)); | |
| 409 | dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 410 | } | |
| 411 | reg_addr += @sizeOf(usize); | |
| 412 | { | |
| 413 | const dest: *align(1) usize = @ptrCast(try context.cpu_state.dwarfRegisterBytes(64 + 9 + i)); | |
| 414 | dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*; | |
| 415 | } | |
| 416 | reg_addr += @sizeOf(usize); | |
| 417 | } | |
| 418 | } | |
| 419 | ||
| 420 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | |
| 421 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | |
| 422 | ||
| 423 | (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp; | |
| 424 | (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip; | |
| 425 | ||
| 426 | break :ip new_ip; | |
| 427 | }, | |
| 428 | }, | |
| 429 | else => comptime unreachable, // unimplemented | |
| 430 | }; | |
| 431 | ||
| 432 | const ret_addr = std.debug.stripInstructionPtrAuthCode(new_ip); | |
| 433 | ||
| 434 | // Like `Dwarf.SelfUnwinder.next`, adjust our next lookup pc in case the `call` was this | |
| 435 | // function's last instruction making `ret_addr` one byte past its end. | |
| 436 | context.pc = ret_addr -| 1; | |
| 437 | ||
| 438 | return ret_addr; | |
| 439 | } | |
| 440 | ||
| 441 | /// Acquires the mutex on success. | |
| 442 | fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module { | |
| 443 | var info: std.c.dl_info = undefined; | |
| 444 | if (std.c.dladdr(@ptrFromInt(address), &info) == 0) { | |
| 445 | return error.MissingDebugInfo; | |
| 446 | } | |
| 447 | si.mutex.lock(); | |
| 448 | errdefer si.mutex.unlock(); | |
| 449 | const gop = try si.modules.getOrPutAdapted(gpa, @intFromPtr(info.fbase), Module.Adapter{}); | |
| 450 | errdefer comptime unreachable; | |
| 451 | if (!gop.found_existing) { | |
| 452 | gop.key_ptr.* = .{ | |
| 453 | .text_base = @intFromPtr(info.fbase), | |
| 454 | .name = std.mem.span(info.fname), | |
| 455 | .unwind = null, | |
| 456 | .loaded_macho = null, | |
| 457 | }; | |
| 458 | } | |
| 459 | return gop.key_ptr; | |
| 460 | } | |
| 461 | ||
| 462 | const Module = struct { | |
| 463 | text_base: usize, | |
| 464 | name: []const u8, | |
| 465 | unwind: ?(Error!Unwind), | |
| 466 | loaded_macho: ?(Error!LoadedMachO), | |
| 467 | ||
| 468 | const Adapter = struct { | |
| 469 | pub fn hash(_: Adapter, text_base: usize) u32 { | |
| 470 | return @truncate(std.hash.int(text_base)); | |
| 471 | } | |
| 472 | pub fn eql(_: Adapter, a_text_base: usize, b_module: Module, b_index: usize) bool { | |
| 473 | _ = b_index; | |
| 474 | return a_text_base == b_module.text_base; | |
| 475 | } | |
| 476 | }; | |
| 477 | const Context = struct { | |
| 478 | pub fn hash(_: Context, module: Module) u32 { | |
| 479 | return @truncate(std.hash.int(module.text_base)); | |
| 480 | } | |
| 481 | pub fn eql(_: Context, a_module: Module, b_module: Module, b_index: usize) bool { | |
| 482 | _ = b_index; | |
| 483 | return a_module.text_base == b_module.text_base; | |
| 484 | } | |
| 485 | }; | |
| 486 | ||
| 487 | const Unwind = struct { | |
| 488 | /// The slide applied to the `__unwind_info` and `__eh_frame` sections. | |
| 489 | /// So, `unwind_info.ptr` is this many bytes higher than the section's vmaddr. | |
| 490 | vmaddr_slide: u64, | |
| 491 | /// Backed by the in-memory section mapped by the loader. | |
| 492 | unwind_info: ?[]const u8, | |
| 493 | /// Backed by the in-memory `__eh_frame` section mapped by the loader. | |
| 494 | dwarf: ?Dwarf.Unwind, | |
| 495 | }; | |
| 496 | ||
| 497 | const LoadedMachO = struct { | |
| 498 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 499 | symbols: []const MachoSymbol, | |
| 500 | strings: []const u8, | |
| 501 | /// This is not necessarily the same as the vmaddr_slide that dyld would report. This is | |
| 502 | /// because the segments in the file on disk might differ from the ones in memory. Normally | |
| 503 | /// we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying: | |
| 504 | /// it exists on disk (necessarily, because the kernel needs to load it!), but is also in | |
| 505 | /// the dyld cache (dyld actually restart itself from cache after loading it), and the two | |
| 506 | /// versions have (very) different segment base addresses. It's sort of like a large slide | |
| 507 | /// has been applied to all addresses in memory. For an optimal experience, we consider the | |
| 508 | /// on-disk vmaddr instead of the in-memory one. | |
| 509 | vaddr_offset: usize, | |
| 510 | }; | |
| 511 | ||
| 512 | fn getUnwindInfo(module: *Module, gpa: Allocator) Error!*Unwind { | |
| 513 | if (module.unwind == null) module.unwind = loadUnwindInfo(module, gpa); | |
| 514 | return if (module.unwind.?) |*unwind| unwind else |err| err; | |
| 515 | } | |
| 516 | fn loadUnwindInfo(module: *const Module, gpa: Allocator) Error!Unwind { | |
| 517 | const header: *std.macho.mach_header = @ptrFromInt(module.text_base); | |
| 518 | ||
| 519 | var it: macho.LoadCommandIterator = .{ | |
| 520 | .ncmds = header.ncmds, | |
| 521 | .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds], | |
| 522 | }; | |
| 523 | const sections, const text_vmaddr = while (it.next()) |load_cmd| { | |
| 524 | if (load_cmd.cmd() != .SEGMENT_64) continue; | |
| 525 | const segment_cmd = load_cmd.cast(macho.segment_command_64).?; | |
| 526 | if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue; | |
| 527 | break .{ load_cmd.getSections(), segment_cmd.vmaddr }; | |
| 528 | } else unreachable; | |
| 529 | ||
| 530 | const vmaddr_slide = module.text_base - text_vmaddr; | |
| 531 | ||
| 532 | var opt_unwind_info: ?[]const u8 = null; | |
| 533 | var opt_eh_frame: ?[]const u8 = null; | |
| 534 | for (sections) |sect| { | |
| 535 | if (mem.eql(u8, sect.sectName(), "__unwind_info")) { | |
| 536 | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr))); | |
| 537 | opt_unwind_info = sect_ptr[0..@intCast(sect.size)]; | |
| 538 | } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) { | |
| 539 | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr))); | |
| 540 | opt_eh_frame = sect_ptr[0..@intCast(sect.size)]; | |
| 541 | } | |
| 542 | } | |
| 543 | const eh_frame = opt_eh_frame orelse return .{ | |
| 544 | .vmaddr_slide = vmaddr_slide, | |
| 545 | .unwind_info = opt_unwind_info, | |
| 546 | .dwarf = null, | |
| 547 | }; | |
| 548 | var dwarf: Dwarf.Unwind = .initSection(.eh_frame, @intFromPtr(eh_frame.ptr) - vmaddr_slide, eh_frame); | |
| 549 | errdefer dwarf.deinit(gpa); | |
| 550 | // We don't need lookups, so this call is just for scanning CIEs. | |
| 551 | dwarf.prepare(gpa, @sizeOf(usize), native_endian, false, true) catch |err| switch (err) { | |
| 552 | error.ReadFailed => unreachable, // it's all fixed buffers | |
| 553 | error.InvalidDebugInfo, | |
| 554 | error.MissingDebugInfo, | |
| 555 | error.OutOfMemory, | |
| 556 | => |e| return e, | |
| 557 | error.EndOfStream, | |
| 558 | error.Overflow, | |
| 559 | error.StreamTooLong, | |
| 560 | error.InvalidOperand, | |
| 561 | error.InvalidOpcode, | |
| 562 | error.InvalidOperation, | |
| 563 | => return error.InvalidDebugInfo, | |
| 564 | error.UnsupportedAddrSize, | |
| 565 | error.UnsupportedDwarfVersion, | |
| 566 | error.UnimplementedUserOpcode, | |
| 567 | => return error.UnsupportedDebugInfo, | |
| 568 | }; | |
| 569 | ||
| 570 | return .{ | |
| 571 | .vmaddr_slide = vmaddr_slide, | |
| 572 | .unwind_info = opt_unwind_info, | |
| 573 | .dwarf = dwarf, | |
| 574 | }; | |
| 575 | } | |
| 576 | ||
| 577 | fn getLoadedMachO(module: *Module, gpa: Allocator) Error!*LoadedMachO { | |
| 578 | if (module.loaded_macho == null) module.loaded_macho = loadMachO(module, gpa) catch |err| switch (err) { | |
| 579 | error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| e, | |
| 580 | else => error.ReadFailed, | |
| 581 | }; | |
| 582 | return if (module.loaded_macho.?) |*lm| lm else |err| err; | |
| 583 | } | |
| 584 | fn loadMachO(module: *const Module, gpa: Allocator) Error!LoadedMachO { | |
| 585 | const all_mapped_memory = try mapDebugInfoFile(module.name); | |
| 586 | errdefer posix.munmap(all_mapped_memory); | |
| 587 | ||
| 588 | // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal | |
| 589 | // binary": a simple file format which contains Mach-O binaries for multiple targets. For | |
| 590 | // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images | |
| 591 | // for both ARM64 macOS and x86_64 macOS. | |
| 592 | if (all_mapped_memory.len < 4) return error.InvalidDebugInfo; | |
| 593 | const magic = @as(*const u32, @ptrCast(all_mapped_memory.ptr)).*; | |
| 594 | // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`. | |
| 595 | const mapped_macho = switch (magic) { | |
| 596 | macho.MH_MAGIC_64 => all_mapped_memory, | |
| 597 | ||
| 598 | macho.FAT_CIGAM => mapped_macho: { | |
| 599 | // This is the universal binary format (aka a "fat binary"). Annoyingly, the whole thing | |
| 600 | // is big-endian, so we'll be swapping some bytes. | |
| 601 | if (all_mapped_memory.len < @sizeOf(macho.fat_header)) return error.InvalidDebugInfo; | |
| 602 | const hdr: *const macho.fat_header = @ptrCast(all_mapped_memory.ptr); | |
| 603 | const archs_ptr: [*]const macho.fat_arch = @ptrCast(all_mapped_memory.ptr + @sizeOf(macho.fat_header)); | |
| 604 | const archs: []const macho.fat_arch = archs_ptr[0..@byteSwap(hdr.nfat_arch)]; | |
| 605 | const native_cpu_type = switch (builtin.cpu.arch) { | |
| 606 | .x86_64 => macho.CPU_TYPE_X86_64, | |
| 607 | .aarch64 => macho.CPU_TYPE_ARM64, | |
| 608 | else => comptime unreachable, | |
| 609 | }; | |
| 610 | for (archs) |*arch| { | |
| 611 | if (@byteSwap(arch.cputype) != native_cpu_type) continue; | |
| 612 | const offset = @byteSwap(arch.offset); | |
| 613 | const size = @byteSwap(arch.size); | |
| 614 | break :mapped_macho all_mapped_memory[offset..][0..size]; | |
| 615 | } | |
| 616 | // Our native architecture was not present in the fat binary. | |
| 617 | return error.MissingDebugInfo; | |
| 618 | }, | |
| 619 | ||
| 620 | // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It | |
| 621 | // will be fairly easy to add support here if necessary; it's very similar to above. | |
| 622 | macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo, | |
| 623 | ||
| 624 | else => return error.InvalidDebugInfo, | |
| 625 | }; | |
| 626 | ||
| 627 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_macho.ptr)); | |
| 628 | if (hdr.magic != macho.MH_MAGIC_64) | |
| 629 | return error.InvalidDebugInfo; | |
| 630 | ||
| 631 | const symtab: macho.symtab_command, const text_vmaddr: u64 = lc_iter: { | |
| 632 | var it: macho.LoadCommandIterator = .{ | |
| 633 | .ncmds = hdr.ncmds, | |
| 634 | .buffer = mapped_macho[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | |
| 635 | }; | |
| 636 | var symtab: ?macho.symtab_command = null; | |
| 637 | var text_vmaddr: ?u64 = null; | |
| 638 | while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 639 | .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo, | |
| 640 | .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| { | |
| 641 | if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue; | |
| 642 | text_vmaddr = seg_cmd.vmaddr; | |
| 643 | }, | |
| 644 | else => {}, | |
| 645 | }; | |
| 646 | break :lc_iter .{ | |
| 647 | symtab orelse return error.MissingDebugInfo, | |
| 648 | text_vmaddr orelse return error.MissingDebugInfo, | |
| 649 | }; | |
| 650 | }; | |
| 651 | ||
| 652 | const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_macho[symtab.symoff..]); | |
| 653 | const syms = syms_ptr[0..symtab.nsyms]; | |
| 654 | const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1]; | |
| 655 | ||
| 656 | var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len); | |
| 657 | defer symbols.deinit(gpa); | |
| 658 | ||
| 659 | // This map is temporary; it is used only to detect duplicates here. This is | |
| 660 | // necessary because we prefer to use STAB ("symbolic debugging table") symbols, | |
| 661 | // but they might not be present, so we track normal symbols too. | |
| 662 | // Indices match 1-1 with those of `symbols`. | |
| 663 | var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty; | |
| 664 | defer symbol_names.deinit(gpa); | |
| 665 | try symbol_names.ensureUnusedCapacity(gpa, syms.len); | |
| 666 | ||
| 667 | var ofile: u32 = undefined; | |
| 668 | var last_sym: MachoSymbol = undefined; | |
| 669 | var state: enum { | |
| 670 | init, | |
| 671 | oso_open, | |
| 672 | oso_close, | |
| 673 | bnsym, | |
| 674 | fun_strx, | |
| 675 | fun_size, | |
| 676 | ensym, | |
| 677 | } = .init; | |
| 678 | ||
| 679 | for (syms) |*sym| { | |
| 680 | if (sym.n_type.bits.is_stab == 0) { | |
| 681 | if (sym.n_strx == 0) continue; | |
| 682 | switch (sym.n_type.bits.type) { | |
| 683 | .undf, .pbud, .indr, .abs, _ => continue, | |
| 684 | .sect => { | |
| 685 | const name = std.mem.sliceTo(strings[sym.n_strx..], 0); | |
| 686 | const gop = symbol_names.getOrPutAssumeCapacity(name); | |
| 687 | if (!gop.found_existing) { | |
| 688 | assert(gop.index == symbols.items.len); | |
| 689 | symbols.appendAssumeCapacity(.{ | |
| 690 | .strx = sym.n_strx, | |
| 691 | .addr = sym.n_value, | |
| 692 | .ofile = MachoSymbol.unknown_ofile, | |
| 693 | }); | |
| 694 | } | |
| 695 | }, | |
| 696 | } | |
| 697 | continue; | |
| 698 | } | |
| 699 | ||
| 700 | // TODO handle globals N_GSYM, and statics N_STSYM | |
| 701 | switch (sym.n_type.stab) { | |
| 702 | .oso => switch (state) { | |
| 703 | .init, .oso_close => { | |
| 704 | state = .oso_open; | |
| 705 | ofile = sym.n_strx; | |
| 706 | }, | |
| 707 | else => return error.InvalidDebugInfo, | |
| 708 | }, | |
| 709 | .bnsym => switch (state) { | |
| 710 | .oso_open, .ensym => { | |
| 711 | state = .bnsym; | |
| 712 | last_sym = .{ | |
| 713 | .strx = 0, | |
| 714 | .addr = sym.n_value, | |
| 715 | .ofile = ofile, | |
| 716 | }; | |
| 717 | }, | |
| 718 | else => return error.InvalidDebugInfo, | |
| 719 | }, | |
| 720 | .fun => switch (state) { | |
| 721 | .bnsym => { | |
| 722 | state = .fun_strx; | |
| 723 | last_sym.strx = sym.n_strx; | |
| 724 | }, | |
| 725 | .fun_strx => { | |
| 726 | state = .fun_size; | |
| 727 | }, | |
| 728 | else => return error.InvalidDebugInfo, | |
| 729 | }, | |
| 730 | .ensym => switch (state) { | |
| 731 | .fun_size => { | |
| 732 | state = .ensym; | |
| 733 | if (last_sym.strx != 0) { | |
| 734 | const name = std.mem.sliceTo(strings[last_sym.strx..], 0); | |
| 735 | const gop = symbol_names.getOrPutAssumeCapacity(name); | |
| 736 | if (!gop.found_existing) { | |
| 737 | assert(gop.index == symbols.items.len); | |
| 738 | symbols.appendAssumeCapacity(last_sym); | |
| 739 | } else { | |
| 740 | symbols.items[gop.index] = last_sym; | |
| 741 | } | |
| 742 | } | |
| 743 | }, | |
| 744 | else => return error.InvalidDebugInfo, | |
| 745 | }, | |
| 746 | .so => switch (state) { | |
| 747 | .init, .oso_close => {}, | |
| 748 | .oso_open, .ensym => { | |
| 749 | state = .oso_close; | |
| 750 | }, | |
| 751 | else => return error.InvalidDebugInfo, | |
| 752 | }, | |
| 753 | else => {}, | |
| 754 | } | |
| 755 | } | |
| 756 | ||
| 757 | switch (state) { | |
| 758 | .init => { | |
| 759 | // Missing STAB symtab entries is still okay, unless there were also no normal symbols. | |
| 760 | if (symbols.items.len == 0) return error.MissingDebugInfo; | |
| 761 | }, | |
| 762 | .oso_close => {}, | |
| 763 | else => return error.InvalidDebugInfo, // corrupted STAB entries in symtab | |
| 764 | } | |
| 765 | ||
| 766 | const symbols_slice = try symbols.toOwnedSlice(gpa); | |
| 767 | errdefer gpa.free(symbols_slice); | |
| 768 | ||
| 769 | // Even though lld emits symbols in ascending order, this debug code | |
| 770 | // should work for programs linked in any valid way. | |
| 771 | // This sort is so that we can binary search later. | |
| 772 | mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan); | |
| 773 | ||
| 774 | return .{ | |
| 775 | .mapped_memory = all_mapped_memory, | |
| 776 | .symbols = symbols_slice, | |
| 777 | .strings = strings, | |
| 778 | .vaddr_offset = module.text_base - text_vmaddr, | |
| 779 | }; | |
| 780 | } | |
| 781 | }; | |
| 782 | ||
| 783 | const OFile = struct { | |
| 784 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 785 | dwarf: Dwarf, | |
| 786 | strtab: []const u8, | |
| 787 | symtab: []align(1) const macho.nlist_64, | |
| 788 | /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed | |
| 789 | /// through `SymbolAdapter`, so that the symbol name is used as the logical key. | |
| 790 | symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true), | |
| 791 | ||
| 792 | const SymbolAdapter = struct { | |
| 793 | strtab: []const u8, | |
| 794 | symtab: []align(1) const macho.nlist_64, | |
| 795 | pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 { | |
| 796 | _ = ctx; | |
| 797 | return @truncate(std.hash.Wyhash.hash(0, sym_name)); | |
| 798 | } | |
| 799 | pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool { | |
| 800 | _ = b_index; | |
| 801 | const b_sym = ctx.symtab[b_sym_index]; | |
| 802 | const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0); | |
| 803 | return mem.eql(u8, a_sym_name, b_sym_name); | |
| 804 | } | |
| 805 | }; | |
| 806 | }; | |
| 807 | ||
| 808 | const MachoSymbol = struct { | |
| 809 | strx: u32, | |
| 810 | addr: u64, | |
| 811 | /// Value may be `unknown_ofile`. | |
| 812 | ofile: u32, | |
| 813 | const unknown_ofile = std.math.maxInt(u32); | |
| 814 | fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool { | |
| 815 | _ = context; | |
| 816 | return lhs.addr < rhs.addr; | |
| 817 | } | |
| 818 | /// Assumes that `symbols` is sorted in order of ascending `addr`. | |
| 819 | fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { | |
| 820 | if (symbols.len == 0) return null; // no potential match | |
| 821 | if (address < symbols[0].addr) return null; // address is before the lowest-address symbol | |
| 822 | var left: usize = 0; | |
| 823 | var len: usize = symbols.len; | |
| 824 | while (len > 1) { | |
| 825 | const mid = left + len / 2; | |
| 826 | if (address < symbols[mid].addr) { | |
| 827 | len /= 2; | |
| 828 | } else { | |
| 829 | left = mid; | |
| 830 | len -= len / 2; | |
| 831 | } | |
| 832 | } | |
| 833 | return &symbols[left]; | |
| 834 | } | |
| 835 | ||
| 836 | test find { | |
| 837 | const symbols: []const MachoSymbol = &.{ | |
| 838 | .{ .addr = 100, .strx = undefined, .ofile = undefined }, | |
| 839 | .{ .addr = 200, .strx = undefined, .ofile = undefined }, | |
| 840 | .{ .addr = 300, .strx = undefined, .ofile = undefined }, | |
| 841 | }; | |
| 842 | ||
| 843 | try testing.expectEqual(null, find(symbols, 0)); | |
| 844 | try testing.expectEqual(null, find(symbols, 99)); | |
| 845 | try testing.expectEqual(&symbols[0], find(symbols, 100).?); | |
| 846 | try testing.expectEqual(&symbols[0], find(symbols, 150).?); | |
| 847 | try testing.expectEqual(&symbols[0], find(symbols, 199).?); | |
| 848 | ||
| 849 | try testing.expectEqual(&symbols[1], find(symbols, 200).?); | |
| 850 | try testing.expectEqual(&symbols[1], find(symbols, 250).?); | |
| 851 | try testing.expectEqual(&symbols[1], find(symbols, 299).?); | |
| 852 | ||
| 853 | try testing.expectEqual(&symbols[2], find(symbols, 300).?); | |
| 854 | try testing.expectEqual(&symbols[2], find(symbols, 301).?); | |
| 855 | try testing.expectEqual(&symbols[2], find(symbols, 5000).?); | |
| 856 | } | |
| 857 | }; | |
| 858 | test { | |
| 859 | _ = MachoSymbol; | |
| 860 | } | |
| 861 | ||
| 862 | /// Uses `mmap` to map the file at `path` into memory. | |
| 863 | fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 { | |
| 864 | const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { | |
| 865 | error.FileNotFound => return error.MissingDebugInfo, | |
| 866 | else => return error.ReadFailed, | |
| 867 | }; | |
| 868 | defer file.close(); | |
| 869 | ||
| 870 | const file_end_pos = file.getEndPos() catch |err| switch (err) { | |
| 871 | error.Unexpected => |e| return e, | |
| 872 | else => return error.ReadFailed, | |
| 873 | }; | |
| 874 | const file_len = std.math.cast(usize, file_end_pos) orelse return error.InvalidDebugInfo; | |
| 875 | ||
| 876 | return posix.mmap( | |
| 877 | null, | |
| 878 | file_len, | |
| 879 | posix.PROT.READ, | |
| 880 | .{ .TYPE = .SHARED }, | |
| 881 | file.handle, | |
| 882 | 0, | |
| 883 | ) catch |err| switch (err) { | |
| 884 | error.Unexpected => |e| return e, | |
| 885 | else => return error.ReadFailed, | |
| 886 | }; | |
| 887 | } | |
| 888 | ||
| 889 | fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile { | |
| 890 | const mapped_mem = try mapDebugInfoFile(o_file_path); | |
| 891 | errdefer posix.munmap(mapped_mem); | |
| 892 | ||
| 893 | if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo; | |
| 894 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); | |
| 895 | if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo; | |
| 896 | ||
| 897 | const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: { | |
| 898 | var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null; | |
| 899 | var symtab_cmd: ?macho.symtab_command = null; | |
| 900 | var it: macho.LoadCommandIterator = .{ | |
| 901 | .ncmds = hdr.ncmds, | |
| 902 | .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | |
| 903 | }; | |
| 904 | while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 905 | .SEGMENT_64 => seg_cmd = cmd, | |
| 906 | .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo, | |
| 907 | else => {}, | |
| 908 | }; | |
| 909 | break :cmds .{ | |
| 910 | seg_cmd orelse return error.MissingDebugInfo, | |
| 911 | symtab_cmd orelse return error.MissingDebugInfo, | |
| 912 | }; | |
| 913 | }; | |
| 914 | ||
| 915 | if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo; | |
| 916 | if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo; | |
| 917 | const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1]; | |
| 918 | ||
| 919 | const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64); | |
| 920 | if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo; | |
| 921 | const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]); | |
| 922 | ||
| 923 | // TODO handle tentative (common) symbols | |
| 924 | var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty; | |
| 925 | defer symbols_by_name.deinit(gpa); | |
| 926 | try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab.len)); | |
| 927 | for (symtab, 0..) |sym, sym_index| { | |
| 928 | if (sym.n_strx == 0) continue; | |
| 929 | switch (sym.n_type.bits.type) { | |
| 930 | .undf => continue, // includes tentative symbols | |
| 931 | .abs => continue, | |
| 932 | else => {}, | |
| 933 | } | |
| 934 | const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); | |
| 935 | const gop = symbols_by_name.getOrPutAssumeCapacityAdapted( | |
| 936 | @as([]const u8, sym_name), | |
| 937 | @as(OFile.SymbolAdapter, .{ .strtab = strtab, .symtab = symtab }), | |
| 938 | ); | |
| 939 | if (gop.found_existing) return error.InvalidDebugInfo; | |
| 940 | gop.key_ptr.* = @intCast(sym_index); | |
| 941 | } | |
| 942 | ||
| 943 | var sections: Dwarf.SectionArray = @splat(null); | |
| 944 | for (seg_cmd.getSections()) |sect| { | |
| 945 | if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; | |
| 946 | ||
| 947 | const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { | |
| 948 | if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i; | |
| 949 | } else continue; | |
| 950 | ||
| 951 | if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo; | |
| 952 | const section_bytes = mapped_mem[sect.offset..][0..sect.size]; | |
| 953 | sections[section_index] = .{ | |
| 954 | .data = section_bytes, | |
| 955 | .owned = false, | |
| 956 | }; | |
| 957 | } | |
| 958 | ||
| 959 | const missing_debug_info = | |
| 960 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | |
| 961 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | |
| 962 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | |
| 963 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; | |
| 964 | if (missing_debug_info) return error.MissingDebugInfo; | |
| 965 | ||
| 966 | var dwarf: Dwarf = .{ .sections = sections }; | |
| 967 | errdefer dwarf.deinit(gpa); | |
| 968 | try dwarf.open(gpa, native_endian); | |
| 969 | ||
| 970 | return .{ | |
| 971 | .mapped_memory = mapped_mem, | |
| 972 | .dwarf = dwarf, | |
| 973 | .strtab = strtab, | |
| 974 | .symtab = symtab, | |
| 975 | .symbols_by_name = symbols_by_name.move(), | |
| 976 | }; | |
| 977 | } | |
| 978 | ||
| 979 | const std = @import("std"); | |
| 980 | const Allocator = std.mem.Allocator; | |
| 981 | const Dwarf = std.debug.Dwarf; | |
| 982 | const Error = std.debug.SelfInfoError; | |
| 983 | const assert = std.debug.assert; | |
| 984 | const posix = std.posix; | |
| 985 | const macho = std.macho; | |
| 986 | const mem = std.mem; | |
| 987 | const testing = std.testing; | |
| 988 | const dwarfRegNative = std.debug.Dwarf.SelfUnwinder.regNative; | |
| 989 | ||
| 990 | const builtin = @import("builtin"); | |
| 991 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 992 | ||
| 993 | const SelfInfo = @This(); |
lib/std/debug/SelfInfo/Elf.zig created+427| ... | ... | @@ -0,0 +1,427 @@ |
| 1 | rwlock: std.Thread.RwLock, | |
| 2 | ||
| 3 | modules: std.ArrayList(Module), | |
| 4 | ranges: std.ArrayList(Module.Range), | |
| 5 | ||
| 6 | unwind_cache: if (can_unwind) ?[]Dwarf.SelfUnwinder.CacheEntry else ?noreturn, | |
| 7 | ||
| 8 | pub const init: SelfInfo = .{ | |
| 9 | .rwlock = .{}, | |
| 10 | .modules = .empty, | |
| 11 | .ranges = .empty, | |
| 12 | .unwind_cache = null, | |
| 13 | }; | |
| 14 | pub fn deinit(si: *SelfInfo, gpa: Allocator) void { | |
| 15 | for (si.modules.items) |*mod| { | |
| 16 | unwind: { | |
| 17 | const u = &(mod.unwind orelse break :unwind catch break :unwind); | |
| 18 | for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa); | |
| 19 | } | |
| 20 | loaded: { | |
| 21 | const l = &(mod.loaded_elf orelse break :loaded catch break :loaded); | |
| 22 | l.file.deinit(gpa); | |
| 23 | } | |
| 24 | } | |
| 25 | ||
| 26 | si.modules.deinit(gpa); | |
| 27 | si.ranges.deinit(gpa); | |
| 28 | if (si.unwind_cache) |cache| gpa.free(cache); | |
| 29 | } | |
| 30 | ||
| 31 | pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol { | |
| 32 | const module = try si.findModule(gpa, address, .exclusive); | |
| 33 | defer si.rwlock.unlock(); | |
| 34 | ||
| 35 | const vaddr = address - module.load_offset; | |
| 36 | ||
| 37 | const loaded_elf = try module.getLoadedElf(gpa); | |
| 38 | if (loaded_elf.file.dwarf) |*dwarf| { | |
| 39 | if (!loaded_elf.scanned_dwarf) { | |
| 40 | dwarf.open(gpa, native_endian) catch |err| switch (err) { | |
| 41 | error.InvalidDebugInfo, | |
| 42 | error.MissingDebugInfo, | |
| 43 | error.OutOfMemory, | |
| 44 | => |e| return e, | |
| 45 | error.EndOfStream, | |
| 46 | error.Overflow, | |
| 47 | error.ReadFailed, | |
| 48 | error.StreamTooLong, | |
| 49 | => return error.InvalidDebugInfo, | |
| 50 | }; | |
| 51 | loaded_elf.scanned_dwarf = true; | |
| 52 | } | |
| 53 | if (dwarf.getSymbol(gpa, native_endian, vaddr)) |sym| { | |
| 54 | return sym; | |
| 55 | } else |err| switch (err) { | |
| 56 | error.MissingDebugInfo => {}, | |
| 57 | ||
| 58 | error.InvalidDebugInfo, | |
| 59 | error.OutOfMemory, | |
| 60 | => |e| return e, | |
| 61 | ||
| 62 | error.ReadFailed, | |
| 63 | error.EndOfStream, | |
| 64 | error.Overflow, | |
| 65 | error.StreamTooLong, | |
| 66 | => return error.InvalidDebugInfo, | |
| 67 | } | |
| 68 | } | |
| 69 | // When DWARF is unavailable, fall back to searching the symtab. | |
| 70 | return loaded_elf.file.searchSymtab(gpa, vaddr) catch |err| switch (err) { | |
| 71 | error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo, | |
| 72 | error.BadSymtab => return error.InvalidDebugInfo, | |
| 73 | error.OutOfMemory => |e| return e, | |
| 74 | }; | |
| 75 | } | |
| 76 | pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { | |
| 77 | const module = try si.findModule(gpa, address, .shared); | |
| 78 | defer si.rwlock.unlockShared(); | |
| 79 | if (module.name.len == 0) return error.MissingDebugInfo; | |
| 80 | return module.name; | |
| 81 | } | |
| 82 | ||
| 83 | pub const can_unwind: bool = s: { | |
| 84 | // Notably, we are yet to support unwinding on ARM. There, unwinding is not done through | |
| 85 | // `.eh_frame`, but instead with the `.ARM.exidx` section, which has a different format. | |
| 86 | const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) { | |
| 87 | .linux => &.{ .x86, .x86_64, .aarch64, .aarch64_be }, | |
| 88 | .netbsd => &.{ .x86, .x86_64, .aarch64, .aarch64_be }, | |
| 89 | .freebsd => &.{ .x86_64, .aarch64, .aarch64_be }, | |
| 90 | .openbsd => &.{.x86_64}, | |
| 91 | .solaris => &.{ .x86, .x86_64 }, | |
| 92 | .illumos => &.{ .x86, .x86_64 }, | |
| 93 | else => unreachable, | |
| 94 | }; | |
| 95 | for (archs) |a| { | |
| 96 | if (builtin.target.cpu.arch == a) break :s true; | |
| 97 | } | |
| 98 | break :s false; | |
| 99 | }; | |
| 100 | comptime { | |
| 101 | if (can_unwind) { | |
| 102 | std.debug.assert(Dwarf.supportsUnwinding(&builtin.target)); | |
| 103 | } | |
| 104 | } | |
| 105 | pub const UnwindContext = Dwarf.SelfUnwinder; | |
| 106 | pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { | |
| 107 | comptime assert(can_unwind); | |
| 108 | ||
| 109 | { | |
| 110 | si.rwlock.lockShared(); | |
| 111 | defer si.rwlock.unlockShared(); | |
| 112 | if (si.unwind_cache) |cache| { | |
| 113 | if (Dwarf.SelfUnwinder.CacheEntry.find(cache, context.pc)) |entry| { | |
| 114 | return context.next(gpa, entry); | |
| 115 | } | |
| 116 | } | |
| 117 | } | |
| 118 | ||
| 119 | const module = try si.findModule(gpa, context.pc, .exclusive); | |
| 120 | defer si.rwlock.unlock(); | |
| 121 | ||
| 122 | if (si.unwind_cache == null) { | |
| 123 | si.unwind_cache = try gpa.alloc(Dwarf.SelfUnwinder.CacheEntry, 2048); | |
| 124 | @memset(si.unwind_cache.?, .empty); | |
| 125 | } | |
| 126 | ||
| 127 | const unwind_sections = try module.getUnwindSections(gpa); | |
| 128 | for (unwind_sections) |*unwind| { | |
| 129 | if (context.computeRules(gpa, unwind, module.load_offset, null)) |entry| { | |
| 130 | entry.populate(si.unwind_cache.?); | |
| 131 | return context.next(gpa, &entry); | |
| 132 | } else |err| switch (err) { | |
| 133 | error.MissingDebugInfo => continue, | |
| 134 | ||
| 135 | error.InvalidDebugInfo, | |
| 136 | error.UnsupportedDebugInfo, | |
| 137 | error.OutOfMemory, | |
| 138 | => |e| return e, | |
| 139 | ||
| 140 | error.EndOfStream, | |
| 141 | error.StreamTooLong, | |
| 142 | error.ReadFailed, | |
| 143 | error.Overflow, | |
| 144 | error.InvalidOpcode, | |
| 145 | error.InvalidOperation, | |
| 146 | error.InvalidOperand, | |
| 147 | => return error.InvalidDebugInfo, | |
| 148 | ||
| 149 | error.UnimplementedUserOpcode, | |
| 150 | error.UnsupportedAddrSize, | |
| 151 | => return error.UnsupportedDebugInfo, | |
| 152 | } | |
| 153 | } | |
| 154 | return error.MissingDebugInfo; | |
| 155 | } | |
| 156 | ||
| 157 | const Module = struct { | |
| 158 | load_offset: usize, | |
| 159 | name: []const u8, | |
| 160 | build_id: ?[]const u8, | |
| 161 | gnu_eh_frame: ?[]const u8, | |
| 162 | ||
| 163 | /// `null` means unwind information has not yet been loaded. | |
| 164 | unwind: ?(Error!UnwindSections), | |
| 165 | ||
| 166 | /// `null` means the ELF file has not yet been loaded. | |
| 167 | loaded_elf: ?(Error!LoadedElf), | |
| 168 | ||
| 169 | const LoadedElf = struct { | |
| 170 | file: std.debug.ElfFile, | |
| 171 | scanned_dwarf: bool, | |
| 172 | }; | |
| 173 | ||
| 174 | const UnwindSections = struct { | |
| 175 | buf: [2]Dwarf.Unwind, | |
| 176 | len: usize, | |
| 177 | }; | |
| 178 | ||
| 179 | const Range = struct { | |
| 180 | start: usize, | |
| 181 | len: usize, | |
| 182 | /// Index into `modules` | |
| 183 | module_index: usize, | |
| 184 | }; | |
| 185 | ||
| 186 | /// Assumes we already hold an exclusive lock. | |
| 187 | fn getUnwindSections(mod: *Module, gpa: Allocator) Error![]Dwarf.Unwind { | |
| 188 | if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa); | |
| 189 | const us = &(mod.unwind.? catch |err| return err); | |
| 190 | return us.buf[0..us.len]; | |
| 191 | } | |
| 192 | fn loadUnwindSections(mod: *Module, gpa: Allocator) Error!UnwindSections { | |
| 193 | var us: UnwindSections = .{ | |
| 194 | .buf = undefined, | |
| 195 | .len = 0, | |
| 196 | }; | |
| 197 | if (mod.gnu_eh_frame) |section_bytes| { | |
| 198 | const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - mod.load_offset; | |
| 199 | const header = Dwarf.Unwind.EhFrameHeader.parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian) catch |err| switch (err) { | |
| 200 | error.ReadFailed => unreachable, // it's all fixed buffers | |
| 201 | error.InvalidDebugInfo => |e| return e, | |
| 202 | error.EndOfStream, error.Overflow => return error.InvalidDebugInfo, | |
| 203 | error.UnsupportedAddrSize => return error.UnsupportedDebugInfo, | |
| 204 | }; | |
| 205 | us.buf[us.len] = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(@as(usize, @intCast(mod.load_offset + header.eh_frame_vaddr)))); | |
| 206 | us.len += 1; | |
| 207 | } else { | |
| 208 | // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame` | |
| 209 | // section, but we'll have to load the binary to get at it. | |
| 210 | const loaded = try mod.getLoadedElf(gpa); | |
| 211 | // If both are present, we can't just pick one -- the info could be split between them. | |
| 212 | // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one. | |
| 213 | if (loaded.file.debug_frame) |*debug_frame| { | |
| 214 | us.buf[us.len] = .initSection(.debug_frame, debug_frame.vaddr, debug_frame.bytes); | |
| 215 | us.len += 1; | |
| 216 | } | |
| 217 | if (loaded.file.eh_frame) |*eh_frame| { | |
| 218 | us.buf[us.len] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes); | |
| 219 | us.len += 1; | |
| 220 | } | |
| 221 | } | |
| 222 | errdefer for (us.buf[0..us.len]) |*u| u.deinit(gpa); | |
| 223 | for (us.buf[0..us.len]) |*u| u.prepare(gpa, @sizeOf(usize), native_endian, true, false) catch |err| switch (err) { | |
| 224 | error.ReadFailed => unreachable, // it's all fixed buffers | |
| 225 | error.InvalidDebugInfo, | |
| 226 | error.MissingDebugInfo, | |
| 227 | error.OutOfMemory, | |
| 228 | => |e| return e, | |
| 229 | error.EndOfStream, | |
| 230 | error.Overflow, | |
| 231 | error.StreamTooLong, | |
| 232 | error.InvalidOperand, | |
| 233 | error.InvalidOpcode, | |
| 234 | error.InvalidOperation, | |
| 235 | => return error.InvalidDebugInfo, | |
| 236 | error.UnsupportedAddrSize, | |
| 237 | error.UnsupportedDwarfVersion, | |
| 238 | error.UnimplementedUserOpcode, | |
| 239 | => return error.UnsupportedDebugInfo, | |
| 240 | }; | |
| 241 | return us; | |
| 242 | } | |
| 243 | ||
| 244 | /// Assumes we already hold an exclusive lock. | |
| 245 | fn getLoadedElf(mod: *Module, gpa: Allocator) Error!*LoadedElf { | |
| 246 | if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa); | |
| 247 | return if (mod.loaded_elf.?) |*elf| elf else |err| err; | |
| 248 | } | |
| 249 | fn loadElf(mod: *Module, gpa: Allocator) Error!LoadedElf { | |
| 250 | const load_result = if (mod.name.len > 0) res: { | |
| 251 | var file = std.fs.cwd().openFile(mod.name, .{}) catch return error.MissingDebugInfo; | |
| 252 | defer file.close(); | |
| 253 | break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name)); | |
| 254 | } else res: { | |
| 255 | const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) { | |
| 256 | error.OutOfMemory => |e| return e, | |
| 257 | else => return error.ReadFailed, | |
| 258 | }; | |
| 259 | defer gpa.free(path); | |
| 260 | var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo; | |
| 261 | defer file.close(); | |
| 262 | break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(path)); | |
| 263 | }; | |
| 264 | ||
| 265 | var elf_file = load_result catch |err| switch (err) { | |
| 266 | error.OutOfMemory, | |
| 267 | error.Unexpected, | |
| 268 | => |e| return e, | |
| 269 | ||
| 270 | error.Overflow, | |
| 271 | error.TruncatedElfFile, | |
| 272 | error.InvalidCompressedSection, | |
| 273 | error.InvalidElfMagic, | |
| 274 | error.InvalidElfVersion, | |
| 275 | error.InvalidElfClass, | |
| 276 | error.InvalidElfEndian, | |
| 277 | => return error.InvalidDebugInfo, | |
| 278 | ||
| 279 | error.SystemResources, | |
| 280 | error.MemoryMappingNotSupported, | |
| 281 | error.AccessDenied, | |
| 282 | error.LockedMemoryLimitExceeded, | |
| 283 | error.ProcessFdQuotaExceeded, | |
| 284 | error.SystemFdQuotaExceeded, | |
| 285 | => return error.ReadFailed, | |
| 286 | }; | |
| 287 | errdefer elf_file.deinit(gpa); | |
| 288 | ||
| 289 | if (elf_file.endian != native_endian) return error.InvalidDebugInfo; | |
| 290 | if (elf_file.is_64 != (@sizeOf(usize) == 8)) return error.InvalidDebugInfo; | |
| 291 | ||
| 292 | return .{ | |
| 293 | .file = elf_file, | |
| 294 | .scanned_dwarf = false, | |
| 295 | }; | |
| 296 | } | |
| 297 | }; | |
| 298 | ||
| 299 | fn findModule(si: *SelfInfo, gpa: Allocator, address: usize, lock: enum { shared, exclusive }) Error!*Module { | |
| 300 | // With the requested lock, scan the module ranges looking for `address`. | |
| 301 | switch (lock) { | |
| 302 | .shared => si.rwlock.lockShared(), | |
| 303 | .exclusive => si.rwlock.lock(), | |
| 304 | } | |
| 305 | for (si.ranges.items) |*range| { | |
| 306 | if (address >= range.start and address < range.start + range.len) { | |
| 307 | return &si.modules.items[range.module_index]; | |
| 308 | } | |
| 309 | } | |
| 310 | // The address wasn't in a known range. We will rebuild the module/range lists, since it's possible | |
| 311 | // a new module was loaded. Upgrade to an exclusive lock if necessary. | |
| 312 | switch (lock) { | |
| 313 | .shared => { | |
| 314 | si.rwlock.unlockShared(); | |
| 315 | si.rwlock.lock(); | |
| 316 | }, | |
| 317 | .exclusive => {}, | |
| 318 | } | |
| 319 | // Rebuild module list with the exclusive lock. | |
| 320 | { | |
| 321 | errdefer si.rwlock.unlock(); | |
| 322 | for (si.modules.items) |*mod| { | |
| 323 | unwind: { | |
| 324 | const u = &(mod.unwind orelse break :unwind catch break :unwind); | |
| 325 | for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa); | |
| 326 | } | |
| 327 | loaded: { | |
| 328 | const l = &(mod.loaded_elf orelse break :loaded catch break :loaded); | |
| 329 | l.file.deinit(gpa); | |
| 330 | } | |
| 331 | } | |
| 332 | si.modules.clearRetainingCapacity(); | |
| 333 | si.ranges.clearRetainingCapacity(); | |
| 334 | var ctx: DlIterContext = .{ .si = si, .gpa = gpa }; | |
| 335 | try std.posix.dl_iterate_phdr(&ctx, error{OutOfMemory}, DlIterContext.callback); | |
| 336 | } | |
| 337 | // Downgrade the lock back to shared if necessary. | |
| 338 | switch (lock) { | |
| 339 | .shared => { | |
| 340 | si.rwlock.unlock(); | |
| 341 | si.rwlock.lockShared(); | |
| 342 | }, | |
| 343 | .exclusive => {}, | |
| 344 | } | |
| 345 | // Scan the newly rebuilt module ranges. | |
| 346 | for (si.ranges.items) |*range| { | |
| 347 | if (address >= range.start and address < range.start + range.len) { | |
| 348 | return &si.modules.items[range.module_index]; | |
| 349 | } | |
| 350 | } | |
| 351 | // Still nothing; unlock and error. | |
| 352 | switch (lock) { | |
| 353 | .shared => si.rwlock.unlockShared(), | |
| 354 | .exclusive => si.rwlock.unlock(), | |
| 355 | } | |
| 356 | return error.MissingDebugInfo; | |
| 357 | } | |
| 358 | const DlIterContext = struct { | |
| 359 | si: *SelfInfo, | |
| 360 | gpa: Allocator, | |
| 361 | ||
| 362 | fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void { | |
| 363 | _ = size; | |
| 364 | ||
| 365 | var build_id: ?[]const u8 = null; | |
| 366 | var gnu_eh_frame: ?[]const u8 = null; | |
| 367 | ||
| 368 | // Populate `build_id` and `gnu_eh_frame` | |
| 369 | for (info.phdr[0..info.phnum]) |phdr| { | |
| 370 | switch (phdr.p_type) { | |
| 371 | std.elf.PT_NOTE => { | |
| 372 | // Look for .note.gnu.build-id | |
| 373 | const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); | |
| 374 | var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]); | |
| 375 | const name_size = r.takeInt(u32, native_endian) catch continue; | |
| 376 | const desc_size = r.takeInt(u32, native_endian) catch continue; | |
| 377 | const note_type = r.takeInt(u32, native_endian) catch continue; | |
| 378 | const name = r.take(name_size) catch continue; | |
| 379 | if (note_type != std.elf.NT_GNU_BUILD_ID) continue; | |
| 380 | if (!std.mem.eql(u8, name, "GNU\x00")) continue; | |
| 381 | const desc = r.take(desc_size) catch continue; | |
| 382 | build_id = desc; | |
| 383 | }, | |
| 384 | std.elf.PT_GNU_EH_FRAME => { | |
| 385 | const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); | |
| 386 | gnu_eh_frame = segment_ptr[0..phdr.p_memsz]; | |
| 387 | }, | |
| 388 | else => {}, | |
| 389 | } | |
| 390 | } | |
| 391 | ||
| 392 | const gpa = context.gpa; | |
| 393 | const si = context.si; | |
| 394 | ||
| 395 | const module_index = si.modules.items.len; | |
| 396 | try si.modules.append(gpa, .{ | |
| 397 | .load_offset = info.addr, | |
| 398 | // Android libc uses NULL instead of "" to mark the main program | |
| 399 | .name = std.mem.sliceTo(info.name, 0) orelse "", | |
| 400 | .build_id = build_id, | |
| 401 | .gnu_eh_frame = gnu_eh_frame, | |
| 402 | .unwind = null, | |
| 403 | .loaded_elf = null, | |
| 404 | }); | |
| 405 | ||
| 406 | for (info.phdr[0..info.phnum]) |phdr| { | |
| 407 | if (phdr.p_type != std.elf.PT_LOAD) continue; | |
| 408 | try context.si.ranges.append(gpa, .{ | |
| 409 | // Overflowing addition handles VSDOs having p_vaddr = 0xffffffffff700000 | |
| 410 | .start = info.addr +% phdr.p_vaddr, | |
| 411 | .len = phdr.p_memsz, | |
| 412 | .module_index = module_index, | |
| 413 | }); | |
| 414 | } | |
| 415 | } | |
| 416 | }; | |
| 417 | ||
| 418 | const std = @import("std"); | |
| 419 | const Allocator = std.mem.Allocator; | |
| 420 | const Dwarf = std.debug.Dwarf; | |
| 421 | const Error = std.debug.SelfInfoError; | |
| 422 | const assert = std.debug.assert; | |
| 423 | ||
| 424 | const builtin = @import("builtin"); | |
| 425 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 426 | ||
| 427 | const SelfInfo = @This(); |
lib/std/debug/SelfInfo/Windows.zig created+559| ... | ... | @@ -0,0 +1,559 @@ |
| 1 | mutex: std.Thread.Mutex, | |
| 2 | modules: std.ArrayListUnmanaged(Module), | |
| 3 | module_name_arena: std.heap.ArenaAllocator.State, | |
| 4 | ||
| 5 | pub const init: SelfInfo = .{ | |
| 6 | .mutex = .{}, | |
| 7 | .modules = .empty, | |
| 8 | .module_name_arena = .{}, | |
| 9 | }; | |
| 10 | pub fn deinit(si: *SelfInfo, gpa: Allocator) void { | |
| 11 | for (si.modules.items) |*module| { | |
| 12 | di: { | |
| 13 | const di = &(module.di orelse break :di catch break :di); | |
| 14 | di.deinit(gpa); | |
| 15 | } | |
| 16 | } | |
| 17 | si.modules.deinit(gpa); | |
| 18 | ||
| 19 | var module_name_arena = si.module_name_arena.promote(gpa); | |
| 20 | module_name_arena.deinit(); | |
| 21 | } | |
| 22 | ||
| 23 | pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol { | |
| 24 | si.mutex.lock(); | |
| 25 | defer si.mutex.unlock(); | |
| 26 | const module = try si.findModule(gpa, address); | |
| 27 | const di = try module.getDebugInfo(gpa); | |
| 28 | return di.getSymbol(gpa, address - module.base_address); | |
| 29 | } | |
| 30 | pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { | |
| 31 | si.mutex.lock(); | |
| 32 | defer si.mutex.unlock(); | |
| 33 | const module = try si.findModule(gpa, address); | |
| 34 | return module.name; | |
| 35 | } | |
| 36 | ||
| 37 | pub const can_unwind: bool = switch (builtin.cpu.arch) { | |
| 38 | else => true, | |
| 39 | // On x86, `RtlVirtualUnwind` does not exist. We could in theory use `RtlCaptureStackBackTrace` | |
| 40 | // instead, but on x86, it turns out that function is just... doing FP unwinding with esp! It's | |
| 41 | // hard to find implementation details to confirm that, but the most authoritative source I have | |
| 42 | // is an entry in the LLVM mailing list from 2020/08/16 which contains this quote: | |
| 43 | // | |
| 44 | // > x86 doesn't have what most architectures would consider an "unwinder" in the sense of | |
| 45 | // > restoring registers; there is simply a linked list of frames that participate in SEH and | |
| 46 | // > that desire to be called for a dynamic unwind operation, so RtlCaptureStackBackTrace | |
| 47 | // > assumes that EBP-based frames are in use and walks an EBP-based frame chain on x86 - not | |
| 48 | // > all x86 code is written with EBP-based frames so while even though we generally build the | |
| 49 | // > OS that way, you might always run the risk of encountering external code that uses EBP as a | |
| 50 | // > general purpose register for which such an unwind attempt for a stack trace would fail. | |
| 51 | // | |
| 52 | // Regardless, it's easy to effectively confirm this hypothesis just by compiling some code with | |
| 53 | // `-fomit-frame-pointer -OReleaseFast` and observing that `RtlCaptureStackBackTrace` returns an | |
| 54 | // empty trace when it's called in such an application. Note that without `-OReleaseFast` or | |
| 55 | // similar, LLVM seems reluctant to ever clobber ebp, so you'll get a trace returned which just | |
| 56 | // contains all of the kernel32/ntdll frames but none of your own. Don't be deceived---this is | |
| 57 | // just coincidental! | |
| 58 | // | |
| 59 | // Anyway, the point is, the only stack walking primitive on x86-windows is FP unwinding. We | |
| 60 | // *could* ask Microsoft to do that for us with `RtlCaptureStackBackTrace`... but better to just | |
| 61 | // use our existing FP unwinder in `std.debug`! | |
| 62 | .x86 => false, | |
| 63 | }; | |
| 64 | pub const UnwindContext = struct { | |
| 65 | pc: usize, | |
| 66 | cur: windows.CONTEXT, | |
| 67 | history_table: windows.UNWIND_HISTORY_TABLE, | |
| 68 | pub fn init(ctx: *const std.debug.cpu_context.Native) UnwindContext { | |
| 69 | return .{ | |
| 70 | .pc = @returnAddress(), | |
| 71 | .cur = switch (builtin.cpu.arch) { | |
| 72 | .x86_64 => std.mem.zeroInit(windows.CONTEXT, .{ | |
| 73 | .Rax = ctx.gprs.get(.rax), | |
| 74 | .Rcx = ctx.gprs.get(.rcx), | |
| 75 | .Rdx = ctx.gprs.get(.rdx), | |
| 76 | .Rbx = ctx.gprs.get(.rbx), | |
| 77 | .Rsp = ctx.gprs.get(.rsp), | |
| 78 | .Rbp = ctx.gprs.get(.rbp), | |
| 79 | .Rsi = ctx.gprs.get(.rsi), | |
| 80 | .Rdi = ctx.gprs.get(.rdi), | |
| 81 | .R8 = ctx.gprs.get(.r8), | |
| 82 | .R9 = ctx.gprs.get(.r9), | |
| 83 | .R10 = ctx.gprs.get(.r10), | |
| 84 | .R11 = ctx.gprs.get(.r11), | |
| 85 | .R12 = ctx.gprs.get(.r12), | |
| 86 | .R13 = ctx.gprs.get(.r13), | |
| 87 | .R14 = ctx.gprs.get(.r14), | |
| 88 | .R15 = ctx.gprs.get(.r15), | |
| 89 | .Rip = ctx.gprs.get(.rip), | |
| 90 | }), | |
| 91 | .aarch64, .aarch64_be => .{ | |
| 92 | .ContextFlags = 0, | |
| 93 | .Cpsr = 0, | |
| 94 | .DUMMYUNIONNAME = .{ .X = ctx.x }, | |
| 95 | .Sp = ctx.sp, | |
| 96 | .Pc = ctx.pc, | |
| 97 | .V = @splat(.{ .B = @splat(0) }), | |
| 98 | .Fpcr = 0, | |
| 99 | .Fpsr = 0, | |
| 100 | .Bcr = @splat(0), | |
| 101 | .Bvr = @splat(0), | |
| 102 | .Wcr = @splat(0), | |
| 103 | .Wvr = @splat(0), | |
| 104 | }, | |
| 105 | .thumb => .{ | |
| 106 | .ContextFlags = 0, | |
| 107 | .R0 = ctx.r[0], | |
| 108 | .R1 = ctx.r[1], | |
| 109 | .R2 = ctx.r[2], | |
| 110 | .R3 = ctx.r[3], | |
| 111 | .R4 = ctx.r[4], | |
| 112 | .R5 = ctx.r[5], | |
| 113 | .R6 = ctx.r[6], | |
| 114 | .R7 = ctx.r[7], | |
| 115 | .R8 = ctx.r[8], | |
| 116 | .R9 = ctx.r[9], | |
| 117 | .R10 = ctx.r[10], | |
| 118 | .R11 = ctx.r[11], | |
| 119 | .R12 = ctx.r[12], | |
| 120 | .Sp = ctx.r[13], | |
| 121 | .Lr = ctx.r[14], | |
| 122 | .Pc = ctx.r[15], | |
| 123 | .Cpsr = 0, | |
| 124 | .Fpcsr = 0, | |
| 125 | .Padding = 0, | |
| 126 | .DUMMYUNIONNAME = .{ .S = @splat(0) }, | |
| 127 | .Bvr = @splat(0), | |
| 128 | .Bcr = @splat(0), | |
| 129 | .Wvr = @splat(0), | |
| 130 | .Wcr = @splat(0), | |
| 131 | .Padding2 = @splat(0), | |
| 132 | }, | |
| 133 | else => comptime unreachable, | |
| 134 | }, | |
| 135 | .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE), | |
| 136 | }; | |
| 137 | } | |
| 138 | pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void { | |
| 139 | _ = ctx; | |
| 140 | _ = gpa; | |
| 141 | } | |
| 142 | pub fn getFp(ctx: *UnwindContext) usize { | |
| 143 | return ctx.cur.getRegs().bp; | |
| 144 | } | |
| 145 | }; | |
| 146 | pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { | |
| 147 | _ = si; | |
| 148 | _ = gpa; | |
| 149 | ||
| 150 | const current_regs = context.cur.getRegs(); | |
| 151 | var image_base: windows.DWORD64 = undefined; | |
| 152 | if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &context.history_table)) |runtime_function| { | |
| 153 | var handler_data: ?*anyopaque = null; | |
| 154 | var establisher_frame: u64 = undefined; | |
| 155 | _ = windows.ntdll.RtlVirtualUnwind( | |
| 156 | windows.UNW_FLAG_NHANDLER, | |
| 157 | image_base, | |
| 158 | current_regs.ip, | |
| 159 | runtime_function, | |
| 160 | &context.cur, | |
| 161 | &handler_data, | |
| 162 | &establisher_frame, | |
| 163 | null, | |
| 164 | ); | |
| 165 | } else { | |
| 166 | // leaf function | |
| 167 | context.cur.setIp(@as(*const usize, @ptrFromInt(current_regs.sp)).*); | |
| 168 | context.cur.setSp(current_regs.sp + @sizeOf(usize)); | |
| 169 | } | |
| 170 | ||
| 171 | const next_regs = context.cur.getRegs(); | |
| 172 | const tib = &windows.teb().NtTib; | |
| 173 | if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) { | |
| 174 | context.pc = 0; | |
| 175 | return 0; | |
| 176 | } | |
| 177 | // Like `DwarfUnwindContext.unwindFrame`, adjust our next lookup pc in case the `call` was this | |
| 178 | // function's last instruction making `next_regs.ip` one byte past its end. | |
| 179 | context.pc = next_regs.ip -| 1; | |
| 180 | return next_regs.ip; | |
| 181 | } | |
| 182 | ||
| 183 | const Module = struct { | |
| 184 | base_address: usize, | |
| 185 | size: u32, | |
| 186 | name: []const u8, | |
| 187 | handle: windows.HMODULE, | |
| 188 | ||
| 189 | di: ?(Error!DebugInfo), | |
| 190 | ||
| 191 | const DebugInfo = struct { | |
| 192 | arena: std.heap.ArenaAllocator.State, | |
| 193 | coff_image_base: u64, | |
| 194 | mapped_file: ?MappedFile, | |
| 195 | dwarf: ?Dwarf, | |
| 196 | pdb: ?Pdb, | |
| 197 | coff_section_headers: []coff.SectionHeader, | |
| 198 | ||
| 199 | const MappedFile = struct { | |
| 200 | file: fs.File, | |
| 201 | section_handle: windows.HANDLE, | |
| 202 | section_view: []const u8, | |
| 203 | fn deinit(mf: *const MappedFile) void { | |
| 204 | const process_handle = windows.GetCurrentProcess(); | |
| 205 | assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mf.section_view.ptr)) == .SUCCESS); | |
| 206 | windows.CloseHandle(mf.section_handle); | |
| 207 | mf.file.close(); | |
| 208 | } | |
| 209 | }; | |
| 210 | ||
| 211 | fn deinit(di: *DebugInfo, gpa: Allocator) void { | |
| 212 | if (di.dwarf) |*dwarf| dwarf.deinit(gpa); | |
| 213 | if (di.pdb) |*pdb| { | |
| 214 | pdb.file_reader.file.close(); | |
| 215 | pdb.deinit(); | |
| 216 | } | |
| 217 | if (di.mapped_file) |*mf| mf.deinit(); | |
| 218 | ||
| 219 | var arena = di.arena.promote(gpa); | |
| 220 | arena.deinit(); | |
| 221 | } | |
| 222 | ||
| 223 | fn getSymbol(di: *DebugInfo, gpa: Allocator, vaddr: usize) Error!std.debug.Symbol { | |
| 224 | pdb: { | |
| 225 | const pdb = &(di.pdb orelse break :pdb); | |
| 226 | var coff_section: *align(1) const coff.SectionHeader = undefined; | |
| 227 | const mod_index = for (pdb.sect_contribs) |sect_contrib| { | |
| 228 | if (sect_contrib.section > di.coff_section_headers.len) continue; | |
| 229 | // Remember that SectionContribEntry.Section is 1-based. | |
| 230 | coff_section = &di.coff_section_headers[sect_contrib.section - 1]; | |
| 231 | ||
| 232 | const vaddr_start = coff_section.virtual_address + sect_contrib.offset; | |
| 233 | const vaddr_end = vaddr_start + sect_contrib.size; | |
| 234 | if (vaddr >= vaddr_start and vaddr < vaddr_end) { | |
| 235 | break sect_contrib.module_index; | |
| 236 | } | |
| 237 | } else { | |
| 238 | // we have no information to add to the address | |
| 239 | break :pdb; | |
| 240 | }; | |
| 241 | const module = pdb.getModule(mod_index) catch |err| switch (err) { | |
| 242 | error.InvalidDebugInfo, | |
| 243 | error.MissingDebugInfo, | |
| 244 | error.OutOfMemory, | |
| 245 | => |e| return e, | |
| 246 | ||
| 247 | error.ReadFailed, | |
| 248 | error.EndOfStream, | |
| 249 | => return error.InvalidDebugInfo, | |
| 250 | } orelse { | |
| 251 | return error.InvalidDebugInfo; // bad module index | |
| 252 | }; | |
| 253 | return .{ | |
| 254 | .name = pdb.getSymbolName(module, vaddr - coff_section.virtual_address), | |
| 255 | .compile_unit_name = fs.path.basename(module.obj_file_name), | |
| 256 | .source_location = pdb.getLineNumberInfo(module, vaddr - coff_section.virtual_address) catch null, | |
| 257 | }; | |
| 258 | } | |
| 259 | dwarf: { | |
| 260 | const dwarf = &(di.dwarf orelse break :dwarf); | |
| 261 | const dwarf_address = vaddr + di.coff_image_base; | |
| 262 | return dwarf.getSymbol(gpa, native_endian, dwarf_address) catch |err| switch (err) { | |
| 263 | error.MissingDebugInfo => break :dwarf, | |
| 264 | ||
| 265 | error.InvalidDebugInfo, | |
| 266 | error.OutOfMemory, | |
| 267 | => |e| return e, | |
| 268 | ||
| 269 | error.ReadFailed, | |
| 270 | error.EndOfStream, | |
| 271 | error.Overflow, | |
| 272 | error.StreamTooLong, | |
| 273 | => return error.InvalidDebugInfo, | |
| 274 | }; | |
| 275 | } | |
| 276 | return error.MissingDebugInfo; | |
| 277 | } | |
| 278 | }; | |
| 279 | ||
| 280 | fn getDebugInfo(module: *Module, gpa: Allocator) Error!*DebugInfo { | |
| 281 | if (module.di == null) module.di = loadDebugInfo(module, gpa); | |
| 282 | return if (module.di.?) |*di| di else |err| err; | |
| 283 | } | |
| 284 | fn loadDebugInfo(module: *const Module, gpa: Allocator) Error!DebugInfo { | |
| 285 | const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address); | |
| 286 | const mapped = mapped_ptr[0..module.size]; | |
| 287 | var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo; | |
| 288 | ||
| 289 | var arena_instance: std.heap.ArenaAllocator = .init(gpa); | |
| 290 | errdefer arena_instance.deinit(); | |
| 291 | const arena = arena_instance.allocator(); | |
| 292 | ||
| 293 | // The string table is not mapped into memory by the loader, so if a section name is in the | |
| 294 | // string table then we have to map the full image file from disk. This can happen when | |
| 295 | // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. | |
| 296 | const mapped_file: ?DebugInfo.MappedFile = mapped: { | |
| 297 | if (!coff_obj.strtabRequired()) break :mapped null; | |
| 298 | var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; | |
| 299 | name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present | |
| 300 | const process_handle = windows.GetCurrentProcess(); | |
| 301 | const len = windows.kernel32.GetModuleFileNameExW( | |
| 302 | process_handle, | |
| 303 | module.handle, | |
| 304 | name_buffer[4..], | |
| 305 | windows.PATH_MAX_WIDE, | |
| 306 | ); | |
| 307 | if (len == 0) return error.MissingDebugInfo; | |
| 308 | const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { | |
| 309 | error.Unexpected => |e| return e, | |
| 310 | error.FileNotFound => return error.MissingDebugInfo, | |
| 311 | ||
| 312 | error.FileTooBig, | |
| 313 | error.IsDir, | |
| 314 | error.NotDir, | |
| 315 | error.SymLinkLoop, | |
| 316 | error.NameTooLong, | |
| 317 | error.InvalidUtf8, | |
| 318 | error.InvalidWtf8, | |
| 319 | error.BadPathName, | |
| 320 | => return error.InvalidDebugInfo, | |
| 321 | ||
| 322 | error.SystemResources, | |
| 323 | error.WouldBlock, | |
| 324 | error.AccessDenied, | |
| 325 | error.ProcessNotFound, | |
| 326 | error.PermissionDenied, | |
| 327 | error.NoSpaceLeft, | |
| 328 | error.DeviceBusy, | |
| 329 | error.NoDevice, | |
| 330 | error.SharingViolation, | |
| 331 | error.PathAlreadyExists, | |
| 332 | error.PipeBusy, | |
| 333 | error.NetworkNotFound, | |
| 334 | error.AntivirusInterference, | |
| 335 | error.ProcessFdQuotaExceeded, | |
| 336 | error.SystemFdQuotaExceeded, | |
| 337 | error.FileLocksNotSupported, | |
| 338 | error.FileBusy, | |
| 339 | => return error.ReadFailed, | |
| 340 | }; | |
| 341 | errdefer coff_file.close(); | |
| 342 | var section_handle: windows.HANDLE = undefined; | |
| 343 | const create_section_rc = windows.ntdll.NtCreateSection( | |
| 344 | &section_handle, | |
| 345 | windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, | |
| 346 | null, | |
| 347 | null, | |
| 348 | windows.PAGE_READONLY, | |
| 349 | // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. | |
| 350 | // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. | |
| 351 | windows.SEC_COMMIT, | |
| 352 | coff_file.handle, | |
| 353 | ); | |
| 354 | if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; | |
| 355 | errdefer windows.CloseHandle(section_handle); | |
| 356 | var coff_len: usize = 0; | |
| 357 | var section_view_ptr: ?[*]const u8 = null; | |
| 358 | const map_section_rc = windows.ntdll.NtMapViewOfSection( | |
| 359 | section_handle, | |
| 360 | process_handle, | |
| 361 | @ptrCast(&section_view_ptr), | |
| 362 | null, | |
| 363 | 0, | |
| 364 | null, | |
| 365 | &coff_len, | |
| 366 | .ViewUnmap, | |
| 367 | 0, | |
| 368 | windows.PAGE_READONLY, | |
| 369 | ); | |
| 370 | if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; | |
| 371 | errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr.?)) == .SUCCESS); | |
| 372 | const section_view = section_view_ptr.?[0..coff_len]; | |
| 373 | coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo; | |
| 374 | break :mapped .{ | |
| 375 | .file = coff_file, | |
| 376 | .section_handle = section_handle, | |
| 377 | .section_view = section_view, | |
| 378 | }; | |
| 379 | }; | |
| 380 | errdefer if (mapped_file) |*mf| mf.deinit(); | |
| 381 | ||
| 382 | const coff_image_base = coff_obj.getImageBase(); | |
| 383 | ||
| 384 | var opt_dwarf: ?Dwarf = dwarf: { | |
| 385 | if (coff_obj.getSectionByName(".debug_info") == null) break :dwarf null; | |
| 386 | ||
| 387 | var sections: Dwarf.SectionArray = undefined; | |
| 388 | inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { | |
| 389 | sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| .{ | |
| 390 | .data = try coff_obj.getSectionDataAlloc(section_header, arena), | |
| 391 | .owned = false, | |
| 392 | } else null; | |
| 393 | } | |
| 394 | break :dwarf .{ .sections = sections }; | |
| 395 | }; | |
| 396 | errdefer if (opt_dwarf) |*dwarf| dwarf.deinit(gpa); | |
| 397 | ||
| 398 | if (opt_dwarf) |*dwarf| { | |
| 399 | dwarf.open(gpa, native_endian) catch |err| switch (err) { | |
| 400 | error.Overflow, | |
| 401 | error.EndOfStream, | |
| 402 | error.StreamTooLong, | |
| 403 | error.ReadFailed, | |
| 404 | => return error.InvalidDebugInfo, | |
| 405 | ||
| 406 | error.InvalidDebugInfo, | |
| 407 | error.MissingDebugInfo, | |
| 408 | error.OutOfMemory, | |
| 409 | => |e| return e, | |
| 410 | }; | |
| 411 | } | |
| 412 | ||
| 413 | var opt_pdb: ?Pdb = pdb: { | |
| 414 | const path = coff_obj.getPdbPath() catch { | |
| 415 | return error.InvalidDebugInfo; | |
| 416 | } orelse { | |
| 417 | break :pdb null; | |
| 418 | }; | |
| 419 | const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: { | |
| 420 | break :res std.fs.cwd().openFile(path, .{}); | |
| 421 | } else res: { | |
| 422 | const self_dir = fs.selfExeDirPathAlloc(gpa) catch |err| switch (err) { | |
| 423 | error.OutOfMemory, error.Unexpected => |e| return e, | |
| 424 | else => return error.ReadFailed, | |
| 425 | }; | |
| 426 | defer gpa.free(self_dir); | |
| 427 | const abs_path = try fs.path.join(gpa, &.{ self_dir, path }); | |
| 428 | defer gpa.free(abs_path); | |
| 429 | break :res std.fs.cwd().openFile(abs_path, .{}); | |
| 430 | }; | |
| 431 | const pdb_file = pdb_file_open_result catch |err| switch (err) { | |
| 432 | error.FileNotFound, error.IsDir => break :pdb null, | |
| 433 | else => return error.ReadFailed, | |
| 434 | }; | |
| 435 | errdefer pdb_file.close(); | |
| 436 | ||
| 437 | const pdb_reader = try arena.create(std.fs.File.Reader); | |
| 438 | pdb_reader.* = pdb_file.reader(try arena.alloc(u8, 4096)); | |
| 439 | ||
| 440 | var pdb = Pdb.init(gpa, pdb_reader) catch |err| switch (err) { | |
| 441 | error.OutOfMemory, error.ReadFailed, error.Unexpected => |e| return e, | |
| 442 | else => return error.InvalidDebugInfo, | |
| 443 | }; | |
| 444 | errdefer pdb.deinit(); | |
| 445 | pdb.parseInfoStream() catch |err| switch (err) { | |
| 446 | error.UnknownPDBVersion => return error.UnsupportedDebugInfo, | |
| 447 | error.EndOfStream => return error.InvalidDebugInfo, | |
| 448 | ||
| 449 | error.InvalidDebugInfo, | |
| 450 | error.MissingDebugInfo, | |
| 451 | error.OutOfMemory, | |
| 452 | error.ReadFailed, | |
| 453 | => |e| return e, | |
| 454 | }; | |
| 455 | pdb.parseDbiStream() catch |err| switch (err) { | |
| 456 | error.UnknownPDBVersion => return error.UnsupportedDebugInfo, | |
| 457 | ||
| 458 | error.EndOfStream, | |
| 459 | error.EOF, | |
| 460 | error.StreamTooLong, | |
| 461 | error.WriteFailed, | |
| 462 | => return error.InvalidDebugInfo, | |
| 463 | ||
| 464 | error.InvalidDebugInfo, | |
| 465 | error.OutOfMemory, | |
| 466 | error.ReadFailed, | |
| 467 | => |e| return e, | |
| 468 | }; | |
| 469 | ||
| 470 | if (!std.mem.eql(u8, &coff_obj.guid, &pdb.guid) or coff_obj.age != pdb.age) | |
| 471 | return error.InvalidDebugInfo; | |
| 472 | ||
| 473 | break :pdb pdb; | |
| 474 | }; | |
| 475 | errdefer if (opt_pdb) |*pdb| { | |
| 476 | pdb.file_reader.file.close(); | |
| 477 | pdb.deinit(); | |
| 478 | }; | |
| 479 | ||
| 480 | const coff_section_headers: []coff.SectionHeader = if (opt_pdb != null) csh: { | |
| 481 | break :csh try coff_obj.getSectionHeadersAlloc(arena); | |
| 482 | } else &.{}; | |
| 483 | ||
| 484 | return .{ | |
| 485 | .arena = arena_instance.state, | |
| 486 | .coff_image_base = coff_image_base, | |
| 487 | .mapped_file = mapped_file, | |
| 488 | .dwarf = opt_dwarf, | |
| 489 | .pdb = opt_pdb, | |
| 490 | .coff_section_headers = coff_section_headers, | |
| 491 | }; | |
| 492 | } | |
| 493 | }; | |
| 494 | ||
| 495 | /// Assumes we already hold `si.mutex`. | |
| 496 | fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebugInfo, OutOfMemory, Unexpected }!*Module { | |
| 497 | for (si.modules.items) |*mod| { | |
| 498 | if (address >= mod.base_address and address < mod.base_address + mod.size) { | |
| 499 | return mod; | |
| 500 | } | |
| 501 | } | |
| 502 | ||
| 503 | // A new module might have been loaded; rebuild the list. | |
| 504 | { | |
| 505 | for (si.modules.items) |*mod| { | |
| 506 | const di = &(mod.di orelse continue catch continue); | |
| 507 | di.deinit(gpa); | |
| 508 | } | |
| 509 | si.modules.clearRetainingCapacity(); | |
| 510 | ||
| 511 | var module_name_arena = si.module_name_arena.promote(gpa); | |
| 512 | defer si.module_name_arena = module_name_arena.state; | |
| 513 | _ = module_name_arena.reset(.retain_capacity); | |
| 514 | ||
| 515 | const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); | |
| 516 | if (handle == windows.INVALID_HANDLE_VALUE) { | |
| 517 | return windows.unexpectedError(windows.GetLastError()); | |
| 518 | } | |
| 519 | defer windows.CloseHandle(handle); | |
| 520 | var entry: windows.MODULEENTRY32 = undefined; | |
| 521 | entry.dwSize = @sizeOf(windows.MODULEENTRY32); | |
| 522 | var result = windows.kernel32.Module32First(handle, &entry); | |
| 523 | while (result != 0) : (result = windows.kernel32.Module32Next(handle, &entry)) { | |
| 524 | try si.modules.append(gpa, .{ | |
| 525 | .base_address = @intFromPtr(entry.modBaseAddr), | |
| 526 | .size = entry.modBaseSize, | |
| 527 | .name = try module_name_arena.allocator().dupe( | |
| 528 | u8, | |
| 529 | std.mem.sliceTo(&entry.szModule, 0), | |
| 530 | ), | |
| 531 | .handle = entry.hModule, | |
| 532 | .di = null, | |
| 533 | }); | |
| 534 | } | |
| 535 | } | |
| 536 | ||
| 537 | for (si.modules.items) |*mod| { | |
| 538 | if (address >= mod.base_address and address < mod.base_address + mod.size) { | |
| 539 | return mod; | |
| 540 | } | |
| 541 | } | |
| 542 | ||
| 543 | return error.MissingDebugInfo; | |
| 544 | } | |
| 545 | ||
| 546 | const std = @import("std"); | |
| 547 | const Allocator = std.mem.Allocator; | |
| 548 | const Dwarf = std.debug.Dwarf; | |
| 549 | const Pdb = std.debug.Pdb; | |
| 550 | const Error = std.debug.SelfInfoError; | |
| 551 | const assert = std.debug.assert; | |
| 552 | const coff = std.coff; | |
| 553 | const fs = std.fs; | |
| 554 | const windows = std.os.windows; | |
| 555 | ||
| 556 | const builtin = @import("builtin"); | |
| 557 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 558 | ||
| 559 | const SelfInfo = @This(); |
lib/std/debug/cpu_context.zig created+1028| ... | ... | @@ -0,0 +1,1028 @@ |
| 1 | /// Register state for the native architecture, used by `std.debug` for stack unwinding. | |
| 2 | /// `noreturn` if there is no implementation for the native architecture. | |
| 3 | /// This can be overriden by exposing a declaration `root.debug.CpuContext`. | |
| 4 | pub const Native = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "CpuContext")) | |
| 5 | root.debug.CpuContext | |
| 6 | else switch (native_arch) { | |
| 7 | .x86 => X86, | |
| 8 | .x86_64 => X86_64, | |
| 9 | .arm, .armeb, .thumb, .thumbeb => Arm, | |
| 10 | .aarch64, .aarch64_be => Aarch64, | |
| 11 | else => noreturn, | |
| 12 | }; | |
| 13 | ||
| 14 | pub const DwarfRegisterError = error{ | |
| 15 | InvalidRegister, | |
| 16 | UnsupportedRegister, | |
| 17 | }; | |
| 18 | ||
| 19 | pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native { | |
| 20 | if (signal_ucontext_t == void) return null; | |
| 21 | const uc: *const signal_ucontext_t = @ptrCast(@alignCast(ctx_ptr)); | |
| 22 | return switch (native_arch) { | |
| 23 | .x86 => switch (native_os) { | |
| 24 | .linux, .netbsd, .solaris, .illumos => .{ .gprs = .init(.{ | |
| 25 | .eax = uc.mcontext.gregs[std.posix.REG.EAX], | |
| 26 | .ecx = uc.mcontext.gregs[std.posix.REG.ECX], | |
| 27 | .edx = uc.mcontext.gregs[std.posix.REG.EDX], | |
| 28 | .ebx = uc.mcontext.gregs[std.posix.REG.EBX], | |
| 29 | .esp = uc.mcontext.gregs[std.posix.REG.ESP], | |
| 30 | .ebp = uc.mcontext.gregs[std.posix.REG.EBP], | |
| 31 | .esi = uc.mcontext.gregs[std.posix.REG.ESI], | |
| 32 | .edi = uc.mcontext.gregs[std.posix.REG.EDI], | |
| 33 | .eip = uc.mcontext.gregs[std.posix.REG.EIP], | |
| 34 | }) }, | |
| 35 | else => null, | |
| 36 | }, | |
| 37 | .x86_64 => switch (native_os) { | |
| 38 | .linux, .solaris, .illumos => .{ .gprs = .init(.{ | |
| 39 | .rax = uc.mcontext.gregs[std.posix.REG.RAX], | |
| 40 | .rdx = uc.mcontext.gregs[std.posix.REG.RDX], | |
| 41 | .rcx = uc.mcontext.gregs[std.posix.REG.RCX], | |
| 42 | .rbx = uc.mcontext.gregs[std.posix.REG.RBX], | |
| 43 | .rsi = uc.mcontext.gregs[std.posix.REG.RSI], | |
| 44 | .rdi = uc.mcontext.gregs[std.posix.REG.RDI], | |
| 45 | .rbp = uc.mcontext.gregs[std.posix.REG.RBP], | |
| 46 | .rsp = uc.mcontext.gregs[std.posix.REG.RSP], | |
| 47 | .r8 = uc.mcontext.gregs[std.posix.REG.R8], | |
| 48 | .r9 = uc.mcontext.gregs[std.posix.REG.R9], | |
| 49 | .r10 = uc.mcontext.gregs[std.posix.REG.R10], | |
| 50 | .r11 = uc.mcontext.gregs[std.posix.REG.R11], | |
| 51 | .r12 = uc.mcontext.gregs[std.posix.REG.R12], | |
| 52 | .r13 = uc.mcontext.gregs[std.posix.REG.R13], | |
| 53 | .r14 = uc.mcontext.gregs[std.posix.REG.R14], | |
| 54 | .r15 = uc.mcontext.gregs[std.posix.REG.R15], | |
| 55 | .rip = uc.mcontext.gregs[std.posix.REG.RIP], | |
| 56 | }) }, | |
| 57 | .freebsd => .{ .gprs = .init(.{ | |
| 58 | .rax = uc.mcontext.rax, | |
| 59 | .rdx = uc.mcontext.rdx, | |
| 60 | .rcx = uc.mcontext.rcx, | |
| 61 | .rbx = uc.mcontext.rbx, | |
| 62 | .rsi = uc.mcontext.rsi, | |
| 63 | .rdi = uc.mcontext.rdi, | |
| 64 | .rbp = uc.mcontext.rbp, | |
| 65 | .rsp = uc.mcontext.rsp, | |
| 66 | .r8 = uc.mcontext.r8, | |
| 67 | .r9 = uc.mcontext.r9, | |
| 68 | .r10 = uc.mcontext.r10, | |
| 69 | .r11 = uc.mcontext.r11, | |
| 70 | .r12 = uc.mcontext.r12, | |
| 71 | .r13 = uc.mcontext.r13, | |
| 72 | .r14 = uc.mcontext.r14, | |
| 73 | .r15 = uc.mcontext.r15, | |
| 74 | .rip = uc.mcontext.rip, | |
| 75 | }) }, | |
| 76 | .openbsd => .{ .gprs = .init(.{ | |
| 77 | .rax = @bitCast(uc.sc_rax), | |
| 78 | .rdx = @bitCast(uc.sc_rdx), | |
| 79 | .rcx = @bitCast(uc.sc_rcx), | |
| 80 | .rbx = @bitCast(uc.sc_rbx), | |
| 81 | .rsi = @bitCast(uc.sc_rsi), | |
| 82 | .rdi = @bitCast(uc.sc_rdi), | |
| 83 | .rbp = @bitCast(uc.sc_rbp), | |
| 84 | .rsp = @bitCast(uc.sc_rsp), | |
| 85 | .r8 = @bitCast(uc.sc_r8), | |
| 86 | .r9 = @bitCast(uc.sc_r9), | |
| 87 | .r10 = @bitCast(uc.sc_r10), | |
| 88 | .r11 = @bitCast(uc.sc_r11), | |
| 89 | .r12 = @bitCast(uc.sc_r12), | |
| 90 | .r13 = @bitCast(uc.sc_r13), | |
| 91 | .r14 = @bitCast(uc.sc_r14), | |
| 92 | .r15 = @bitCast(uc.sc_r15), | |
| 93 | .rip = @bitCast(uc.sc_rip), | |
| 94 | }) }, | |
| 95 | .macos, .ios => .{ .gprs = .init(.{ | |
| 96 | .rax = uc.mcontext.ss.rax, | |
| 97 | .rdx = uc.mcontext.ss.rdx, | |
| 98 | .rcx = uc.mcontext.ss.rcx, | |
| 99 | .rbx = uc.mcontext.ss.rbx, | |
| 100 | .rsi = uc.mcontext.ss.rsi, | |
| 101 | .rdi = uc.mcontext.ss.rdi, | |
| 102 | .rbp = uc.mcontext.ss.rbp, | |
| 103 | .rsp = uc.mcontext.ss.rsp, | |
| 104 | .r8 = uc.mcontext.ss.r8, | |
| 105 | .r9 = uc.mcontext.ss.r9, | |
| 106 | .r10 = uc.mcontext.ss.r10, | |
| 107 | .r11 = uc.mcontext.ss.r11, | |
| 108 | .r12 = uc.mcontext.ss.r12, | |
| 109 | .r13 = uc.mcontext.ss.r13, | |
| 110 | .r14 = uc.mcontext.ss.r14, | |
| 111 | .r15 = uc.mcontext.ss.r15, | |
| 112 | .rip = uc.mcontext.ss.rip, | |
| 113 | }) }, | |
| 114 | else => null, | |
| 115 | }, | |
| 116 | .arm, .armeb, .thumb, .thumbeb => switch (builtin.os.tag) { | |
| 117 | .linux => .{ | |
| 118 | .r = .{ | |
| 119 | uc.mcontext.arm_r0, | |
| 120 | uc.mcontext.arm_r1, | |
| 121 | uc.mcontext.arm_r2, | |
| 122 | uc.mcontext.arm_r3, | |
| 123 | uc.mcontext.arm_r4, | |
| 124 | uc.mcontext.arm_r5, | |
| 125 | uc.mcontext.arm_r6, | |
| 126 | uc.mcontext.arm_r7, | |
| 127 | uc.mcontext.arm_r8, | |
| 128 | uc.mcontext.arm_r9, | |
| 129 | uc.mcontext.arm_r10, | |
| 130 | uc.mcontext.arm_fp, // r11 = fp | |
| 131 | uc.mcontext.arm_ip, // r12 = ip | |
| 132 | uc.mcontext.arm_sp, // r13 = sp | |
| 133 | uc.mcontext.arm_lr, // r14 = lr | |
| 134 | uc.mcontext.arm_pc, // r15 = pc | |
| 135 | }, | |
| 136 | }, | |
| 137 | else => null, | |
| 138 | }, | |
| 139 | .aarch64, .aarch64_be => switch (builtin.os.tag) { | |
| 140 | .macos, .ios, .tvos, .watchos, .visionos => .{ | |
| 141 | .x = uc.mcontext.ss.regs ++ @as([2]u64, .{ | |
| 142 | uc.mcontext.ss.fp, // x29 = fp | |
| 143 | uc.mcontext.ss.lr, // x30 = lr | |
| 144 | }), | |
| 145 | .sp = uc.mcontext.ss.sp, | |
| 146 | .pc = uc.mcontext.ss.pc, | |
| 147 | }, | |
| 148 | .netbsd => .{ | |
| 149 | .x = uc.mcontext.gregs[0..31].*, | |
| 150 | .sp = uc.mcontext.gregs[31], | |
| 151 | .pc = uc.mcontext.gregs[32], | |
| 152 | }, | |
| 153 | .freebsd => .{ | |
| 154 | .x = uc.mcontext.gpregs.x ++ @as([1]u64, .{ | |
| 155 | uc.mcontext.gpregs.lr, // x30 = lr | |
| 156 | }), | |
| 157 | .sp = uc.mcontext.gpregs.sp, | |
| 158 | // On aarch64, the register ELR_LR1 defines the address to return to after handling | |
| 159 | // a CPU exception (ELR is "Exception Link Register"). FreeBSD's ucontext_t uses | |
| 160 | // this as the field name, but it's the same thing as the context's PC. | |
| 161 | .pc = uc.mcontext.gpregs.elr, | |
| 162 | }, | |
| 163 | .openbsd => .{ | |
| 164 | .x = uc.sc_x ++ .{uc.sc_lr}, | |
| 165 | .sp = uc.sc_sp, | |
| 166 | // Not a bug; see freebsd above for explanation. | |
| 167 | .pc = uc.sc_elr, | |
| 168 | }, | |
| 169 | .linux => .{ | |
| 170 | .x = uc.mcontext.regs, | |
| 171 | .sp = uc.mcontext.sp, | |
| 172 | .pc = uc.mcontext.pc, | |
| 173 | }, | |
| 174 | else => null, | |
| 175 | }, | |
| 176 | else => null, | |
| 177 | }; | |
| 178 | } | |
| 179 | ||
| 180 | pub fn fromWindowsContext(ctx: *const std.os.windows.CONTEXT) Native { | |
| 181 | return switch (native_arch) { | |
| 182 | .x86 => .{ .gprs = .init(.{ | |
| 183 | .eax = ctx.Eax, | |
| 184 | .ecx = ctx.Ecx, | |
| 185 | .edx = ctx.Edx, | |
| 186 | .ebx = ctx.Ebx, | |
| 187 | .esp = ctx.Esp, | |
| 188 | .ebp = ctx.Ebp, | |
| 189 | .esi = ctx.Esi, | |
| 190 | .edi = ctx.Edi, | |
| 191 | .eip = ctx.Eip, | |
| 192 | }) }, | |
| 193 | .x86_64 => .{ .gprs = .init(.{ | |
| 194 | .rax = ctx.Rax, | |
| 195 | .rdx = ctx.Rdx, | |
| 196 | .rcx = ctx.Rcx, | |
| 197 | .rbx = ctx.Rbx, | |
| 198 | .rsi = ctx.Rsi, | |
| 199 | .rdi = ctx.Rdi, | |
| 200 | .rbp = ctx.Rbp, | |
| 201 | .rsp = ctx.Rsp, | |
| 202 | .r8 = ctx.R8, | |
| 203 | .r9 = ctx.R9, | |
| 204 | .r10 = ctx.R10, | |
| 205 | .r11 = ctx.R11, | |
| 206 | .r12 = ctx.R12, | |
| 207 | .r13 = ctx.R13, | |
| 208 | .r14 = ctx.R14, | |
| 209 | .r15 = ctx.R15, | |
| 210 | .rip = ctx.Rip, | |
| 211 | }) }, | |
| 212 | .aarch64, .aarch64_be => .{ | |
| 213 | .x = ctx.DUMMYUNIONNAME.X[0..31].*, | |
| 214 | .sp = ctx.Sp, | |
| 215 | .pc = ctx.Pc, | |
| 216 | }, | |
| 217 | .thumb => .{ .r = .{ | |
| 218 | ctx.R0, ctx.R1, ctx.R2, ctx.R3, | |
| 219 | ctx.R4, ctx.R5, ctx.R6, ctx.R7, | |
| 220 | ctx.R8, ctx.R9, ctx.R10, ctx.R11, | |
| 221 | ctx.R12, ctx.Sp, ctx.Lr, ctx.Pc, | |
| 222 | } }, | |
| 223 | else => comptime unreachable, | |
| 224 | }; | |
| 225 | } | |
| 226 | ||
| 227 | pub const X86 = struct { | |
| 228 | /// The first 8 registers here intentionally match the order of registers in the x86 instruction | |
| 229 | /// encoding. This order is inherited by the PUSHA instruction and the DWARF register mappings, | |
| 230 | /// among other things. | |
| 231 | pub const Gpr = enum { | |
| 232 | // zig fmt: off | |
| 233 | eax, ecx, edx, ebx, | |
| 234 | esp, ebp, esi, edi, | |
| 235 | eip, | |
| 236 | // zig fmt: on | |
| 237 | }; | |
| 238 | gprs: std.enums.EnumArray(Gpr, u32), | |
| 239 | ||
| 240 | pub inline fn current() X86 { | |
| 241 | var ctx: X86 = undefined; | |
| 242 | asm volatile ( | |
| 243 | \\movl %%eax, 0x00(%%edi) | |
| 244 | \\movl %%ecx, 0x04(%%edi) | |
| 245 | \\movl %%edx, 0x08(%%edi) | |
| 246 | \\movl %%ebx, 0x0c(%%edi) | |
| 247 | \\movl %%esp, 0x10(%%edi) | |
| 248 | \\movl %%ebp, 0x14(%%edi) | |
| 249 | \\movl %%esi, 0x18(%%edi) | |
| 250 | \\movl %%edi, 0x1c(%%edi) | |
| 251 | \\call 1f | |
| 252 | \\1: | |
| 253 | \\popl 0x20(%%edi) | |
| 254 | : | |
| 255 | : [gprs] "{edi}" (&ctx.gprs.values), | |
| 256 | : .{ .memory = true }); | |
| 257 | return ctx; | |
| 258 | } | |
| 259 | ||
| 260 | pub fn dwarfRegisterBytes(ctx: *X86, register_num: u16) DwarfRegisterError![]u8 { | |
| 261 | // System V Application Binary Interface Intel386 Architecture Processor Supplement Version 1.1 | |
| 262 | // § 2.4.2 "DWARF Register Number Mapping" | |
| 263 | switch (register_num) { | |
| 264 | // The order of `Gpr` intentionally matches DWARF's mappings. | |
| 265 | // | |
| 266 | // x86-macos sometimes uses different mappings (ebp and esp are reversed when the unwind | |
| 267 | // information is from `__eh_frame`). This deviation is not considered here, because | |
| 268 | // x86-macos is a deprecated target which is not supported by the Zig Standard Library. | |
| 269 | 0...8 => return @ptrCast(&ctx.gprs.values[register_num]), | |
| 270 | ||
| 271 | 9 => return error.UnsupportedRegister, // rflags | |
| 272 | 11...18 => return error.UnsupportedRegister, // st0 - st7 | |
| 273 | 21...28 => return error.UnsupportedRegister, // xmm0 - xmm7 | |
| 274 | 29...36 => return error.UnsupportedRegister, // mm0 - mm7 | |
| 275 | 39 => return error.UnsupportedRegister, // mxcsr | |
| 276 | 40...45 => return error.UnsupportedRegister, // es, cs, ss, ds, fs, gs | |
| 277 | 48 => return error.UnsupportedRegister, // tr | |
| 278 | 49 => return error.UnsupportedRegister, // ldtr | |
| 279 | 93...94 => return error.UnsupportedRegister, // fs.base, gs.base | |
| 280 | ||
| 281 | else => return error.InvalidRegister, | |
| 282 | } | |
| 283 | } | |
| 284 | }; | |
| 285 | ||
| 286 | pub const X86_64 = struct { | |
| 287 | /// The order here intentionally matches the order of the DWARF register mappings. It's unclear | |
| 288 | /// where those mappings actually originated from---the ordering of the first 4 registers seems | |
| 289 | /// quite unusual---but it is currently convenient for us to match DWARF. | |
| 290 | pub const Gpr = enum { | |
| 291 | // zig fmt: off | |
| 292 | rax, rdx, rcx, rbx, | |
| 293 | rsi, rdi, rbp, rsp, | |
| 294 | r8, r9, r10, r11, | |
| 295 | r12, r13, r14, r15, | |
| 296 | rip, | |
| 297 | // zig fmt: on | |
| 298 | }; | |
| 299 | gprs: std.enums.EnumArray(Gpr, u64), | |
| 300 | ||
| 301 | pub inline fn current() X86_64 { | |
| 302 | var ctx: X86_64 = undefined; | |
| 303 | asm volatile ( | |
| 304 | \\movq %%rax, 0x00(%%rdi) | |
| 305 | \\movq %%rdx, 0x08(%%rdi) | |
| 306 | \\movq %%rcx, 0x10(%%rdi) | |
| 307 | \\movq %%rbx, 0x18(%%rdi) | |
| 308 | \\movq %%rsi, 0x20(%%rdi) | |
| 309 | \\movq %%rdi, 0x28(%%rdi) | |
| 310 | \\movq %%rbp, 0x30(%%rdi) | |
| 311 | \\movq %%rsp, 0x38(%%rdi) | |
| 312 | \\movq %%r8, 0x40(%%rdi) | |
| 313 | \\movq %%r9, 0x48(%%rdi) | |
| 314 | \\movq %%r10, 0x50(%%rdi) | |
| 315 | \\movq %%r11, 0x58(%%rdi) | |
| 316 | \\movq %%r12, 0x60(%%rdi) | |
| 317 | \\movq %%r13, 0x68(%%rdi) | |
| 318 | \\movq %%r14, 0x70(%%rdi) | |
| 319 | \\movq %%r15, 0x78(%%rdi) | |
| 320 | \\leaq (%%rip), %%rax | |
| 321 | \\movq %%rax, 0x80(%%rdi) | |
| 322 | \\movq 0x00(%%rdi), %%rax | |
| 323 | : | |
| 324 | : [gprs] "{rdi}" (&ctx.gprs.values), | |
| 325 | : .{ .memory = true }); | |
| 326 | return ctx; | |
| 327 | } | |
| 328 | ||
| 329 | pub fn dwarfRegisterBytes(ctx: *X86_64, register_num: u16) DwarfRegisterError![]u8 { | |
| 330 | // System V Application Binary Interface AMD64 Architecture Processor Supplement | |
| 331 | // § 3.6.2 "DWARF Register Number Mapping" | |
| 332 | switch (register_num) { | |
| 333 | // The order of `Gpr` intentionally matches DWARF's mappings. | |
| 334 | 0...16 => return @ptrCast(&ctx.gprs.values[register_num]), | |
| 335 | ||
| 336 | 17...32 => return error.UnsupportedRegister, // xmm0 - xmm15 | |
| 337 | 33...40 => return error.UnsupportedRegister, // st0 - st7 | |
| 338 | 41...48 => return error.UnsupportedRegister, // mm0 - mm7 | |
| 339 | 49 => return error.UnsupportedRegister, // rflags | |
| 340 | 50...55 => return error.UnsupportedRegister, // es, cs, ss, ds, fs, gs | |
| 341 | 58...59 => return error.UnsupportedRegister, // fs.base, gs.base | |
| 342 | 62 => return error.UnsupportedRegister, // tr | |
| 343 | 63 => return error.UnsupportedRegister, // ldtr | |
| 344 | 64 => return error.UnsupportedRegister, // mxcsr | |
| 345 | 65 => return error.UnsupportedRegister, // fcw | |
| 346 | 66 => return error.UnsupportedRegister, // fsw | |
| 347 | ||
| 348 | else => return error.InvalidRegister, | |
| 349 | } | |
| 350 | } | |
| 351 | }; | |
| 352 | ||
| 353 | pub const Arm = struct { | |
| 354 | /// The numbered general-purpose registers R0 - R15. | |
| 355 | r: [16]u32, | |
| 356 | ||
| 357 | pub inline fn current() Arm { | |
| 358 | var ctx: Arm = undefined; | |
| 359 | asm volatile ( | |
| 360 | \\// For compatibility with Thumb, we can't write r13 (sp) or r15 (pc) with stm. | |
| 361 | \\stm r0, {r0-r12} | |
| 362 | \\str r13, [r0, #0x34] | |
| 363 | \\str r14, [r0, #0x38] | |
| 364 | \\str r15, [r0, #0x3c] | |
| 365 | : | |
| 366 | : [r] "{r0}" (&ctx.r), | |
| 367 | : .{ .memory = true }); | |
| 368 | return ctx; | |
| 369 | } | |
| 370 | ||
| 371 | pub fn dwarfRegisterBytes(ctx: *Arm, register_num: u16) DwarfRegisterError![]u8 { | |
| 372 | // DWARF for the Arm(r) Architecture § 4.1 "DWARF register names" | |
| 373 | switch (register_num) { | |
| 374 | // The order of `Gpr` intentionally matches DWARF's mappings. | |
| 375 | 0...15 => return @ptrCast(&ctx.r[register_num]), | |
| 376 | ||
| 377 | 64...95 => return error.UnsupportedRegister, // S0 - S31 | |
| 378 | 96...103 => return error.UnsupportedRegister, // F0 - F7 | |
| 379 | 104...111 => return error.UnsupportedRegister, // wCGR0 - wCGR7, or ACC0 - ACC7 | |
| 380 | 112...127 => return error.UnsupportedRegister, // wR0 - wR15 | |
| 381 | 128 => return error.UnsupportedRegister, // SPSR | |
| 382 | 129 => return error.UnsupportedRegister, // SPSR_FIQ | |
| 383 | 130 => return error.UnsupportedRegister, // SPSR_IRQ | |
| 384 | 131 => return error.UnsupportedRegister, // SPSR_ABT | |
| 385 | 132 => return error.UnsupportedRegister, // SPSR_UND | |
| 386 | 133 => return error.UnsupportedRegister, // SPSR_SVC | |
| 387 | 143 => return error.UnsupportedRegister, // RA_AUTH_CODE | |
| 388 | 144...150 => return error.UnsupportedRegister, // R8_USR - R14_USR | |
| 389 | 151...157 => return error.UnsupportedRegister, // R8_FIQ - R14_FIQ | |
| 390 | 158...159 => return error.UnsupportedRegister, // R13_IRQ - R14_IRQ | |
| 391 | 160...161 => return error.UnsupportedRegister, // R13_ABT - R14_ABT | |
| 392 | 162...163 => return error.UnsupportedRegister, // R13_UND - R14_UND | |
| 393 | 164...165 => return error.UnsupportedRegister, // R13_SVC - R14_SVC | |
| 394 | 192...199 => return error.UnsupportedRegister, // wC0 - wC7 | |
| 395 | 256...287 => return error.UnsupportedRegister, // D0 - D31 | |
| 396 | 320 => return error.UnsupportedRegister, // TPIDRURO | |
| 397 | 321 => return error.UnsupportedRegister, // TPIDRURW | |
| 398 | 322 => return error.UnsupportedRegister, // TPIDPR | |
| 399 | 323 => return error.UnsupportedRegister, // HTPIDPR | |
| 400 | 8192...16383 => return error.UnsupportedRegister, // Unspecified vendor co-processor register | |
| 401 | ||
| 402 | else => return error.InvalidRegister, | |
| 403 | } | |
| 404 | } | |
| 405 | }; | |
| 406 | ||
| 407 | /// This is an `extern struct` so that inline assembly in `current` can use field offsets. | |
| 408 | pub const Aarch64 = extern struct { | |
| 409 | /// The numbered general-purpose registers X0 - X30. | |
| 410 | x: [31]u64, | |
| 411 | sp: u64, | |
| 412 | pc: u64, | |
| 413 | ||
| 414 | pub inline fn current() Aarch64 { | |
| 415 | var ctx: Aarch64 = undefined; | |
| 416 | asm volatile ( | |
| 417 | \\stp x0, x1, [x0, #0x000] | |
| 418 | \\stp x2, x3, [x0, #0x010] | |
| 419 | \\stp x4, x5, [x0, #0x020] | |
| 420 | \\stp x6, x7, [x0, #0x030] | |
| 421 | \\stp x8, x9, [x0, #0x040] | |
| 422 | \\stp x10, x11, [x0, #0x050] | |
| 423 | \\stp x12, x13, [x0, #0x060] | |
| 424 | \\stp x14, x15, [x0, #0x070] | |
| 425 | \\stp x16, x17, [x0, #0x080] | |
| 426 | \\stp x18, x19, [x0, #0x090] | |
| 427 | \\stp x20, x21, [x0, #0x0a0] | |
| 428 | \\stp x22, x23, [x0, #0x0b0] | |
| 429 | \\stp x24, x25, [x0, #0x0c0] | |
| 430 | \\stp x26, x27, [x0, #0x0d0] | |
| 431 | \\stp x28, x29, [x0, #0x0e0] | |
| 432 | \\str x30, [x0, #0x0f0] | |
| 433 | \\mov x1, sp | |
| 434 | \\str x1, [x0, #0x0f8] | |
| 435 | \\adr x1, . | |
| 436 | \\str x1, [x0, #0x100] | |
| 437 | \\ldr x1, [x0, #0x008] | |
| 438 | : | |
| 439 | : [gprs] "{x0}" (&ctx), | |
| 440 | : .{ .memory = true }); | |
| 441 | return ctx; | |
| 442 | } | |
| 443 | ||
| 444 | pub fn dwarfRegisterBytes(ctx: *Aarch64, register_num: u16) DwarfRegisterError![]u8 { | |
| 445 | // DWARF for the Arm(r) 64-bit Architecture (AArch64) § 4.1 "DWARF register names" | |
| 446 | switch (register_num) { | |
| 447 | // The order of `Gpr` intentionally matches DWARF's mappings. | |
| 448 | 0...30 => return @ptrCast(&ctx.x[register_num]), | |
| 449 | 31 => return @ptrCast(&ctx.sp), | |
| 450 | 32 => return @ptrCast(&ctx.pc), | |
| 451 | ||
| 452 | 33 => return error.UnsupportedRegister, // ELF_mode | |
| 453 | 34 => return error.UnsupportedRegister, // RA_SIGN_STATE | |
| 454 | 35 => return error.UnsupportedRegister, // TPIDRRO_ELO | |
| 455 | 36 => return error.UnsupportedRegister, // RPIDR_ELO | |
| 456 | 37 => return error.UnsupportedRegister, // RPIDR_EL1 | |
| 457 | 38 => return error.UnsupportedRegister, // RPIDR_EL2 | |
| 458 | 39 => return error.UnsupportedRegister, // RPIDR_EL3 | |
| 459 | 46 => return error.UnsupportedRegister, // VG | |
| 460 | 47 => return error.UnsupportedRegister, // FFR | |
| 461 | 48...63 => return error.UnsupportedRegister, // P0 - P15 | |
| 462 | 64...95 => return error.UnsupportedRegister, // V0 - V31 | |
| 463 | 96...127 => return error.UnsupportedRegister, // Z0 - Z31 | |
| 464 | ||
| 465 | else => return error.InvalidRegister, | |
| 466 | } | |
| 467 | } | |
| 468 | }; | |
| 469 | ||
| 470 | const signal_ucontext_t = switch (native_os) { | |
| 471 | .linux => std.os.linux.ucontext_t, | |
| 472 | .emscripten => std.os.emscripten.ucontext_t, | |
| 473 | .freebsd => std.os.freebsd.ucontext_t, | |
| 474 | .macos, .ios, .tvos, .watchos, .visionos => extern struct { | |
| 475 | onstack: c_int, | |
| 476 | sigmask: std.c.sigset_t, | |
| 477 | stack: std.c.stack_t, | |
| 478 | link: ?*signal_ucontext_t, | |
| 479 | mcsize: u64, | |
| 480 | mcontext: *mcontext_t, | |
| 481 | const mcontext_t = switch (native_arch) { | |
| 482 | .aarch64 => extern struct { | |
| 483 | es: extern struct { | |
| 484 | far: u64, // Virtual Fault Address | |
| 485 | esr: u32, // Exception syndrome | |
| 486 | exception: u32, // Number of arm exception taken | |
| 487 | }, | |
| 488 | ss: extern struct { | |
| 489 | /// General purpose registers | |
| 490 | regs: [29]u64, | |
| 491 | /// Frame pointer x29 | |
| 492 | fp: u64, | |
| 493 | /// Link register x30 | |
| 494 | lr: u64, | |
| 495 | /// Stack pointer x31 | |
| 496 | sp: u64, | |
| 497 | /// Program counter | |
| 498 | pc: u64, | |
| 499 | /// Current program status register | |
| 500 | cpsr: u32, | |
| 501 | __pad: u32, | |
| 502 | }, | |
| 503 | ns: extern struct { | |
| 504 | q: [32]u128, | |
| 505 | fpsr: u32, | |
| 506 | fpcr: u32, | |
| 507 | }, | |
| 508 | }, | |
| 509 | .x86_64 => extern struct { | |
| 510 | es: extern struct { | |
| 511 | trapno: u16, | |
| 512 | cpu: u16, | |
| 513 | err: u32, | |
| 514 | faultvaddr: u64, | |
| 515 | }, | |
| 516 | ss: extern struct { | |
| 517 | rax: u64, | |
| 518 | rbx: u64, | |
| 519 | rcx: u64, | |
| 520 | rdx: u64, | |
| 521 | rdi: u64, | |
| 522 | rsi: u64, | |
| 523 | rbp: u64, | |
| 524 | rsp: u64, | |
| 525 | r8: u64, | |
| 526 | r9: u64, | |
| 527 | r10: u64, | |
| 528 | r11: u64, | |
| 529 | r12: u64, | |
| 530 | r13: u64, | |
| 531 | r14: u64, | |
| 532 | r15: u64, | |
| 533 | rip: u64, | |
| 534 | rflags: u64, | |
| 535 | cs: u64, | |
| 536 | fs: u64, | |
| 537 | gs: u64, | |
| 538 | }, | |
| 539 | fs: extern struct { | |
| 540 | reserved: [2]c_int, | |
| 541 | fcw: u16, | |
| 542 | fsw: u16, | |
| 543 | ftw: u8, | |
| 544 | rsrv1: u8, | |
| 545 | fop: u16, | |
| 546 | ip: u32, | |
| 547 | cs: u16, | |
| 548 | rsrv2: u16, | |
| 549 | dp: u32, | |
| 550 | ds: u16, | |
| 551 | rsrv3: u16, | |
| 552 | mxcsr: u32, | |
| 553 | mxcsrmask: u32, | |
| 554 | stmm: [8]stmm_reg, | |
| 555 | xmm: [16]xmm_reg, | |
| 556 | rsrv4: [96]u8, | |
| 557 | reserved1: c_int, | |
| 558 | ||
| 559 | const stmm_reg = [16]u8; | |
| 560 | const xmm_reg = [16]u8; | |
| 561 | }, | |
| 562 | }, | |
| 563 | else => void, | |
| 564 | }; | |
| 565 | }, | |
| 566 | .solaris, .illumos => extern struct { | |
| 567 | flags: u64, | |
| 568 | link: ?*signal_ucontext_t, | |
| 569 | sigmask: std.c.sigset_t, | |
| 570 | stack: std.c.stack_t, | |
| 571 | mcontext: mcontext_t, | |
| 572 | brand_data: [3]?*anyopaque, | |
| 573 | filler: [2]i64, | |
| 574 | const mcontext_t = extern struct { | |
| 575 | gregs: [28]u64, | |
| 576 | fpregs: std.c.fpregset_t, | |
| 577 | }; | |
| 578 | }, | |
| 579 | .openbsd => switch (builtin.cpu.arch) { | |
| 580 | .x86_64 => extern struct { | |
| 581 | sc_rdi: c_long, | |
| 582 | sc_rsi: c_long, | |
| 583 | sc_rdx: c_long, | |
| 584 | sc_rcx: c_long, | |
| 585 | sc_r8: c_long, | |
| 586 | sc_r9: c_long, | |
| 587 | sc_r10: c_long, | |
| 588 | sc_r11: c_long, | |
| 589 | sc_r12: c_long, | |
| 590 | sc_r13: c_long, | |
| 591 | sc_r14: c_long, | |
| 592 | sc_r15: c_long, | |
| 593 | sc_rbp: c_long, | |
| 594 | sc_rbx: c_long, | |
| 595 | sc_rax: c_long, | |
| 596 | sc_gs: c_long, | |
| 597 | sc_fs: c_long, | |
| 598 | sc_es: c_long, | |
| 599 | sc_ds: c_long, | |
| 600 | sc_trapno: c_long, | |
| 601 | sc_err: c_long, | |
| 602 | sc_rip: c_long, | |
| 603 | sc_cs: c_long, | |
| 604 | sc_rflags: c_long, | |
| 605 | sc_rsp: c_long, | |
| 606 | sc_ss: c_long, | |
| 607 | ||
| 608 | sc_fpstate: *anyopaque, // struct fxsave64 * | |
| 609 | __sc_unused: c_int, | |
| 610 | sc_mask: c_int, | |
| 611 | sc_cookie: c_long, | |
| 612 | }, | |
| 613 | .aarch64 => extern struct { | |
| 614 | __sc_unused: c_int, | |
| 615 | sc_mask: c_int, | |
| 616 | sc_sp: c_ulong, | |
| 617 | sc_lr: c_ulong, | |
| 618 | sc_elr: c_ulong, | |
| 619 | sc_spsr: c_ulong, | |
| 620 | sc_x: [30]c_ulong, | |
| 621 | sc_cookie: c_long, | |
| 622 | }, | |
| 623 | else => void, | |
| 624 | }, | |
| 625 | .netbsd => extern struct { | |
| 626 | flags: u32, | |
| 627 | link: ?*signal_ucontext_t, | |
| 628 | sigmask: std.c.sigset_t, | |
| 629 | stack: std.c.stack_t, | |
| 630 | mcontext: mcontext_t, | |
| 631 | __pad: [ | |
| 632 | switch (builtin.cpu.arch) { | |
| 633 | .x86 => 4, | |
| 634 | .mips, .mipsel, .mips64, .mips64el => 14, | |
| 635 | .arm, .armeb, .thumb, .thumbeb => 1, | |
| 636 | .sparc, .sparc64 => if (@sizeOf(usize) == 4) 43 else 8, | |
| 637 | else => 0, | |
| 638 | } | |
| 639 | ]u32, | |
| 640 | const mcontext_t = switch (builtin.cpu.arch) { | |
| 641 | .aarch64, .aarch64_be => extern struct { | |
| 642 | gregs: [35]u64, | |
| 643 | fregs: [528]u8 align(16), | |
| 644 | spare: [8]u64, | |
| 645 | }, | |
| 646 | .x86 => extern struct { | |
| 647 | gregs: [19]u32, | |
| 648 | fpregs: [161]u32, | |
| 649 | mc_tlsbase: u32, | |
| 650 | }, | |
| 651 | .x86_64 => extern struct { | |
| 652 | gregs: [26]u64, | |
| 653 | mc_tlsbase: u64, | |
| 654 | fpregs: [512]u8 align(8), | |
| 655 | }, | |
| 656 | else => void, | |
| 657 | }; | |
| 658 | }, | |
| 659 | .dragonfly => extern struct { | |
| 660 | sigmask: std.c.sigset_t, | |
| 661 | mcontext: mcontext_t, | |
| 662 | link: ?*signal_ucontext_t, | |
| 663 | stack: std.c.stack_t, | |
| 664 | cofunc: ?*fn (?*signal_ucontext_t, ?*anyopaque) void, | |
| 665 | arg: ?*void, | |
| 666 | _spare: [4]c_int, | |
| 667 | const mcontext_t = extern struct { | |
| 668 | const register_t = isize; | |
| 669 | onstack: register_t, // XXX - sigcontext compat. | |
| 670 | rdi: register_t, | |
| 671 | rsi: register_t, | |
| 672 | rdx: register_t, | |
| 673 | rcx: register_t, | |
| 674 | r8: register_t, | |
| 675 | r9: register_t, | |
| 676 | rax: register_t, | |
| 677 | rbx: register_t, | |
| 678 | rbp: register_t, | |
| 679 | r10: register_t, | |
| 680 | r11: register_t, | |
| 681 | r12: register_t, | |
| 682 | r13: register_t, | |
| 683 | r14: register_t, | |
| 684 | r15: register_t, | |
| 685 | xflags: register_t, | |
| 686 | trapno: register_t, | |
| 687 | addr: register_t, | |
| 688 | flags: register_t, | |
| 689 | err: register_t, | |
| 690 | rip: register_t, | |
| 691 | cs: register_t, | |
| 692 | rflags: register_t, | |
| 693 | rsp: register_t, // machine state | |
| 694 | ss: register_t, | |
| 695 | ||
| 696 | len: c_uint, // sizeof(mcontext_t) | |
| 697 | fpformat: c_uint, | |
| 698 | ownedfp: c_uint, | |
| 699 | reserved: c_uint, | |
| 700 | unused: [8]c_uint, | |
| 701 | ||
| 702 | // NOTE! 64-byte aligned as of here. Also must match savefpu structure. | |
| 703 | fpregs: [256]c_int align(64), | |
| 704 | }; | |
| 705 | }, | |
| 706 | .serenity => extern struct { | |
| 707 | link: ?*signal_ucontext_t, | |
| 708 | sigmask: std.c.sigset_t, | |
| 709 | stack: std.c.stack_t, | |
| 710 | mcontext: mcontext_t, | |
| 711 | const mcontext_t = switch (builtin.cpu.arch) { | |
| 712 | // https://github.com/SerenityOS/serenity/blob/200e91cd7f1ec5453799a2720d4dc114a59cc289/Kernel/Arch/aarch64/mcontext.h#L15-L19 | |
| 713 | .aarch64 => extern struct { | |
| 714 | x: [31]u64, | |
| 715 | sp: u64, | |
| 716 | pc: u64, | |
| 717 | }, | |
| 718 | // https://github.com/SerenityOS/serenity/blob/66f8d0f031ef25c409dbb4fecaa454800fecae0f/Kernel/Arch/riscv64/mcontext.h#L15-L18 | |
| 719 | .riscv64 => extern struct { | |
| 720 | x: [31]u64, | |
| 721 | pc: u64, | |
| 722 | }, | |
| 723 | // https://github.com/SerenityOS/serenity/blob/7b9ea3efdec9f86a1042893e8107d0b23aad8727/Kernel/Arch/x86_64/mcontext.h#L15-L40 | |
| 724 | .x86_64 => extern struct { | |
| 725 | rax: u64, | |
| 726 | rcx: u64, | |
| 727 | rdx: u64, | |
| 728 | rbx: u64, | |
| 729 | rsp: u64, | |
| 730 | rbp: u64, | |
| 731 | rsi: u64, | |
| 732 | rdi: u64, | |
| 733 | rip: u64, | |
| 734 | r8: u64, | |
| 735 | r9: u64, | |
| 736 | r10: u64, | |
| 737 | r11: u64, | |
| 738 | r12: u64, | |
| 739 | r13: u64, | |
| 740 | r14: u64, | |
| 741 | r15: u64, | |
| 742 | rflags: u64, | |
| 743 | cs: u32, | |
| 744 | ss: u32, | |
| 745 | ds: u32, | |
| 746 | es: u32, | |
| 747 | fs: u32, | |
| 748 | gs: u32, | |
| 749 | }, | |
| 750 | else => void, | |
| 751 | }; | |
| 752 | }, | |
| 753 | .haiku => extern struct { | |
| 754 | link: ?*signal_ucontext_t, | |
| 755 | sigmask: std.c.sigset_t, | |
| 756 | stack: std.c.stack_t, | |
| 757 | mcontext: mcontext_t, | |
| 758 | const mcontext_t = switch (builtin.cpu.arch) { | |
| 759 | .arm, .thumb => extern struct { | |
| 760 | r0: u32, | |
| 761 | r1: u32, | |
| 762 | r2: u32, | |
| 763 | r3: u32, | |
| 764 | r4: u32, | |
| 765 | r5: u32, | |
| 766 | r6: u32, | |
| 767 | r7: u32, | |
| 768 | r8: u32, | |
| 769 | r9: u32, | |
| 770 | r10: u32, | |
| 771 | r11: u32, | |
| 772 | r12: u32, | |
| 773 | r13: u32, | |
| 774 | r14: u32, | |
| 775 | r15: u32, | |
| 776 | cpsr: u32, | |
| 777 | }, | |
| 778 | .aarch64 => extern struct { | |
| 779 | x: [10]u64, | |
| 780 | lr: u64, | |
| 781 | sp: u64, | |
| 782 | elr: u64, | |
| 783 | spsr: u64, | |
| 784 | fp_q: [32]u128, | |
| 785 | fpsr: u32, | |
| 786 | fpcr: u32, | |
| 787 | }, | |
| 788 | .m68k => extern struct { | |
| 789 | pc: u32, | |
| 790 | d0: u32, | |
| 791 | d1: u32, | |
| 792 | d2: u32, | |
| 793 | d3: u32, | |
| 794 | d4: u32, | |
| 795 | d5: u32, | |
| 796 | d6: u32, | |
| 797 | d7: u32, | |
| 798 | a0: u32, | |
| 799 | a1: u32, | |
| 800 | a2: u32, | |
| 801 | a3: u32, | |
| 802 | a4: u32, | |
| 803 | a5: u32, | |
| 804 | a6: u32, | |
| 805 | a7: u32, | |
| 806 | ccr: u8, | |
| 807 | f0: f64, | |
| 808 | f1: f64, | |
| 809 | f2: f64, | |
| 810 | f3: f64, | |
| 811 | f4: f64, | |
| 812 | f5: f64, | |
| 813 | f6: f64, | |
| 814 | f7: f64, | |
| 815 | f8: f64, | |
| 816 | f9: f64, | |
| 817 | f10: f64, | |
| 818 | f11: f64, | |
| 819 | f12: f64, | |
| 820 | f13: f64, | |
| 821 | }, | |
| 822 | .mipsel => extern struct { | |
| 823 | r0: u32, | |
| 824 | }, | |
| 825 | .powerpc => extern struct { | |
| 826 | pc: u32, | |
| 827 | r0: u32, | |
| 828 | r1: u32, | |
| 829 | r2: u32, | |
| 830 | r3: u32, | |
| 831 | r4: u32, | |
| 832 | r5: u32, | |
| 833 | r6: u32, | |
| 834 | r7: u32, | |
| 835 | r8: u32, | |
| 836 | r9: u32, | |
| 837 | r10: u32, | |
| 838 | r11: u32, | |
| 839 | r12: u32, | |
| 840 | f0: f64, | |
| 841 | f1: f64, | |
| 842 | f2: f64, | |
| 843 | f3: f64, | |
| 844 | f4: f64, | |
| 845 | f5: f64, | |
| 846 | f6: f64, | |
| 847 | f7: f64, | |
| 848 | f8: f64, | |
| 849 | f9: f64, | |
| 850 | f10: f64, | |
| 851 | f11: f64, | |
| 852 | f12: f64, | |
| 853 | f13: f64, | |
| 854 | reserved: u32, | |
| 855 | fpscr: u32, | |
| 856 | ctr: u32, | |
| 857 | xer: u32, | |
| 858 | cr: u32, | |
| 859 | msr: u32, | |
| 860 | lr: u32, | |
| 861 | }, | |
| 862 | .riscv64 => extern struct { | |
| 863 | x: [31]u64, | |
| 864 | pc: u64, | |
| 865 | f: [32]f64, | |
| 866 | fcsr: u64, | |
| 867 | }, | |
| 868 | .sparc64 => extern struct { | |
| 869 | g1: u64, | |
| 870 | g2: u64, | |
| 871 | g3: u64, | |
| 872 | g4: u64, | |
| 873 | g5: u64, | |
| 874 | g6: u64, | |
| 875 | g7: u64, | |
| 876 | o0: u64, | |
| 877 | o1: u64, | |
| 878 | o2: u64, | |
| 879 | o3: u64, | |
| 880 | o4: u64, | |
| 881 | o5: u64, | |
| 882 | sp: u64, | |
| 883 | o7: u64, | |
| 884 | l0: u64, | |
| 885 | l1: u64, | |
| 886 | l2: u64, | |
| 887 | l3: u64, | |
| 888 | l4: u64, | |
| 889 | l5: u64, | |
| 890 | l6: u64, | |
| 891 | l7: u64, | |
| 892 | i0: u64, | |
| 893 | i1: u64, | |
| 894 | i2: u64, | |
| 895 | i3: u64, | |
| 896 | i4: u64, | |
| 897 | i5: u64, | |
| 898 | fp: u64, | |
| 899 | i7: u64, | |
| 900 | }, | |
| 901 | .x86 => extern struct { | |
| 902 | pub const old_extended_regs = extern struct { | |
| 903 | control: u16, | |
| 904 | reserved1: u16, | |
| 905 | status: u16, | |
| 906 | reserved2: u16, | |
| 907 | tag: u16, | |
| 908 | reserved3: u16, | |
| 909 | eip: u32, | |
| 910 | cs: u16, | |
| 911 | opcode: u16, | |
| 912 | datap: u32, | |
| 913 | ds: u16, | |
| 914 | reserved4: u16, | |
| 915 | fp_mmx: [8][10]u8, | |
| 916 | }; | |
| 917 | ||
| 918 | pub const fp_register = extern struct { value: [10]u8, reserved: [6]u8 }; | |
| 919 | ||
| 920 | pub const xmm_register = extern struct { value: [16]u8 }; | |
| 921 | ||
| 922 | pub const new_extended_regs = extern struct { | |
| 923 | control: u16, | |
| 924 | status: u16, | |
| 925 | tag: u16, | |
| 926 | opcode: u16, | |
| 927 | eip: u32, | |
| 928 | cs: u16, | |
| 929 | reserved1: u16, | |
| 930 | datap: u32, | |
| 931 | ds: u16, | |
| 932 | reserved2: u16, | |
| 933 | mxcsr: u32, | |
| 934 | reserved3: u32, | |
| 935 | fp_mmx: [8]fp_register, | |
| 936 | xmmx: [8]xmm_register, | |
| 937 | reserved4: [224]u8, | |
| 938 | }; | |
| 939 | ||
| 940 | pub const extended_regs = extern struct { | |
| 941 | state: extern union { | |
| 942 | old_format: old_extended_regs, | |
| 943 | new_format: new_extended_regs, | |
| 944 | }, | |
| 945 | format: u32, | |
| 946 | }; | |
| 947 | ||
| 948 | eip: u32, | |
| 949 | eflags: u32, | |
| 950 | eax: u32, | |
| 951 | ecx: u32, | |
| 952 | edx: u32, | |
| 953 | esp: u32, | |
| 954 | ebp: u32, | |
| 955 | reserved: u32, | |
| 956 | xregs: extended_regs, | |
| 957 | edi: u32, | |
| 958 | esi: u32, | |
| 959 | ebx: u32, | |
| 960 | }, | |
| 961 | .x86_64 => extern struct { | |
| 962 | pub const fp_register = extern struct { | |
| 963 | value: [10]u8, | |
| 964 | reserved: [6]u8, | |
| 965 | }; | |
| 966 | ||
| 967 | pub const xmm_register = extern struct { | |
| 968 | value: [16]u8, | |
| 969 | }; | |
| 970 | ||
| 971 | pub const fpu_state = extern struct { | |
| 972 | control: u16, | |
| 973 | status: u16, | |
| 974 | tag: u16, | |
| 975 | opcode: u16, | |
| 976 | rip: u64, | |
| 977 | rdp: u64, | |
| 978 | mxcsr: u32, | |
| 979 | mscsr_mask: u32, | |
| 980 | ||
| 981 | fp_mmx: [8]fp_register, | |
| 982 | xmm: [16]xmm_register, | |
| 983 | reserved: [96]u8, | |
| 984 | }; | |
| 985 | ||
| 986 | pub const xstate_hdr = extern struct { | |
| 987 | bv: u64, | |
| 988 | xcomp_bv: u64, | |
| 989 | reserved: [48]u8, | |
| 990 | }; | |
| 991 | ||
| 992 | pub const savefpu = extern struct { | |
| 993 | fxsave: fpu_state, | |
| 994 | xstate: xstate_hdr, | |
| 995 | ymm: [16]xmm_register, | |
| 996 | }; | |
| 997 | ||
| 998 | rax: u64, | |
| 999 | rbx: u64, | |
| 1000 | rcx: u64, | |
| 1001 | rdx: u64, | |
| 1002 | rdi: u64, | |
| 1003 | rsi: u64, | |
| 1004 | rbp: u64, | |
| 1005 | r8: u64, | |
| 1006 | r9: u64, | |
| 1007 | r10: u64, | |
| 1008 | r11: u64, | |
| 1009 | r12: u64, | |
| 1010 | r13: u64, | |
| 1011 | r14: u64, | |
| 1012 | r15: u64, | |
| 1013 | rsp: u64, | |
| 1014 | rip: u64, | |
| 1015 | rflags: u64, | |
| 1016 | fpu: savefpu, | |
| 1017 | }, | |
| 1018 | else => void, | |
| 1019 | }; | |
| 1020 | }, | |
| 1021 | else => void, | |
| 1022 | }; | |
| 1023 | ||
| 1024 | const std = @import("../std.zig"); | |
| 1025 | const root = @import("root"); | |
| 1026 | const builtin = @import("builtin"); | |
| 1027 | const native_arch = @import("builtin").target.cpu.arch; | |
| 1028 | const native_os = @import("builtin").target.os.tag; |
lib/std/dwarf/EH.zig+30-23| ... | ... | @@ -1,27 +1,34 @@ |
| 1 | pub const PE = struct { | |
| 2 | pub const absptr = 0x00; | |
| 1 | pub const PE = packed struct(u8) { | |
| 2 | type: Type, | |
| 3 | rel: Rel, | |
| 4 | /// Undocumented GCC extension | |
| 5 | indirect: bool = false, | |
| 3 | 6 | |
| 4 | pub const size_mask = 0x7; | |
| 5 | pub const sign_mask = 0x8; | |
| 6 | pub const type_mask = size_mask | sign_mask; | |
| 7 | /// This is a special encoding which does not correspond to named `type`/`rel` values. | |
| 8 | pub const omit: PE = @bitCast(@as(u8, 0xFF)); | |
| 7 | 9 | |
| 8 | pub const uleb128 = 0x01; | |
| 9 | pub const udata2 = 0x02; | |
| 10 | pub const udata4 = 0x03; | |
| 11 | pub const udata8 = 0x04; | |
| 12 | pub const sleb128 = 0x09; | |
| 13 | pub const sdata2 = 0x0A; | |
| 14 | pub const sdata4 = 0x0B; | |
| 15 | pub const sdata8 = 0x0C; | |
| 10 | pub const Type = enum(u4) { | |
| 11 | absptr = 0x0, | |
| 12 | uleb128 = 0x1, | |
| 13 | udata2 = 0x2, | |
| 14 | udata4 = 0x3, | |
| 15 | udata8 = 0x4, | |
| 16 | sleb128 = 0x9, | |
| 17 | sdata2 = 0xA, | |
| 18 | sdata4 = 0xB, | |
| 19 | sdata8 = 0xC, | |
| 20 | _, | |
| 21 | }; | |
| 16 | 22 | |
| 17 | pub const rel_mask = 0x70; | |
| 18 | pub const pcrel = 0x10; | |
| 19 | pub const textrel = 0x20; | |
| 20 | pub const datarel = 0x30; | |
| 21 | pub const funcrel = 0x40; | |
| 22 | pub const aligned = 0x50; | |
| 23 | ||
| 24 | pub const indirect = 0x80; | |
| 25 | ||
| 26 | pub const omit = 0xff; | |
| 23 | /// The specification considers this a `u4`, but the GCC `indirect` field extension conflicts | |
| 24 | /// with that, so we consider it a `u3` instead. | |
| 25 | pub const Rel = enum(u3) { | |
| 26 | abs = 0x0, | |
| 27 | pcrel = 0x1, | |
| 28 | textrel = 0x2, | |
| 29 | datarel = 0x3, | |
| 30 | funcrel = 0x4, | |
| 31 | aligned = 0x5, | |
| 32 | _, | |
| 33 | }; | |
| 27 | 34 | }; |
lib/std/dynamic_library.zig+1-2| ... | ... | @@ -95,8 +95,7 @@ pub fn get_DYNAMIC() ?[*]const elf.Dyn { |
| 95 | 95 | pub fn linkmap_iterator(phdrs: []const elf.Phdr) error{InvalidExe}!LinkMap.Iterator { |
| 96 | 96 | _ = phdrs; |
| 97 | 97 | const _DYNAMIC = get_DYNAMIC() orelse { |
| 98 | // No PT_DYNAMIC means this is either a statically-linked program or a | |
| 99 | // badly corrupted dynamically-linked one. | |
| 98 | // No PT_DYNAMIC means this is a statically-linked non-PIE program. | |
| 100 | 99 | return .{ .current = null }; |
| 101 | 100 | }; |
| 102 | 101 |
lib/std/elf.zig+2-1| ... | ... | @@ -744,7 +744,8 @@ pub const SectionHeaderBufferIterator = struct { |
| 744 | 744 | |
| 745 | 745 | const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr); |
| 746 | 746 | const offset = it.elf_header.shoff + size * it.index; |
| 747 | var reader = std.Io.Reader.fixed(it.buf[offset..]); | |
| 747 | if (offset > it.buf.len) return error.EndOfStream; | |
| 748 | var reader = std.Io.Reader.fixed(it.buf[@intCast(offset)..]); | |
| 748 | 749 | |
| 749 | 750 | return takeShdr(&reader, it.elf_header); |
| 750 | 751 | } |
lib/std/heap/PageAllocator.zig+1-1| ... | ... | @@ -183,7 +183,7 @@ pub fn realloc(uncasted_memory: []u8, new_len: usize, may_move: bool) ?[*]u8 { |
| 183 | 183 | |
| 184 | 184 | if (posix.MREMAP != void) { |
| 185 | 185 | // TODO: if the next_mmap_addr_hint is within the remapped range, update it |
| 186 | const new_memory = posix.mremap(memory.ptr, memory.len, new_len, .{ .MAYMOVE = may_move }, null) catch return null; | |
| 186 | const new_memory = posix.mremap(memory.ptr, page_aligned_len, new_size_aligned, .{ .MAYMOVE = may_move }, null) catch return null; | |
| 187 | 187 | return new_memory.ptr; |
| 188 | 188 | } |
| 189 | 189 |
lib/std/heap/debug_allocator.zig+13-38| ... | ... | @@ -505,23 +505,14 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 505 | 505 | return if (leaks) .leak else .ok; |
| 506 | 506 | } |
| 507 | 507 | |
| 508 | fn collectStackTrace(first_trace_addr: usize, addresses: *[stack_n]usize) void { | |
| 509 | if (stack_n == 0) return; | |
| 510 | @memset(addresses, 0); | |
| 511 | var stack_trace: StackTrace = .{ | |
| 512 | .instruction_addresses = addresses, | |
| 513 | .index = 0, | |
| 514 | }; | |
| 515 | std.debug.captureStackTrace(first_trace_addr, &stack_trace); | |
| 508 | fn collectStackTrace(first_trace_addr: usize, addr_buf: *[stack_n]usize) void { | |
| 509 | const st = std.debug.captureCurrentStackTrace(.{ .first_address = first_trace_addr }, addr_buf); | |
| 510 | @memset(addr_buf[@min(st.index, addr_buf.len)..], 0); | |
| 516 | 511 | } |
| 517 | 512 | |
| 518 | 513 | fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void { |
| 519 | var addresses: [stack_n]usize = @splat(0); | |
| 520 | var second_free_stack_trace: StackTrace = .{ | |
| 521 | .instruction_addresses = &addresses, | |
| 522 | .index = 0, | |
| 523 | }; | |
| 524 | std.debug.captureStackTrace(ret_addr, &second_free_stack_trace); | |
| 514 | var addr_buf: [stack_n]usize = undefined; | |
| 515 | const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); | |
| 525 | 516 | log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{ |
| 526 | 517 | alloc_stack_trace, free_stack_trace, second_free_stack_trace, |
| 527 | 518 | }); |
| ... | ... | @@ -562,12 +553,8 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 562 | 553 | } |
| 563 | 554 | |
| 564 | 555 | if (config.safety and old_mem.len != entry.value_ptr.bytes.len) { |
| 565 | var addresses: [stack_n]usize = [1]usize{0} ** stack_n; | |
| 566 | var free_stack_trace: StackTrace = .{ | |
| 567 | .instruction_addresses = &addresses, | |
| 568 | .index = 0, | |
| 569 | }; | |
| 570 | std.debug.captureStackTrace(ret_addr, &free_stack_trace); | |
| 556 | var addr_buf: [stack_n]usize = undefined; | |
| 557 | const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); | |
| 571 | 558 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ |
| 572 | 559 | entry.value_ptr.bytes.len, |
| 573 | 560 | old_mem.len, |
| ... | ... | @@ -672,12 +659,8 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 672 | 659 | } |
| 673 | 660 | |
| 674 | 661 | if (config.safety and old_mem.len != entry.value_ptr.bytes.len) { |
| 675 | var addresses: [stack_n]usize = [1]usize{0} ** stack_n; | |
| 676 | var free_stack_trace = StackTrace{ | |
| 677 | .instruction_addresses = &addresses, | |
| 678 | .index = 0, | |
| 679 | }; | |
| 680 | std.debug.captureStackTrace(ret_addr, &free_stack_trace); | |
| 662 | var addr_buf: [stack_n]usize = undefined; | |
| 663 | const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); | |
| 681 | 664 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ |
| 682 | 665 | entry.value_ptr.bytes.len, |
| 683 | 666 | old_mem.len, |
| ... | ... | @@ -900,12 +883,8 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 900 | 883 | if (requested_size == 0) @panic("Invalid free"); |
| 901 | 884 | const slot_alignment = bucket.log2PtrAligns(slot_count)[slot_index]; |
| 902 | 885 | if (old_memory.len != requested_size or alignment != slot_alignment) { |
| 903 | var addresses: [stack_n]usize = [1]usize{0} ** stack_n; | |
| 904 | var free_stack_trace: StackTrace = .{ | |
| 905 | .instruction_addresses = &addresses, | |
| 906 | .index = 0, | |
| 907 | }; | |
| 908 | std.debug.captureStackTrace(return_address, &free_stack_trace); | |
| 886 | var addr_buf: [stack_n]usize = undefined; | |
| 887 | const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); | |
| 909 | 888 | if (old_memory.len != requested_size) { |
| 910 | 889 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ |
| 911 | 890 | requested_size, |
| ... | ... | @@ -999,12 +978,8 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 999 | 978 | if (requested_size == 0) @panic("Invalid free"); |
| 1000 | 979 | const slot_alignment = bucket.log2PtrAligns(slot_count)[slot_index]; |
| 1001 | 980 | if (memory.len != requested_size or alignment != slot_alignment) { |
| 1002 | var addresses: [stack_n]usize = [1]usize{0} ** stack_n; | |
| 1003 | var free_stack_trace: StackTrace = .{ | |
| 1004 | .instruction_addresses = &addresses, | |
| 1005 | .index = 0, | |
| 1006 | }; | |
| 1007 | std.debug.captureStackTrace(return_address, &free_stack_trace); | |
| 981 | var addr_buf: [stack_n]usize = undefined; | |
| 982 | const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); | |
| 1008 | 983 | if (memory.len != requested_size) { |
| 1009 | 984 | log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ |
| 1010 | 985 | requested_size, |
lib/std/macho.zig+66-53| ... | ... | @@ -839,62 +839,75 @@ pub const nlist = extern struct { |
| 839 | 839 | |
| 840 | 840 | pub const nlist_64 = extern struct { |
| 841 | 841 | n_strx: u32, |
| 842 | n_type: u8, | |
| 842 | n_type: packed union { | |
| 843 | bits: packed struct(u8) { | |
| 844 | ext: bool, | |
| 845 | type: enum(u3) { | |
| 846 | undf = 0, | |
| 847 | abs = 1, | |
| 848 | sect = 7, | |
| 849 | pbud = 6, | |
| 850 | indr = 5, | |
| 851 | _, | |
| 852 | }, | |
| 853 | pext: bool, | |
| 854 | /// Any non-zero value indicates this is an stab, so the `stab` field should be used. | |
| 855 | is_stab: u3, | |
| 856 | }, | |
| 857 | stab: enum(u8) { | |
| 858 | gsym = N_GSYM, | |
| 859 | fname = N_FNAME, | |
| 860 | fun = N_FUN, | |
| 861 | stsym = N_STSYM, | |
| 862 | lcsym = N_LCSYM, | |
| 863 | bnsym = N_BNSYM, | |
| 864 | ast = N_AST, | |
| 865 | opt = N_OPT, | |
| 866 | rsym = N_RSYM, | |
| 867 | sline = N_SLINE, | |
| 868 | ensym = N_ENSYM, | |
| 869 | ssym = N_SSYM, | |
| 870 | so = N_SO, | |
| 871 | oso = N_OSO, | |
| 872 | lsym = N_LSYM, | |
| 873 | bincl = N_BINCL, | |
| 874 | sol = N_SOL, | |
| 875 | params = N_PARAMS, | |
| 876 | version = N_VERSION, | |
| 877 | olevel = N_OLEVEL, | |
| 878 | psym = N_PSYM, | |
| 879 | eincl = N_EINCL, | |
| 880 | entry = N_ENTRY, | |
| 881 | lbrac = N_LBRAC, | |
| 882 | excl = N_EXCL, | |
| 883 | rbrac = N_RBRAC, | |
| 884 | bcomm = N_BCOMM, | |
| 885 | ecomm = N_ECOMM, | |
| 886 | ecoml = N_ECOML, | |
| 887 | leng = N_LENG, | |
| 888 | _, | |
| 889 | }, | |
| 890 | }, | |
| 843 | 891 | n_sect: u8, |
| 844 | n_desc: u16, | |
| 892 | n_desc: packed struct(u16) { | |
| 893 | _pad0: u3 = 0, | |
| 894 | arm_thumb_def: bool, | |
| 895 | referenced_dynamically: bool, | |
| 896 | /// The meaning of this bit is contextual. | |
| 897 | /// See `N_DESC_DISCARDED` and `N_NO_DEAD_STRIP`. | |
| 898 | discarded_or_no_dead_strip: bool, | |
| 899 | weak_ref: bool, | |
| 900 | /// The meaning of this bit is contextual. | |
| 901 | /// See `N_WEAK_DEF` and `N_REF_TO_WEAK`. | |
| 902 | weak_def_or_ref_to_weak: bool, | |
| 903 | symbol_resolver: bool, | |
| 904 | alt_entry: bool, | |
| 905 | _pad2: u6 = 0, | |
| 906 | }, | |
| 845 | 907 | n_value: u64, |
| 846 | 908 | |
| 847 | pub fn stab(sym: nlist_64) bool { | |
| 848 | return N_STAB & sym.n_type != 0; | |
| 849 | } | |
| 850 | ||
| 851 | pub fn pext(sym: nlist_64) bool { | |
| 852 | return N_PEXT & sym.n_type != 0; | |
| 853 | } | |
| 854 | ||
| 855 | pub fn ext(sym: nlist_64) bool { | |
| 856 | return N_EXT & sym.n_type != 0; | |
| 857 | } | |
| 858 | ||
| 859 | pub fn sect(sym: nlist_64) bool { | |
| 860 | const type_ = N_TYPE & sym.n_type; | |
| 861 | return type_ == N_SECT; | |
| 862 | } | |
| 863 | ||
| 864 | pub fn undf(sym: nlist_64) bool { | |
| 865 | const type_ = N_TYPE & sym.n_type; | |
| 866 | return type_ == N_UNDF; | |
| 867 | } | |
| 868 | ||
| 869 | pub fn indr(sym: nlist_64) bool { | |
| 870 | const type_ = N_TYPE & sym.n_type; | |
| 871 | return type_ == N_INDR; | |
| 872 | } | |
| 873 | ||
| 874 | pub fn abs(sym: nlist_64) bool { | |
| 875 | const type_ = N_TYPE & sym.n_type; | |
| 876 | return type_ == N_ABS; | |
| 877 | } | |
| 878 | ||
| 879 | pub fn weakDef(sym: nlist_64) bool { | |
| 880 | return sym.n_desc & N_WEAK_DEF != 0; | |
| 881 | } | |
| 882 | ||
| 883 | pub fn weakRef(sym: nlist_64) bool { | |
| 884 | return sym.n_desc & N_WEAK_REF != 0; | |
| 885 | } | |
| 886 | ||
| 887 | pub fn discarded(sym: nlist_64) bool { | |
| 888 | return sym.n_desc & N_DESC_DISCARDED != 0; | |
| 889 | } | |
| 890 | ||
| 891 | pub fn noDeadStrip(sym: nlist_64) bool { | |
| 892 | return sym.n_desc & N_NO_DEAD_STRIP != 0; | |
| 893 | } | |
| 894 | ||
| 895 | 909 | pub fn tentative(sym: nlist_64) bool { |
| 896 | if (!sym.undf()) return false; | |
| 897 | return sym.n_value != 0; | |
| 910 | return sym.n_type.bits.type == .undf and sym.n_value != 0; | |
| 898 | 911 | } |
| 899 | 912 | }; |
| 900 | 913 | |
| ... | ... | @@ -2046,7 +2059,7 @@ pub const unwind_info_compressed_second_level_page_header = extern struct { |
| 2046 | 2059 | // encodings array |
| 2047 | 2060 | }; |
| 2048 | 2061 | |
| 2049 | pub const UnwindInfoCompressedEntry = packed struct { | |
| 2062 | pub const UnwindInfoCompressedEntry = packed struct(u32) { | |
| 2050 | 2063 | funcOffset: u24, |
| 2051 | 2064 | encodingIndex: u8, |
| 2052 | 2065 | }; |
lib/std/os/freebsd.zig+73| ... | ... | @@ -3,6 +3,7 @@ const fd_t = std.c.fd_t; |
| 3 | 3 | const off_t = std.c.off_t; |
| 4 | 4 | const unexpectedErrno = std.posix.unexpectedErrno; |
| 5 | 5 | const errno = std.posix.errno; |
| 6 | const builtin = @import("builtin"); | |
| 6 | 7 | |
| 7 | 8 | pub const CopyFileRangeError = std.posix.UnexpectedError || error{ |
| 8 | 9 | /// If infd is not open for reading or outfd is not open for writing, or |
| ... | ... | @@ -47,3 +48,75 @@ pub fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64, |
| 47 | 48 | else => |err| return unexpectedErrno(err), |
| 48 | 49 | } |
| 49 | 50 | } |
| 51 | ||
| 52 | pub const ucontext_t = extern struct { | |
| 53 | sigmask: std.c.sigset_t, | |
| 54 | mcontext: mcontext_t, | |
| 55 | link: ?*ucontext_t, | |
| 56 | stack: std.c.stack_t, | |
| 57 | flags: c_int, | |
| 58 | __spare__: [4]c_int, | |
| 59 | const mcontext_t = switch (builtin.cpu.arch) { | |
| 60 | .x86_64 => extern struct { | |
| 61 | onstack: u64, | |
| 62 | rdi: u64, | |
| 63 | rsi: u64, | |
| 64 | rdx: u64, | |
| 65 | rcx: u64, | |
| 66 | r8: u64, | |
| 67 | r9: u64, | |
| 68 | rax: u64, | |
| 69 | rbx: u64, | |
| 70 | rbp: u64, | |
| 71 | r10: u64, | |
| 72 | r11: u64, | |
| 73 | r12: u64, | |
| 74 | r13: u64, | |
| 75 | r14: u64, | |
| 76 | r15: u64, | |
| 77 | trapno: u32, | |
| 78 | fs: u16, | |
| 79 | gs: u16, | |
| 80 | addr: u64, | |
| 81 | flags: u32, | |
| 82 | es: u16, | |
| 83 | ds: u16, | |
| 84 | err: u64, | |
| 85 | rip: u64, | |
| 86 | cs: u64, | |
| 87 | rflags: u64, | |
| 88 | rsp: u64, | |
| 89 | ss: u64, | |
| 90 | len: u64, | |
| 91 | fpformat: u64, | |
| 92 | ownedfp: u64, | |
| 93 | fpstate: [64]u64 align(16), | |
| 94 | fsbase: u64, | |
| 95 | gsbase: u64, | |
| 96 | xfpustate: u64, | |
| 97 | xfpustate_len: u64, | |
| 98 | spare: [4]u64, | |
| 99 | }, | |
| 100 | .aarch64 => extern struct { | |
| 101 | gpregs: extern struct { | |
| 102 | x: [30]u64, | |
| 103 | lr: u64, | |
| 104 | sp: u64, | |
| 105 | elr: u64, | |
| 106 | spsr: u32, | |
| 107 | _pad: u32, | |
| 108 | }, | |
| 109 | fpregs: extern struct { | |
| 110 | q: [32]u128, | |
| 111 | sr: u32, | |
| 112 | cr: u32, | |
| 113 | flags: u32, | |
| 114 | _pad: u32, | |
| 115 | }, | |
| 116 | flags: u32, | |
| 117 | _pad: u32, | |
| 118 | _spare: [8]u64, | |
| 119 | }, | |
| 120 | else => void, | |
| 121 | }; | |
| 122 | }; |
lib/std/os/linux.zig-2| ... | ... | @@ -49,7 +49,6 @@ const arch_bits = switch (native_arch) { |
| 49 | 49 | .s390x => @import("linux/s390x.zig"), |
| 50 | 50 | else => struct { |
| 51 | 51 | pub const ucontext_t = void; |
| 52 | pub const getcontext = {}; | |
| 53 | 52 | }, |
| 54 | 53 | }; |
| 55 | 54 | |
| ... | ... | @@ -112,7 +111,6 @@ pub const timeval = arch_bits.timeval; |
| 112 | 111 | pub const timezone = arch_bits.timezone; |
| 113 | 112 | pub const ucontext_t = arch_bits.ucontext_t; |
| 114 | 113 | pub const user_desc = arch_bits.user_desc; |
| 115 | pub const getcontext = arch_bits.getcontext; | |
| 116 | 114 | |
| 117 | 115 | pub const tls = @import("linux/tls.zig"); |
| 118 | 116 | pub const BPF = @import("linux/bpf.zig"); |
lib/std/os/linux/aarch64.zig-3| ... | ... | @@ -260,7 +260,4 @@ pub const ucontext_t = extern struct { |
| 260 | 260 | mcontext: mcontext_t, |
| 261 | 261 | }; |
| 262 | 262 | |
| 263 | /// TODO | |
| 264 | pub const getcontext = {}; | |
| 265 | ||
| 266 | 263 | pub const Elf_Symndx = u32; |
lib/std/os/linux/arm.zig-3| ... | ... | @@ -310,7 +310,4 @@ pub const ucontext_t = extern struct { |
| 310 | 310 | regspace: [64]u64, |
| 311 | 311 | }; |
| 312 | 312 | |
| 313 | /// TODO | |
| 314 | pub const getcontext = {}; | |
| 315 | ||
| 316 | 313 | pub const Elf_Symndx = u32; |
lib/std/os/linux/hexagon.zig-3| ... | ... | @@ -237,6 +237,3 @@ pub const VDSO = void; |
| 237 | 237 | |
| 238 | 238 | /// TODO |
| 239 | 239 | pub const ucontext_t = void; |
| 240 | ||
| 241 | /// TODO | |
| 242 | pub const getcontext = {}; |
lib/std/os/linux/loongarch64.zig-3| ... | ... | @@ -250,6 +250,3 @@ pub const ucontext_t = extern struct { |
| 250 | 250 | }; |
| 251 | 251 | |
| 252 | 252 | pub const Elf_Symndx = u32; |
| 253 | ||
| 254 | /// TODO | |
| 255 | pub const getcontext = {}; |
lib/std/os/linux/m68k.zig-3| ... | ... | @@ -258,6 +258,3 @@ pub const VDSO = void; |
| 258 | 258 | |
| 259 | 259 | /// TODO |
| 260 | 260 | pub const ucontext_t = void; |
| 261 | ||
| 262 | /// TODO | |
| 263 | pub const getcontext = {}; |
lib/std/os/linux/mips.zig-3| ... | ... | @@ -349,6 +349,3 @@ pub const Elf_Symndx = u32; |
| 349 | 349 | |
| 350 | 350 | /// TODO |
| 351 | 351 | pub const ucontext_t = void; |
| 352 | ||
| 353 | /// TODO | |
| 354 | pub const getcontext = {}; |
lib/std/os/linux/mips64.zig-3| ... | ... | @@ -328,6 +328,3 @@ pub const Elf_Symndx = u32; |
| 328 | 328 | |
| 329 | 329 | /// TODO |
| 330 | 330 | pub const ucontext_t = void; |
| 331 | ||
| 332 | /// TODO | |
| 333 | pub const getcontext = {}; |
lib/std/os/linux/powerpc.zig-3| ... | ... | @@ -381,6 +381,3 @@ pub const ucontext_t = extern struct { |
| 381 | 381 | }; |
| 382 | 382 | |
| 383 | 383 | pub const Elf_Symndx = u32; |
| 384 | ||
| 385 | /// TODO | |
| 386 | pub const getcontext = {}; |
lib/std/os/linux/powerpc64.zig-3| ... | ... | @@ -376,6 +376,3 @@ pub const ucontext_t = extern struct { |
| 376 | 376 | }; |
| 377 | 377 | |
| 378 | 378 | pub const Elf_Symndx = u32; |
| 379 | ||
| 380 | /// TODO | |
| 381 | pub const getcontext = {}; |
lib/std/os/linux/riscv32.zig-3| ... | ... | @@ -255,6 +255,3 @@ pub const ucontext_t = extern struct { |
| 255 | 255 | sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask |
| 256 | 256 | mcontext: mcontext_t, |
| 257 | 257 | }; |
| 258 | ||
| 259 | /// TODO | |
| 260 | pub const getcontext = {}; |
lib/std/os/linux/riscv64.zig-3| ... | ... | @@ -255,6 +255,3 @@ pub const ucontext_t = extern struct { |
| 255 | 255 | sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask |
| 256 | 256 | mcontext: mcontext_t, |
| 257 | 257 | }; |
| 258 | ||
| 259 | /// TODO | |
| 260 | pub const getcontext = {}; |
lib/std/os/linux/s390x.zig-3| ... | ... | @@ -273,6 +273,3 @@ pub const mcontext_t = extern struct { |
| 273 | 273 | __regs2: [18]u32, |
| 274 | 274 | __regs3: [16]f64, |
| 275 | 275 | }; |
| 276 | ||
| 277 | /// TODO | |
| 278 | pub const getcontext = {}; |
lib/std/os/linux/sparc64.zig-3| ... | ... | @@ -426,6 +426,3 @@ pub const ucontext_t = extern struct { |
| 426 | 426 | stack: stack_t, |
| 427 | 427 | sigset: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask |
| 428 | 428 | }; |
| 429 | ||
| 430 | /// TODO | |
| 431 | pub const getcontext = {}; |
lib/std/os/linux/x86.zig+15-25| ... | ... | @@ -80,28 +80,32 @@ pub fn syscall6( |
| 80 | 80 | arg5: usize, |
| 81 | 81 | arg6: usize, |
| 82 | 82 | ) usize { |
| 83 | // arg5/arg6 are passed via memory as we're out of registers if ebp is used as frame pointer, or | |
| 84 | // if we're compiling with PIC. We push arg5/arg6 on the stack before changing ebp/esp as the | |
| 85 | // compiler may reference arg5/arg6 as an offset relative to ebp/esp. | |
| 83 | // arg6 can't be passed to asm in a register because ebp might be reserved as the frame pointer | |
| 84 | // and there are no more GPRs available; so we'll need a memory operand for it. Adding that | |
| 85 | // memory operand means that on PIC we might need a reference to the GOT, which in turn needs | |
| 86 | // *its* own GPR, so we need to pass another arg in memory too! This is surprisingly hard to get | |
| 87 | // right, because we can't touch esp or ebp until we're done with the memory input (as that | |
| 88 | // input could be relative to esp or ebp). | |
| 89 | const args56: [2]usize = .{ arg5, arg6 }; | |
| 86 | 90 | return asm volatile ( |
| 87 | \\ push %[arg5] | |
| 88 | \\ push %[arg6] | |
| 89 | \\ push %%edi | |
| 91 | \\ push %[args56] | |
| 90 | 92 | \\ push %%ebp |
| 91 | \\ mov 12(%%esp), %%edi | |
| 92 | \\ mov 8(%%esp), %%ebp | |
| 93 | \\ mov 4(%%esp), %%ebp | |
| 94 | \\ mov %%edi, 4(%%esp) | |
| 95 | \\ // The saved %edi and %ebp are on the stack, and %ebp points to `args56`. | |
| 96 | \\ // Prepare the last two args, syscall, then pop the saved %ebp and %edi. | |
| 97 | \\ mov (%%ebp), %%edi | |
| 98 | \\ mov 4(%%ebp), %%ebp | |
| 93 | 99 | \\ int $0x80 |
| 94 | 100 | \\ pop %%ebp |
| 95 | 101 | \\ pop %%edi |
| 96 | \\ add $8, %%esp | |
| 97 | 102 | : [ret] "={eax}" (-> usize), |
| 98 | 103 | : [number] "{eax}" (@intFromEnum(number)), |
| 99 | 104 | [arg1] "{ebx}" (arg1), |
| 100 | 105 | [arg2] "{ecx}" (arg2), |
| 101 | 106 | [arg3] "{edx}" (arg3), |
| 102 | 107 | [arg4] "{esi}" (arg4), |
| 103 | [arg5] "rm" (arg5), | |
| 104 | [arg6] "rm" (arg6), | |
| 108 | [args56] "rm" (&args56), | |
| 105 | 109 | : .{ .memory = true }); |
| 106 | 110 | } |
| 107 | 111 | |
| ... | ... | @@ -432,17 +436,3 @@ pub fn getContextInternal() callconv(.naked) usize { |
| 432 | 436 | [sigset_size] "i" (linux.NSIG / 8), |
| 433 | 437 | : .{ .cc = true, .memory = true, .eax = true, .ecx = true, .edx = true }); |
| 434 | 438 | } |
| 435 | ||
| 436 | pub inline fn getcontext(context: *ucontext_t) usize { | |
| 437 | // This method is used so that getContextInternal can control | |
| 438 | // its prologue in order to read ESP from a constant offset. | |
| 439 | // An aligned stack is not needed for getContextInternal. | |
| 440 | var clobber_edx: usize = undefined; | |
| 441 | return asm volatile ( | |
| 442 | \\ calll %[getContextInternal:P] | |
| 443 | : [_] "={eax}" (-> usize), | |
| 444 | [_] "={edx}" (clobber_edx), | |
| 445 | : [_] "{edx}" (context), | |
| 446 | [getContextInternal] "X" (&getContextInternal), | |
| 447 | : .{ .cc = true, .memory = true, .ecx = true }); | |
| 448 | } |
lib/std/os/linux/x86_64.zig-95| ... | ... | @@ -352,98 +352,3 @@ pub const ucontext_t = extern struct { |
| 352 | 352 | sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a glibc-compatible (1024-bit) sigmask. |
| 353 | 353 | fpregs_mem: [64]usize, // Not part of kernel ABI, only part of glibc ucontext_t |
| 354 | 354 | }; |
| 355 | ||
| 356 | fn gpRegisterOffset(comptime reg_index: comptime_int) usize { | |
| 357 | return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index; | |
| 358 | } | |
| 359 | ||
| 360 | fn getContextInternal() callconv(.naked) usize { | |
| 361 | // TODO: Read GS/FS registers? | |
| 362 | asm volatile ( | |
| 363 | \\ movq $0, %[flags_offset:c](%%rdi) | |
| 364 | \\ movq $0, %[link_offset:c](%%rdi) | |
| 365 | \\ movq %%r8, %[r8_offset:c](%%rdi) | |
| 366 | \\ movq %%r9, %[r9_offset:c](%%rdi) | |
| 367 | \\ movq %%r10, %[r10_offset:c](%%rdi) | |
| 368 | \\ movq %%r11, %[r11_offset:c](%%rdi) | |
| 369 | \\ movq %%r12, %[r12_offset:c](%%rdi) | |
| 370 | \\ movq %%r13, %[r13_offset:c](%%rdi) | |
| 371 | \\ movq %%r14, %[r14_offset:c](%%rdi) | |
| 372 | \\ movq %%r15, %[r15_offset:c](%%rdi) | |
| 373 | \\ movq %%rdi, %[rdi_offset:c](%%rdi) | |
| 374 | \\ movq %%rsi, %[rsi_offset:c](%%rdi) | |
| 375 | \\ movq %%rbp, %[rbp_offset:c](%%rdi) | |
| 376 | \\ movq %%rbx, %[rbx_offset:c](%%rdi) | |
| 377 | \\ movq %%rdx, %[rdx_offset:c](%%rdi) | |
| 378 | \\ movq %%rax, %[rax_offset:c](%%rdi) | |
| 379 | \\ movq %%rcx, %[rcx_offset:c](%%rdi) | |
| 380 | \\ movq (%%rsp), %%rcx | |
| 381 | \\ movq %%rcx, %[rip_offset:c](%%rdi) | |
| 382 | \\ leaq 8(%%rsp), %%rcx | |
| 383 | \\ movq %%rcx, %[rsp_offset:c](%%rdi) | |
| 384 | \\ pushfq | |
| 385 | \\ popq %[efl_offset:c](%%rdi) | |
| 386 | \\ leaq %[fpmem_offset:c](%%rdi), %%rcx | |
| 387 | \\ movq %%rcx, %[fpstate_offset:c](%%rdi) | |
| 388 | \\ fnstenv (%%rcx) | |
| 389 | \\ fldenv (%%rcx) | |
| 390 | \\ stmxcsr %[mxcsr_offset:c](%%rdi) | |
| 391 | \\ leaq %[stack_offset:c](%%rdi), %%rsi | |
| 392 | \\ movq %%rdi, %%r8 | |
| 393 | \\ xorl %%edi, %%edi | |
| 394 | \\ movl %[sigaltstack], %%eax | |
| 395 | \\ syscall | |
| 396 | \\ testq %%rax, %%rax | |
| 397 | \\ jnz 0f | |
| 398 | \\ movl %[sigprocmask], %%eax | |
| 399 | \\ xorl %%esi, %%esi | |
| 400 | \\ leaq %[sigmask_offset:c](%%r8), %%rdx | |
| 401 | \\ movl %[sigset_size], %%r10d | |
| 402 | \\ syscall | |
| 403 | \\0: | |
| 404 | \\ retq | |
| 405 | : | |
| 406 | : [flags_offset] "i" (@offsetOf(ucontext_t, "flags")), | |
| 407 | [link_offset] "i" (@offsetOf(ucontext_t, "link")), | |
| 408 | [r8_offset] "i" (comptime gpRegisterOffset(REG.R8)), | |
| 409 | [r9_offset] "i" (comptime gpRegisterOffset(REG.R9)), | |
| 410 | [r10_offset] "i" (comptime gpRegisterOffset(REG.R10)), | |
| 411 | [r11_offset] "i" (comptime gpRegisterOffset(REG.R11)), | |
| 412 | [r12_offset] "i" (comptime gpRegisterOffset(REG.R12)), | |
| 413 | [r13_offset] "i" (comptime gpRegisterOffset(REG.R13)), | |
| 414 | [r14_offset] "i" (comptime gpRegisterOffset(REG.R14)), | |
| 415 | [r15_offset] "i" (comptime gpRegisterOffset(REG.R15)), | |
| 416 | [rdi_offset] "i" (comptime gpRegisterOffset(REG.RDI)), | |
| 417 | [rsi_offset] "i" (comptime gpRegisterOffset(REG.RSI)), | |
| 418 | [rbp_offset] "i" (comptime gpRegisterOffset(REG.RBP)), | |
| 419 | [rbx_offset] "i" (comptime gpRegisterOffset(REG.RBX)), | |
| 420 | [rdx_offset] "i" (comptime gpRegisterOffset(REG.RDX)), | |
| 421 | [rax_offset] "i" (comptime gpRegisterOffset(REG.RAX)), | |
| 422 | [rcx_offset] "i" (comptime gpRegisterOffset(REG.RCX)), | |
| 423 | [rsp_offset] "i" (comptime gpRegisterOffset(REG.RSP)), | |
| 424 | [rip_offset] "i" (comptime gpRegisterOffset(REG.RIP)), | |
| 425 | [efl_offset] "i" (comptime gpRegisterOffset(REG.EFL)), | |
| 426 | [fpstate_offset] "i" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")), | |
| 427 | [fpmem_offset] "i" (@offsetOf(ucontext_t, "fpregs_mem")), | |
| 428 | [mxcsr_offset] "i" (@offsetOf(ucontext_t, "fpregs_mem") + @offsetOf(fpstate, "mxcsr")), | |
| 429 | [sigaltstack] "i" (@intFromEnum(linux.SYS.sigaltstack)), | |
| 430 | [stack_offset] "i" (@offsetOf(ucontext_t, "stack")), | |
| 431 | [sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)), | |
| 432 | [sigmask_offset] "i" (@offsetOf(ucontext_t, "sigmask")), | |
| 433 | [sigset_size] "i" (@sizeOf(sigset_t)), | |
| 434 | : .{ .cc = true, .memory = true, .rax = true, .rcx = true, .rdx = true, .rdi = true, .rsi = true, .r8 = true, .r10 = true, .r11 = true }); | |
| 435 | } | |
| 436 | ||
| 437 | pub inline fn getcontext(context: *ucontext_t) usize { | |
| 438 | // This method is used so that getContextInternal can control | |
| 439 | // its prologue in order to read RSP from a constant offset | |
| 440 | // An aligned stack is not needed for getContextInternal. | |
| 441 | var clobber_rdi: usize = undefined; | |
| 442 | return asm volatile ( | |
| 443 | \\ callq %[getContextInternal:P] | |
| 444 | : [_] "={rax}" (-> usize), | |
| 445 | [_] "={rdi}" (clobber_rdi), | |
| 446 | : [_] "{rdi}" (context), | |
| 447 | [getContextInternal] "X" (&getContextInternal), | |
| 448 | : .{ .cc = true, .memory = true, .rcx = true, .rdx = true, .rsi = true, .r8 = true, .r10 = true, .r11 = true }); | |
| 449 | } |
lib/std/os/windows.zig+2-2| ... | ... | @@ -2849,7 +2849,7 @@ pub fn unexpectedError(err: Win32Error) UnexpectedError { |
| 2849 | 2849 | std.debug.print("error.Unexpected: GetLastError({d}): {f}\n", .{ |
| 2850 | 2850 | err, std.unicode.fmtUtf16Le(buf_wstr[0..len]), |
| 2851 | 2851 | }); |
| 2852 | std.debug.dumpCurrentStackTrace(@returnAddress()); | |
| 2852 | std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() }); | |
| 2853 | 2853 | } |
| 2854 | 2854 | return error.Unexpected; |
| 2855 | 2855 | } |
| ... | ... | @@ -2863,7 +2863,7 @@ pub fn unexpectedWSAError(err: ws2_32.WinsockError) UnexpectedError { |
| 2863 | 2863 | pub fn unexpectedStatus(status: NTSTATUS) UnexpectedError { |
| 2864 | 2864 | if (std.posix.unexpected_error_tracing) { |
| 2865 | 2865 | std.debug.print("error.Unexpected NTSTATUS=0x{x}\n", .{@intFromEnum(status)}); |
| 2866 | std.debug.dumpCurrentStackTrace(@returnAddress()); | |
| 2866 | std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() }); | |
| 2867 | 2867 | } |
| 2868 | 2868 | return error.Unexpected; |
| 2869 | 2869 | } |
lib/std/posix.zig+2-5| ... | ... | @@ -47,7 +47,6 @@ else switch (native_os) { |
| 47 | 47 | .linux => linux, |
| 48 | 48 | .plan9 => std.os.plan9, |
| 49 | 49 | else => struct { |
| 50 | pub const ucontext_t = void; | |
| 51 | 50 | pub const pid_t = void; |
| 52 | 51 | pub const pollfd = void; |
| 53 | 52 | pub const fd_t = void; |
| ... | ... | @@ -141,7 +140,6 @@ pub const in_pktinfo = system.in_pktinfo; |
| 141 | 140 | pub const in6_pktinfo = system.in6_pktinfo; |
| 142 | 141 | pub const ino_t = system.ino_t; |
| 143 | 142 | pub const linger = system.linger; |
| 144 | pub const mcontext_t = system.mcontext_t; | |
| 145 | 143 | pub const mode_t = system.mode_t; |
| 146 | 144 | pub const msghdr = system.msghdr; |
| 147 | 145 | pub const msghdr_const = system.msghdr_const; |
| ... | ... | @@ -170,7 +168,6 @@ pub const timespec = system.timespec; |
| 170 | 168 | pub const timestamp_t = system.timestamp_t; |
| 171 | 169 | pub const timeval = system.timeval; |
| 172 | 170 | pub const timezone = system.timezone; |
| 173 | pub const ucontext_t = system.ucontext_t; | |
| 174 | 171 | pub const uid_t = system.uid_t; |
| 175 | 172 | pub const user_desc = system.user_desc; |
| 176 | 173 | pub const utsname = system.utsname; |
| ... | ... | @@ -692,7 +689,7 @@ pub fn abort() noreturn { |
| 692 | 689 | // even when linking libc on Windows we use our own abort implementation. |
| 693 | 690 | // See https://github.com/ziglang/zig/issues/2071 for more details. |
| 694 | 691 | if (native_os == .windows) { |
| 695 | if (builtin.mode == .Debug) { | |
| 692 | if (builtin.mode == .Debug and windows.peb().BeingDebugged != 0) { | |
| 696 | 693 | @breakpoint(); |
| 697 | 694 | } |
| 698 | 695 | windows.kernel32.ExitProcess(3); |
| ... | ... | @@ -7590,7 +7587,7 @@ pub const UnexpectedError = error{ |
| 7590 | 7587 | pub fn unexpectedErrno(err: E) UnexpectedError { |
| 7591 | 7588 | if (unexpected_error_tracing) { |
| 7592 | 7589 | std.debug.print("unexpected errno: {d}\n", .{@intFromEnum(err)}); |
| 7593 | std.debug.dumpCurrentStackTrace(null); | |
| 7590 | std.debug.dumpCurrentStackTrace(.{}); | |
| 7594 | 7591 | } |
| 7595 | 7592 | return error.Unexpected; |
| 7596 | 7593 | } |
lib/std/start.zig+5-2| ... | ... | @@ -635,8 +635,11 @@ pub inline fn callMain() u8 { |
| 635 | 635 | else => {}, |
| 636 | 636 | } |
| 637 | 637 | std.log.err("{s}", .{@errorName(err)}); |
| 638 | if (@errorReturnTrace()) |trace| { | |
| 639 | std.debug.dumpStackTrace(trace.*); | |
| 638 | switch (native_os) { | |
| 639 | .freestanding, .other => {}, | |
| 640 | else => if (@errorReturnTrace()) |trace| { | |
| 641 | std.debug.dumpStackTrace(trace); | |
| 642 | }, | |
| 640 | 643 | } |
| 641 | 644 | return 1; |
| 642 | 645 | }; |
lib/std/std.zig+16| ... | ... | @@ -171,6 +171,22 @@ pub const Options = struct { |
| 171 | 171 | http_enable_ssl_key_log_file: bool = @import("builtin").mode == .Debug, |
| 172 | 172 | |
| 173 | 173 | side_channels_mitigations: crypto.SideChannelsMitigations = crypto.default_side_channels_mitigations, |
| 174 | ||
| 175 | /// Whether to allow capturing and writing stack traces. This affects the following functions: | |
| 176 | /// * `debug.captureCurrentStackTrace` | |
| 177 | /// * `debug.writeCurrentStackTrace` | |
| 178 | /// * `debug.dumpCurrentStackTrace` | |
| 179 | /// * `debug.writeStackTrace` | |
| 180 | /// * `debug.dumpStackTrace` | |
| 181 | /// | |
| 182 | /// Stack traces can generally be collected and printed when debug info is stripped, but are | |
| 183 | /// often less useful since they usually cannot be mapped to source locations and/or have bad | |
| 184 | /// source locations. The stack tracing logic can also be quite large, which may be undesirable, | |
| 185 | /// particularly in ReleaseSmall. | |
| 186 | /// | |
| 187 | /// If this is `false`, then captured stack traces will always be empty, and attempts to write | |
| 188 | /// stack traces will just print an error to the relevant `Io.Writer` and return. | |
| 189 | allow_stack_tracing: bool = !@import("builtin").strip_debug_info, | |
| 174 | 190 | }; |
| 175 | 191 | |
| 176 | 192 | // This forces the start.zig file to be imported, and the comptime logic inside that |
lib/std/testing/FailingAllocator.zig+2-6| ... | ... | @@ -64,12 +64,8 @@ fn alloc( |
| 64 | 64 | const self: *FailingAllocator = @ptrCast(@alignCast(ctx)); |
| 65 | 65 | if (self.alloc_index == self.fail_index) { |
| 66 | 66 | if (!self.has_induced_failure) { |
| 67 | @memset(&self.stack_addresses, 0); | |
| 68 | var stack_trace = std.builtin.StackTrace{ | |
| 69 | .instruction_addresses = &self.stack_addresses, | |
| 70 | .index = 0, | |
| 71 | }; | |
| 72 | std.debug.captureStackTrace(return_address, &stack_trace); | |
| 67 | const st = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &self.stack_addresses); | |
| 68 | @memset(self.stack_addresses[@min(st.index, self.stack_addresses.len)..], 0); | |
| 73 | 69 | self.has_induced_failure = true; |
| 74 | 70 | } |
| 75 | 71 | return null; |
lib/zig.h+8| ... | ... | @@ -1510,8 +1510,16 @@ static inline zig_u128 zig_shl_u128(zig_u128 lhs, uint8_t rhs) { |
| 1510 | 1510 | } |
| 1511 | 1511 | |
| 1512 | 1512 | static inline zig_i128 zig_shr_i128(zig_i128 lhs, uint8_t rhs) { |
| 1513 | // This works around a GCC miscompilation, but it has the side benefit of | |
| 1514 | // emitting better code. It is behind the `#if` because it depends on | |
| 1515 | // arithmetic right shift, which is implementation-defined in C, but should | |
| 1516 | // be guaranteed on any GCC-compatible compiler. | |
| 1517 | #if defined(zig_gnuc) | |
| 1518 | return lhs >> rhs; | |
| 1519 | #else | |
| 1513 | 1520 | zig_i128 sign_mask = lhs < zig_make_i128(0, 0) ? -zig_make_i128(0, 1) : zig_make_i128(0, 0); |
| 1514 | 1521 | return ((lhs ^ sign_mask) >> rhs) ^ sign_mask; |
| 1522 | #endif | |
| 1515 | 1523 | } |
| 1516 | 1524 | |
| 1517 | 1525 | static inline zig_i128 zig_shl_i128(zig_i128 lhs, uint8_t rhs) { |
src/Sema.zig+3-3| ... | ... | @@ -1130,8 +1130,8 @@ fn analyzeBodyInner( |
| 1130 | 1130 | const tags = sema.code.instructions.items(.tag); |
| 1131 | 1131 | const datas = sema.code.instructions.items(.data); |
| 1132 | 1132 | |
| 1133 | var crash_info = crash_report.prepAnalyzeBody(sema, block, body); | |
| 1134 | crash_info.push(); | |
| 1133 | var crash_info: crash_report.AnalyzeBody = undefined; | |
| 1134 | crash_info.push(sema, block, body); | |
| 1135 | 1135 | defer crash_info.pop(); |
| 1136 | 1136 | |
| 1137 | 1137 | // We use a while (true) loop here to avoid a redundant way of breaking out of |
| ... | ... | @@ -2632,7 +2632,7 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg |
| 2632 | 2632 | std.debug.print("compile error during Sema:\n", .{}); |
| 2633 | 2633 | var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory"); |
| 2634 | 2634 | error_bundle.renderToStdErr(.{ .ttyconf = .no_color }); |
| 2635 | crash_report.compilerPanic("unexpected compile error occurred", null); | |
| 2635 | std.debug.panicExtra(@returnAddress(), "unexpected compile error occurred", .{}); | |
| 2636 | 2636 | } |
| 2637 | 2637 | |
| 2638 | 2638 | if (block) |start_block| { |
src/Zcu/PerThread.zig+4| ... | ... | @@ -28,6 +28,7 @@ const Value = @import("../Value.zig"); |
| 28 | 28 | const Zcu = @import("../Zcu.zig"); |
| 29 | 29 | const Compilation = @import("../Compilation.zig"); |
| 30 | 30 | const codegen = @import("../codegen.zig"); |
| 31 | const crash_report = @import("../crash_report.zig"); | |
| 31 | 32 | const Zir = std.zig.Zir; |
| 32 | 33 | const Zoir = std.zig.Zoir; |
| 33 | 34 | const ZonGen = std.zig.ZonGen; |
| ... | ... | @@ -4390,6 +4391,9 @@ pub fn addDependency(pt: Zcu.PerThread, unit: AnalUnit, dependee: InternPool.Dep |
| 4390 | 4391 | pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air, out: *@import("../link.zig").ZcuTask.LinkFunc.SharedMir) void { |
| 4391 | 4392 | const zcu = pt.zcu; |
| 4392 | 4393 | |
| 4394 | crash_report.CodegenFunc.start(zcu, func_index); | |
| 4395 | defer crash_report.CodegenFunc.stop(func_index); | |
| 4396 | ||
| 4393 | 4397 | var timer = zcu.comp.startTimer(); |
| 4394 | 4398 | |
| 4395 | 4399 | const success: bool = if (runCodegenInner(pt, func_index, air)) |mir| success: { |
src/crash_report.zig+109-461| ... | ... | @@ -1,34 +1,31 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const build_options = @import("build_options"); | |
| 4 | const debug = std.debug; | |
| 5 | const print_zir = @import("print_zir.zig"); | |
| 6 | const windows = std.os.windows; | |
| 7 | const posix = std.posix; | |
| 8 | const native_os = builtin.os.tag; | |
| 9 | ||
| 10 | const Zcu = @import("Zcu.zig"); | |
| 11 | const Sema = @import("Sema.zig"); | |
| 12 | const InternPool = @import("InternPool.zig"); | |
| 13 | const Zir = std.zig.Zir; | |
| 14 | const Decl = Zcu.Decl; | |
| 15 | const dev = @import("dev.zig"); | |
| 16 | ||
| 17 | /// To use these crash report diagnostics, publish this panic in your main file | |
| 18 | /// and add `pub const enable_segfault_handler = false;` to your `std_options`. | |
| 19 | /// You will also need to call initialize() on startup, preferably as the very first operation in your program. | |
| 20 | pub const panic = if (build_options.enable_debug_extensions) | |
| 21 | std.debug.FullPanic(compilerPanic) | |
| 22 | else if (dev.env == .bootstrap) | |
| 1 | /// We override the panic implementation to our own one, so we can print our own information before | |
| 2 | /// calling the default panic handler. This declaration must be re-exposed from `@import("root")`. | |
| 3 | pub const panic = if (dev.env == .bootstrap) | |
| 23 | 4 | std.debug.simple_panic |
| 24 | 5 | else |
| 25 | std.debug.FullPanic(std.debug.defaultPanic); | |
| 6 | std.debug.FullPanic(panicImpl); | |
| 26 | 7 | |
| 27 | /// Install signal handlers to identify crashes and report diagnostics. | |
| 28 | pub fn initialize() void { | |
| 29 | if (build_options.enable_debug_extensions and debug.have_segfault_handling_support) { | |
| 30 | attachSegfaultHandler(); | |
| 31 | } | |
| 8 | /// We let std install its segfault handler, but we override the target-agnostic handler it calls, | |
| 9 | /// so we can print our own information before calling the default segfault logic. This declaration | |
| 10 | /// must be re-exposed from `@import("root")`. | |
| 11 | pub const debug = struct { | |
| 12 | pub const handleSegfault = handleSegfaultImpl; | |
| 13 | }; | |
| 14 | ||
| 15 | /// Printed in panic messages when suggesting a command to run, allowing copy-pasting the command. | |
| 16 | /// Set by `main` as soon as arguments are known. The value here is a default in case we somehow | |
| 17 | /// crash earlier than that. | |
| 18 | pub var zig_argv0: []const u8 = "zig"; | |
| 19 | ||
| 20 | fn handleSegfaultImpl(addr: ?usize, name: []const u8, opt_ctx: ?std.debug.CpuContextPtr) noreturn { | |
| 21 | @branchHint(.cold); | |
| 22 | dumpCrashContext() catch {}; | |
| 23 | std.debug.defaultHandleSegfault(addr, name, opt_ctx); | |
| 24 | } | |
| 25 | fn panicImpl(msg: []const u8, first_trace_addr: ?usize) noreturn { | |
| 26 | @branchHint(.cold); | |
| 27 | dumpCrashContext() catch {}; | |
| 28 | std.debug.defaultPanic(msg, first_trace_addr orelse @returnAddress()); | |
| 32 | 29 | } |
| 33 | 30 | |
| 34 | 31 | pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct { |
| ... | ... | @@ -38,63 +35,98 @@ pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct { |
| 38 | 35 | body: []const Zir.Inst.Index, |
| 39 | 36 | body_index: usize, |
| 40 | 37 | |
| 41 | pub fn push(self: *@This()) void { | |
| 42 | const head = &zir_state; | |
| 43 | debug.assert(self.parent == null); | |
| 44 | self.parent = head.*; | |
| 45 | head.* = self; | |
| 46 | } | |
| 38 | threadlocal var current: ?*AnalyzeBody = null; | |
| 47 | 39 | |
| 48 | pub fn pop(self: *@This()) void { | |
| 49 | const head = &zir_state; | |
| 50 | const old = head.*.?; | |
| 51 | debug.assert(old == self); | |
| 52 | head.* = old.parent; | |
| 40 | pub fn setBodyIndex(ab: *AnalyzeBody, index: usize) void { | |
| 41 | ab.body_index = index; | |
| 53 | 42 | } |
| 54 | 43 | |
| 55 | pub fn setBodyIndex(self: *@This(), index: usize) void { | |
| 56 | self.body_index = index; | |
| 44 | pub fn push(ab: *AnalyzeBody, sema: *Sema, block: *Sema.Block, body: []const Zir.Inst.Index) void { | |
| 45 | ab.* = .{ | |
| 46 | .parent = current, | |
| 47 | .sema = sema, | |
| 48 | .block = block, | |
| 49 | .body = body, | |
| 50 | .body_index = 0, | |
| 51 | }; | |
| 52 | current = ab; | |
| 53 | } | |
| 54 | pub fn pop(ab: *AnalyzeBody) void { | |
| 55 | std.debug.assert(current.? == ab); // `Sema.analyzeBodyInner` did not match push/pop calls | |
| 56 | current = ab.parent; | |
| 57 | 57 | } |
| 58 | 58 | } else struct { |
| 59 | pub inline fn push(_: @This()) void {} | |
| 60 | pub inline fn pop(_: @This()) void {} | |
| 59 | const current: ?noreturn = null; | |
| 60 | // Dummy implementation, with functions marked `inline` to avoid interfering with tail calls. | |
| 61 | pub inline fn push(_: AnalyzeBody, _: *Sema, _: *Sema.Block, _: []const Zir.Inst.Index) void {} | |
| 62 | pub inline fn pop(_: AnalyzeBody) void {} | |
| 61 | 63 | pub inline fn setBodyIndex(_: @This(), _: usize) void {} |
| 62 | 64 | }; |
| 63 | 65 | |
| 64 | threadlocal var zir_state: ?*AnalyzeBody = if (build_options.enable_debug_extensions) null else @compileError("Cannot use zir_state without debug extensions."); | |
| 66 | pub const CodegenFunc = if (build_options.enable_debug_extensions) struct { | |
| 67 | zcu: *const Zcu, | |
| 68 | func_index: InternPool.Index, | |
| 69 | threadlocal var current: ?CodegenFunc = null; | |
| 70 | pub fn start(zcu: *const Zcu, func_index: InternPool.Index) void { | |
| 71 | std.debug.assert(current == null); | |
| 72 | current = .{ .zcu = zcu, .func_index = func_index }; | |
| 73 | } | |
| 74 | pub fn stop(func_index: InternPool.Index) void { | |
| 75 | std.debug.assert(current.?.func_index == func_index); | |
| 76 | current = null; | |
| 77 | } | |
| 78 | } else struct { | |
| 79 | const current: ?noreturn = null; | |
| 80 | // Dummy implementation | |
| 81 | pub fn start(_: *const Zcu, _: InternPool.Index) void {} | |
| 82 | pub fn stop(_: InternPool.Index) void {} | |
| 83 | }; | |
| 65 | 84 | |
| 66 | pub fn prepAnalyzeBody(sema: *Sema, block: *Sema.Block, body: []const Zir.Inst.Index) AnalyzeBody { | |
| 67 | return if (build_options.enable_debug_extensions) .{ | |
| 68 | .parent = null, | |
| 69 | .sema = sema, | |
| 70 | .block = block, | |
| 71 | .body = body, | |
| 72 | .body_index = 0, | |
| 73 | } else .{}; | |
| 74 | } | |
| 85 | fn dumpCrashContext() Io.Writer.Error!void { | |
| 86 | const S = struct { | |
| 87 | /// In the case of recursive panics or segfaults, don't print the context for a second time. | |
| 88 | threadlocal var already_dumped = false; | |
| 89 | /// TODO: make this unnecessary. It exists because `print_zir` currently needs an allocator, | |
| 90 | /// but that shouldn't be necessary---it's already only used in one place. | |
| 91 | threadlocal var crash_heap: [64 * 1024]u8 = undefined; | |
| 92 | }; | |
| 93 | if (S.already_dumped) return; | |
| 94 | S.already_dumped = true; | |
| 95 | ||
| 96 | // TODO: this does mean that a different thread could grab the stderr mutex between the context | |
| 97 | // and the actual panic printing, which would be quite confusing. | |
| 98 | const stderr = std.debug.lockStderrWriter(&.{}); | |
| 99 | defer std.debug.unlockStderrWriter(); | |
| 75 | 100 | |
| 76 | fn dumpStatusReport() !void { | |
| 77 | const anal = zir_state orelse return; | |
| 78 | // Note: We have the panic mutex here, so we can safely use the global crash heap. | |
| 79 | var fba = std.heap.FixedBufferAllocator.init(&crash_heap); | |
| 80 | const allocator = fba.allocator(); | |
| 101 | try stderr.writeAll("Compiler crash context:\n"); | |
| 81 | 102 | |
| 82 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | |
| 83 | const stderr = &stderr_fw.interface; | |
| 103 | if (CodegenFunc.current) |*cg| { | |
| 104 | const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav; | |
| 105 | const func_fqn = cg.zcu.intern_pool.getNav(func_nav).fqn; | |
| 106 | try stderr.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)}); | |
| 107 | } else if (AnalyzeBody.current) |anal| { | |
| 108 | try dumpCrashContextSema(anal, stderr, &S.crash_heap); | |
| 109 | } else { | |
| 110 | try stderr.writeAll("(no context)\n\n"); | |
| 111 | } | |
| 112 | } | |
| 113 | fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void { | |
| 84 | 114 | const block: *Sema.Block = anal.block; |
| 85 | 115 | const zcu = anal.sema.pt.zcu; |
| 116 | const comp = zcu.comp; | |
| 117 | ||
| 118 | var fba: std.heap.FixedBufferAllocator = .init(crash_heap); | |
| 86 | 119 | |
| 87 | 120 | const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu) orelse { |
| 88 | 121 | const file = zcu.fileByIndex(block.src_base_inst.resolveFile(&zcu.intern_pool)); |
| 89 | try stderr.print("Analyzing lost instruction in file '{f}'. This should not happen!\n\n", .{file.path.fmt(zcu.comp)}); | |
| 122 | try stderr.print("Analyzing lost instruction in file '{f}'. This should not happen!\n\n", .{file.path.fmt(comp)}); | |
| 90 | 123 | return; |
| 91 | 124 | }; |
| 92 | 125 | |
| 93 | try stderr.writeAll("Analyzing "); | |
| 94 | try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(zcu.comp)}); | |
| 126 | try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(comp)}); | |
| 95 | 127 | |
| 96 | 128 | print_zir.renderInstructionContext( |
| 97 | allocator, | |
| 129 | fba.allocator(), | |
| 98 | 130 | anal.body, |
| 99 | 131 | anal.body_index, |
| 100 | 132 | file, |
| ... | ... | @@ -107,16 +139,16 @@ fn dumpStatusReport() !void { |
| 107 | 139 | }; |
| 108 | 140 | try stderr.print( |
| 109 | 141 | \\ For full context, use the command |
| 110 | \\ zig ast-check -t {f} | |
| 142 | \\ {s} ast-check -t {f} | |
| 111 | 143 | \\ |
| 112 | 144 | \\ |
| 113 | , .{file.path.fmt(zcu.comp)}); | |
| 145 | , .{ zig_argv0, file.path.fmt(comp) }); | |
| 114 | 146 | |
| 115 | 147 | var parent = anal.parent; |
| 116 | 148 | while (parent) |curr| { |
| 117 | 149 | fba.reset(); |
| 118 | 150 | const cur_block_file = zcu.fileByIndex(curr.block.src_base_inst.resolveFile(&zcu.intern_pool)); |
| 119 | try stderr.print(" in {f}\n", .{cur_block_file.path.fmt(zcu.comp)}); | |
| 151 | try stderr.print(" in {f}\n", .{cur_block_file.path.fmt(comp)}); | |
| 120 | 152 | _, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu) orelse { |
| 121 | 153 | try stderr.writeAll(" > [lost instruction; this should not happen]\n"); |
| 122 | 154 | parent = curr.parent; |
| ... | ... | @@ -124,7 +156,7 @@ fn dumpStatusReport() !void { |
| 124 | 156 | }; |
| 125 | 157 | try stderr.writeAll(" > "); |
| 126 | 158 | print_zir.renderSingleInstruction( |
| 127 | allocator, | |
| 159 | fba.allocator(), | |
| 128 | 160 | curr.body[curr.body_index], |
| 129 | 161 | cur_block_file, |
| 130 | 162 | cur_block_src_base_node, |
| ... | ... | @@ -142,398 +174,14 @@ fn dumpStatusReport() !void { |
| 142 | 174 | try stderr.writeByte('\n'); |
| 143 | 175 | } |
| 144 | 176 | |
| 145 | var crash_heap: [16 * 4096]u8 = undefined; | |
| 146 | ||
| 147 | pub fn compilerPanic(msg: []const u8, maybe_ret_addr: ?usize) noreturn { | |
| 148 | @branchHint(.cold); | |
| 149 | PanicSwitch.preDispatch(); | |
| 150 | const ret_addr = maybe_ret_addr orelse @returnAddress(); | |
| 151 | const stack_ctx: StackContext = .{ .current = .{ .ret_addr = ret_addr } }; | |
| 152 | PanicSwitch.dispatch(@errorReturnTrace(), stack_ctx, msg); | |
| 153 | } | |
| 154 | ||
| 155 | /// Attaches a global SIGSEGV handler | |
| 156 | pub fn attachSegfaultHandler() void { | |
| 157 | if (!debug.have_segfault_handling_support) { | |
| 158 | @compileError("segfault handler not supported for this target"); | |
| 159 | } | |
| 160 | if (native_os == .windows) { | |
| 161 | _ = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); | |
| 162 | return; | |
| 163 | } | |
| 164 | const act: posix.Sigaction = .{ | |
| 165 | .handler = .{ .sigaction = handleSegfaultPosix }, | |
| 166 | .mask = posix.sigemptyset(), | |
| 167 | .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND), | |
| 168 | }; | |
| 169 | debug.updateSegfaultHandler(&act); | |
| 170 | } | |
| 171 | ||
| 172 | fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn { | |
| 173 | // TODO: use alarm() here to prevent infinite loops | |
| 174 | PanicSwitch.preDispatch(); | |
| 175 | ||
| 176 | const addr = switch (native_os) { | |
| 177 | .linux => @intFromPtr(info.fields.sigfault.addr), | |
| 178 | .freebsd, .macos => @intFromPtr(info.addr), | |
| 179 | .netbsd => @intFromPtr(info.info.reason.fault.addr), | |
| 180 | .openbsd => @intFromPtr(info.data.fault.addr), | |
| 181 | .solaris, .illumos => @intFromPtr(info.reason.fault.addr), | |
| 182 | else => @compileError("TODO implement handleSegfaultPosix for new POSIX OS"), | |
| 183 | }; | |
| 184 | ||
| 185 | var err_buffer: [128]u8 = undefined; | |
| 186 | const error_msg = switch (sig) { | |
| 187 | posix.SIG.SEGV => std.fmt.bufPrint(&err_buffer, "Segmentation fault at address 0x{x}", .{addr}) catch "Segmentation fault", | |
| 188 | posix.SIG.ILL => std.fmt.bufPrint(&err_buffer, "Illegal instruction at address 0x{x}", .{addr}) catch "Illegal instruction", | |
| 189 | posix.SIG.BUS => std.fmt.bufPrint(&err_buffer, "Bus error at address 0x{x}", .{addr}) catch "Bus error", | |
| 190 | else => std.fmt.bufPrint(&err_buffer, "Unknown error (signal {}) at address 0x{x}", .{ sig, addr }) catch "Unknown error", | |
| 191 | }; | |
| 192 | ||
| 193 | const stack_ctx: StackContext = switch (builtin.cpu.arch) { | |
| 194 | .x86, | |
| 195 | .x86_64, | |
| 196 | .arm, | |
| 197 | .aarch64, | |
| 198 | => StackContext{ .exception = @ptrCast(@alignCast(ctx_ptr)) }, | |
| 199 | else => .not_supported, | |
| 200 | }; | |
| 201 | ||
| 202 | PanicSwitch.dispatch(null, stack_ctx, error_msg); | |
| 203 | } | |
| 204 | ||
| 205 | const WindowsSegfaultMessage = union(enum) { | |
| 206 | literal: []const u8, | |
| 207 | segfault: void, | |
| 208 | illegal_instruction: void, | |
| 209 | }; | |
| 210 | ||
| 211 | fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long { | |
| 212 | switch (info.ExceptionRecord.ExceptionCode) { | |
| 213 | windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, .{ .literal = "Unaligned Memory Access" }), | |
| 214 | windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, .segfault), | |
| 215 | windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, .illegal_instruction), | |
| 216 | windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, .{ .literal = "Stack Overflow" }), | |
| 217 | else => return windows.EXCEPTION_CONTINUE_SEARCH, | |
| 218 | } | |
| 219 | } | |
| 220 | ||
| 221 | fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn { | |
| 222 | PanicSwitch.preDispatch(); | |
| 223 | ||
| 224 | const stack_ctx = if (@hasDecl(windows, "CONTEXT")) | |
| 225 | StackContext{ .exception = info.ContextRecord } | |
| 226 | else ctx: { | |
| 227 | const addr = @intFromPtr(info.ExceptionRecord.ExceptionAddress); | |
| 228 | break :ctx StackContext{ .current = .{ .ret_addr = addr } }; | |
| 229 | }; | |
| 230 | ||
| 231 | switch (msg) { | |
| 232 | .literal => |err| PanicSwitch.dispatch(null, stack_ctx, err), | |
| 233 | .segfault => { | |
| 234 | const format_item = "Segmentation fault at address 0x{x}"; | |
| 235 | var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large | |
| 236 | const to_print = std.fmt.bufPrint(&buf, format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable; | |
| 237 | PanicSwitch.dispatch(null, stack_ctx, to_print); | |
| 238 | }, | |
| 239 | .illegal_instruction => { | |
| 240 | const ip: ?usize = switch (stack_ctx) { | |
| 241 | .exception => |ex| ex.getRegs().ip, | |
| 242 | .current => |cur| cur.ret_addr, | |
| 243 | .not_supported => null, | |
| 244 | }; | |
| 245 | ||
| 246 | if (ip) |addr| { | |
| 247 | const format_item = "Illegal instruction at address 0x{x}"; | |
| 248 | var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large | |
| 249 | const to_print = std.fmt.bufPrint(&buf, format_item, .{addr}) catch unreachable; | |
| 250 | PanicSwitch.dispatch(null, stack_ctx, to_print); | |
| 251 | } else { | |
| 252 | PanicSwitch.dispatch(null, stack_ctx, "Illegal Instruction"); | |
| 253 | } | |
| 254 | }, | |
| 255 | } | |
| 256 | } | |
| 257 | ||
| 258 | const StackContext = union(enum) { | |
| 259 | current: struct { | |
| 260 | ret_addr: ?usize, | |
| 261 | }, | |
| 262 | exception: *debug.ThreadContext, | |
| 263 | not_supported: void, | |
| 264 | ||
| 265 | pub fn dumpStackTrace(ctx: @This()) void { | |
| 266 | switch (ctx) { | |
| 267 | .current => |ct| { | |
| 268 | debug.dumpCurrentStackTrace(ct.ret_addr); | |
| 269 | }, | |
| 270 | .exception => |context| { | |
| 271 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | |
| 272 | const stderr = &stderr_fw.interface; | |
| 273 | debug.dumpStackTraceFromBase(context, stderr); | |
| 274 | }, | |
| 275 | .not_supported => { | |
| 276 | std.fs.File.stderr().writeAll("Stack trace not supported on this platform.\n") catch {}; | |
| 277 | }, | |
| 278 | } | |
| 279 | } | |
| 280 | }; | |
| 281 | ||
| 282 | const PanicSwitch = struct { | |
| 283 | const RecoverStage = enum { | |
| 284 | initialize, | |
| 285 | report_stack, | |
| 286 | release_mutex, | |
| 287 | release_ref_count, | |
| 288 | abort, | |
| 289 | silent_abort, | |
| 290 | }; | |
| 291 | ||
| 292 | const RecoverVerbosity = enum { | |
| 293 | message_and_stack, | |
| 294 | message_only, | |
| 295 | silent, | |
| 296 | }; | |
| 297 | ||
| 298 | const PanicState = struct { | |
| 299 | recover_stage: RecoverStage = .initialize, | |
| 300 | recover_verbosity: RecoverVerbosity = .message_and_stack, | |
| 301 | panic_ctx: StackContext = undefined, | |
| 302 | panic_trace: ?*const std.builtin.StackTrace = null, | |
| 303 | awaiting_dispatch: bool = false, | |
| 304 | }; | |
| 305 | ||
| 306 | /// Counter for the number of threads currently panicking. | |
| 307 | /// Updated atomically before taking the panic_mutex. | |
| 308 | /// In recoverable cases, the program will not abort | |
| 309 | /// until all panicking threads have dumped their traces. | |
| 310 | var panicking = std.atomic.Value(u8).init(0); | |
| 311 | ||
| 312 | /// Tracks the state of the current panic. If the code within the | |
| 313 | /// panic triggers a secondary panic, this allows us to recover. | |
| 314 | threadlocal var panic_state_raw: PanicState = .{}; | |
| 315 | ||
| 316 | /// The segfault handlers above need to do some work before they can dispatch | |
| 317 | /// this switch. Calling preDispatch() first makes that work fault tolerant. | |
| 318 | pub fn preDispatch() void { | |
| 319 | // TODO: We want segfaults to trigger the panic recursively here, | |
| 320 | // but if there is a segfault accessing this TLS slot it will cause an | |
| 321 | // infinite loop. We should use `alarm()` to prevent the infinite | |
| 322 | // loop and maybe also use a non-thread-local global to detect if | |
| 323 | // it's happening and print a message. | |
| 324 | var panic_state: *volatile PanicState = &panic_state_raw; | |
| 325 | if (panic_state.awaiting_dispatch) { | |
| 326 | dispatch(null, .{ .current = .{ .ret_addr = null } }, "Panic while preparing callstack"); | |
| 327 | } | |
| 328 | panic_state.awaiting_dispatch = true; | |
| 329 | } | |
| 330 | ||
| 331 | /// This is the entry point to a panic-tolerant panic handler. | |
| 332 | /// preDispatch() *MUST* be called exactly once before calling this. | |
| 333 | /// A threadlocal "recover_stage" is updated throughout the process. | |
| 334 | /// If a panic happens during the panic, the recover_stage will be | |
| 335 | /// used to select a recover* function to call to resume the panic. | |
| 336 | /// The recover_verbosity field is used to handle panics while reporting | |
| 337 | /// panics within panics. If the panic handler triggers a panic, it will | |
| 338 | /// attempt to log an additional stack trace for the secondary panic. If | |
| 339 | /// that panics, it will fall back to just logging the panic message. If | |
| 340 | /// it can't even do that witout panicing, it will recover without logging | |
| 341 | /// anything about the internal panic. Depending on the state, "recover" | |
| 342 | /// here may just mean "call abort". | |
| 343 | pub fn dispatch( | |
| 344 | trace: ?*const std.builtin.StackTrace, | |
| 345 | stack_ctx: StackContext, | |
| 346 | msg: []const u8, | |
| 347 | ) noreturn { | |
| 348 | var panic_state: *volatile PanicState = &panic_state_raw; | |
| 349 | debug.assert(panic_state.awaiting_dispatch); | |
| 350 | panic_state.awaiting_dispatch = false; | |
| 351 | nosuspend switch (panic_state.recover_stage) { | |
| 352 | .initialize => goTo(initPanic, .{ panic_state, trace, stack_ctx, msg }), | |
| 353 | .report_stack => goTo(recoverReportStack, .{ panic_state, trace, stack_ctx, msg }), | |
| 354 | .release_mutex => goTo(recoverReleaseMutex, .{ panic_state, trace, stack_ctx, msg }), | |
| 355 | .release_ref_count => goTo(recoverReleaseRefCount, .{ panic_state, trace, stack_ctx, msg }), | |
| 356 | .abort => goTo(recoverAbort, .{ panic_state, trace, stack_ctx, msg }), | |
| 357 | .silent_abort => goTo(abort, .{}), | |
| 358 | }; | |
| 359 | } | |
| 360 | ||
| 361 | noinline fn initPanic( | |
| 362 | state: *volatile PanicState, | |
| 363 | trace: ?*const std.builtin.StackTrace, | |
| 364 | stack: StackContext, | |
| 365 | msg: []const u8, | |
| 366 | ) noreturn { | |
| 367 | // use a temporary so there's only one volatile store | |
| 368 | const new_state = PanicState{ | |
| 369 | .recover_stage = .abort, | |
| 370 | .panic_ctx = stack, | |
| 371 | .panic_trace = trace, | |
| 372 | }; | |
| 373 | state.* = new_state; | |
| 374 | ||
| 375 | _ = panicking.fetchAdd(1, .seq_cst); | |
| 376 | ||
| 377 | state.recover_stage = .release_ref_count; | |
| 378 | ||
| 379 | std.debug.lockStdErr(); | |
| 380 | ||
| 381 | state.recover_stage = .release_mutex; | |
| 382 | ||
| 383 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | |
| 384 | const stderr = &stderr_fw.interface; | |
| 385 | if (builtin.single_threaded) { | |
| 386 | stderr.print("panic: ", .{}) catch goTo(releaseMutex, .{state}); | |
| 387 | } else { | |
| 388 | const current_thread_id = std.Thread.getCurrentId(); | |
| 389 | stderr.print("thread {} panic: ", .{current_thread_id}) catch goTo(releaseMutex, .{state}); | |
| 390 | } | |
| 391 | stderr.print("{s}\n", .{msg}) catch goTo(releaseMutex, .{state}); | |
| 392 | ||
| 393 | state.recover_stage = .report_stack; | |
| 394 | ||
| 395 | dumpStatusReport() catch |err| { | |
| 396 | stderr.print("\nIntercepted error.{} while dumping current state. Continuing...\n", .{err}) catch {}; | |
| 397 | }; | |
| 398 | ||
| 399 | goTo(reportStack, .{state}); | |
| 400 | } | |
| 401 | ||
| 402 | noinline fn recoverReportStack( | |
| 403 | state: *volatile PanicState, | |
| 404 | trace: ?*const std.builtin.StackTrace, | |
| 405 | stack: StackContext, | |
| 406 | msg: []const u8, | |
| 407 | ) noreturn { | |
| 408 | recover(state, trace, stack, msg); | |
| 409 | ||
| 410 | state.recover_stage = .release_mutex; | |
| 411 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | |
| 412 | const stderr = &stderr_fw.interface; | |
| 413 | stderr.writeAll("\nOriginal Error:\n") catch {}; | |
| 414 | goTo(reportStack, .{state}); | |
| 415 | } | |
| 416 | ||
| 417 | noinline fn reportStack(state: *volatile PanicState) noreturn { | |
| 418 | state.recover_stage = .release_mutex; | |
| 419 | ||
| 420 | if (state.panic_trace) |t| { | |
| 421 | debug.dumpStackTrace(t.*); | |
| 422 | } | |
| 423 | state.panic_ctx.dumpStackTrace(); | |
| 424 | ||
| 425 | goTo(releaseMutex, .{state}); | |
| 426 | } | |
| 427 | ||
| 428 | noinline fn recoverReleaseMutex( | |
| 429 | state: *volatile PanicState, | |
| 430 | trace: ?*const std.builtin.StackTrace, | |
| 431 | stack: StackContext, | |
| 432 | msg: []const u8, | |
| 433 | ) noreturn { | |
| 434 | recover(state, trace, stack, msg); | |
| 435 | goTo(releaseMutex, .{state}); | |
| 436 | } | |
| 437 | ||
| 438 | noinline fn releaseMutex(state: *volatile PanicState) noreturn { | |
| 439 | state.recover_stage = .abort; | |
| 440 | ||
| 441 | std.debug.unlockStdErr(); | |
| 442 | ||
| 443 | goTo(releaseRefCount, .{state}); | |
| 444 | } | |
| 445 | ||
| 446 | noinline fn recoverReleaseRefCount( | |
| 447 | state: *volatile PanicState, | |
| 448 | trace: ?*const std.builtin.StackTrace, | |
| 449 | stack: StackContext, | |
| 450 | msg: []const u8, | |
| 451 | ) noreturn { | |
| 452 | recover(state, trace, stack, msg); | |
| 453 | goTo(releaseRefCount, .{state}); | |
| 454 | } | |
| 455 | ||
| 456 | noinline fn releaseRefCount(state: *volatile PanicState) noreturn { | |
| 457 | state.recover_stage = .abort; | |
| 458 | ||
| 459 | if (panicking.fetchSub(1, .seq_cst) != 1) { | |
| 460 | // Another thread is panicking, wait for the last one to finish | |
| 461 | // and call abort() | |
| 462 | ||
| 463 | // Sleep forever without hammering the CPU | |
| 464 | var futex = std.atomic.Value(u32).init(0); | |
| 465 | while (true) std.Thread.Futex.wait(&futex, 0); | |
| 466 | ||
| 467 | // This should be unreachable, recurse into recoverAbort. | |
| 468 | @panic("event.wait() returned"); | |
| 469 | } | |
| 470 | ||
| 471 | goTo(abort, .{}); | |
| 472 | } | |
| 473 | ||
| 474 | noinline fn recoverAbort( | |
| 475 | state: *volatile PanicState, | |
| 476 | trace: ?*const std.builtin.StackTrace, | |
| 477 | stack: StackContext, | |
| 478 | msg: []const u8, | |
| 479 | ) noreturn { | |
| 480 | recover(state, trace, stack, msg); | |
| 481 | ||
| 482 | state.recover_stage = .silent_abort; | |
| 483 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | |
| 484 | const stderr = &stderr_fw.interface; | |
| 485 | stderr.writeAll("Aborting...\n") catch {}; | |
| 486 | goTo(abort, .{}); | |
| 487 | } | |
| 488 | ||
| 489 | noinline fn abort() noreturn { | |
| 490 | std.process.abort(); | |
| 491 | } | |
| 492 | ||
| 493 | inline fn goTo(comptime func: anytype, args: anytype) noreturn { | |
| 494 | // TODO: Tailcall is broken right now, but eventually this should be used | |
| 495 | // to avoid blowing up the stack. It's ok for now though, there are no | |
| 496 | // cycles in the state machine so the max stack usage is bounded. | |
| 497 | //@call(.always_tail, func, args); | |
| 498 | @call(.auto, func, args); | |
| 499 | } | |
| 500 | ||
| 501 | fn recover( | |
| 502 | state: *volatile PanicState, | |
| 503 | trace: ?*const std.builtin.StackTrace, | |
| 504 | stack: StackContext, | |
| 505 | msg: []const u8, | |
| 506 | ) void { | |
| 507 | switch (state.recover_verbosity) { | |
| 508 | .message_and_stack => { | |
| 509 | // lower the verbosity, and restore it at the end if we don't panic. | |
| 510 | state.recover_verbosity = .message_only; | |
| 511 | ||
| 512 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | |
| 513 | const stderr = &stderr_fw.interface; | |
| 514 | stderr.writeAll("\nPanicked during a panic: ") catch {}; | |
| 515 | stderr.writeAll(msg) catch {}; | |
| 516 | stderr.writeAll("\nInner panic stack:\n") catch {}; | |
| 517 | if (trace) |t| { | |
| 518 | debug.dumpStackTrace(t.*); | |
| 519 | } | |
| 520 | stack.dumpStackTrace(); | |
| 521 | ||
| 522 | state.recover_verbosity = .message_and_stack; | |
| 523 | }, | |
| 524 | .message_only => { | |
| 525 | state.recover_verbosity = .silent; | |
| 177 | const std = @import("std"); | |
| 178 | const Io = std.Io; | |
| 179 | const Zir = std.zig.Zir; | |
| 526 | 180 | |
| 527 | var stderr_fw = std.fs.File.stderr().writer(&.{}); | |
| 528 | const stderr = &stderr_fw.interface; | |
| 529 | stderr.writeAll("\nPanicked while dumping inner panic stack: ") catch {}; | |
| 530 | stderr.writeAll(msg) catch {}; | |
| 531 | stderr.writeByte('\n') catch {}; | |
| 181 | const Sema = @import("Sema.zig"); | |
| 182 | const Zcu = @import("Zcu.zig"); | |
| 183 | const InternPool = @import("InternPool.zig"); | |
| 184 | const dev = @import("dev.zig"); | |
| 185 | const print_zir = @import("print_zir.zig"); | |
| 532 | 186 | |
| 533 | // If we succeed, restore all the way to dumping the stack. | |
| 534 | state.recover_verbosity = .message_and_stack; | |
| 535 | }, | |
| 536 | .silent => {}, | |
| 537 | } | |
| 538 | } | |
| 539 | }; | |
| 187 | const build_options = @import("build_options"); |
src/link/Dwarf.zig+2-2| ... | ... | @@ -4064,6 +4064,7 @@ fn updateLazyValue( |
| 4064 | 4064 | }, |
| 4065 | 4065 | .error_union => |error_union| { |
| 4066 | 4066 | try wip_nav.abbrevCode(.aggregate_comptime_value); |
| 4067 | try wip_nav.refType(.fromInterned(error_union.ty)); | |
| 4067 | 4068 | var err_buf: [4]u8 = undefined; |
| 4068 | 4069 | const err_bytes = err_buf[0 .. std.math.divCeil(u17, zcu.errorSetBits(), 8) catch unreachable]; |
| 4069 | 4070 | dwarf.writeInt(err_bytes, switch (error_union.val) { |
| ... | ... | @@ -4101,7 +4102,6 @@ fn updateLazyValue( |
| 4101 | 4102 | try diw.writeUleb128(err_bytes.len); |
| 4102 | 4103 | try diw.writeAll(err_bytes); |
| 4103 | 4104 | } |
| 4104 | try wip_nav.refType(.fromInterned(error_union.ty)); | |
| 4105 | 4105 | try diw.writeUleb128(@intFromEnum(AbbrevCode.null)); |
| 4106 | 4106 | }, |
| 4107 | 4107 | .enum_literal => |enum_literal| { |
| ... | ... | @@ -4852,7 +4852,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Erro |
| 4852 | 4852 | hw.writeSleb128(dwarf.debug_frame.header.data_alignment_factor) catch unreachable; |
| 4853 | 4853 | hw.writeUleb128(dwarf.debug_frame.header.return_address_register) catch unreachable; |
| 4854 | 4854 | hw.writeUleb128(1) catch unreachable; |
| 4855 | hw.writeByte(DW.EH.PE.pcrel | DW.EH.PE.sdata4) catch unreachable; | |
| 4855 | hw.writeByte(@bitCast(@as(DW.EH.PE, .{ .type = .sdata4, .rel = .pcrel }))) catch unreachable; | |
| 4856 | 4856 | hw.writeByte(DW.CFA.def_cfa_sf) catch unreachable; |
| 4857 | 4857 | hw.writeUleb128(Register.rsp.dwarfNum()) catch unreachable; |
| 4858 | 4858 | hw.writeSleb128(-1) catch unreachable; |
src/link/Elf.zig+22-17| ... | ... | @@ -1884,6 +1884,16 @@ fn initSyntheticSections(self: *Elf) !void { |
| 1884 | 1884 | const ptr_size = self.ptrWidthBytes(); |
| 1885 | 1885 | const shared_objects = self.shared_objects.values(); |
| 1886 | 1886 | |
| 1887 | const is_exe_or_dyn_lib = switch (comp.config.output_mode) { | |
| 1888 | .Exe => true, | |
| 1889 | .Lib => comp.config.link_mode == .dynamic, | |
| 1890 | .Obj => false, | |
| 1891 | }; | |
| 1892 | const have_dynamic_linker = comp.config.link_mode == .dynamic and is_exe_or_dyn_lib and !target.dynamic_linker.eql(.none); | |
| 1893 | ||
| 1894 | const needs_interp = have_dynamic_linker and | |
| 1895 | (comp.config.link_libc or comp.root_mod.resolved_target.is_explicit_dynamic_linker); | |
| 1896 | ||
| 1887 | 1897 | const needs_eh_frame = blk: { |
| 1888 | 1898 | if (self.zigObjectPtr()) |zo| |
| 1889 | 1899 | if (zo.eh_frame_index != null) break :blk true; |
| ... | ... | @@ -1891,6 +1901,7 @@ fn initSyntheticSections(self: *Elf) !void { |
| 1891 | 1901 | if (self.file(index).?.object.cies.items.len > 0) break true; |
| 1892 | 1902 | } else false; |
| 1893 | 1903 | }; |
| 1904 | ||
| 1894 | 1905 | if (needs_eh_frame) { |
| 1895 | 1906 | if (self.section_indexes.eh_frame == null) { |
| 1896 | 1907 | self.section_indexes.eh_frame = self.sectionByName(".eh_frame") orelse try self.addSection(.{ |
| ... | ... | @@ -1922,13 +1933,17 @@ fn initSyntheticSections(self: *Elf) !void { |
| 1922 | 1933 | }); |
| 1923 | 1934 | } |
| 1924 | 1935 | |
| 1925 | if (self.section_indexes.got_plt == null) { | |
| 1926 | self.section_indexes.got_plt = try self.addSection(.{ | |
| 1927 | .name = try self.insertShString(".got.plt"), | |
| 1928 | .type = elf.SHT_PROGBITS, | |
| 1929 | .flags = elf.SHF_ALLOC | elf.SHF_WRITE, | |
| 1930 | .addralign = @alignOf(u64), | |
| 1931 | }); | |
| 1936 | if (have_dynamic_linker) { | |
| 1937 | if (self.section_indexes.got_plt == null) { | |
| 1938 | self.section_indexes.got_plt = try self.addSection(.{ | |
| 1939 | .name = try self.insertShString(".got.plt"), | |
| 1940 | .type = elf.SHT_PROGBITS, | |
| 1941 | .flags = elf.SHF_ALLOC | elf.SHF_WRITE, | |
| 1942 | .addralign = @alignOf(u64), | |
| 1943 | }); | |
| 1944 | } | |
| 1945 | } else { | |
| 1946 | assert(self.plt.symbols.items.len == 0); | |
| 1932 | 1947 | } |
| 1933 | 1948 | |
| 1934 | 1949 | const needs_rela_dyn = blk: { |
| ... | ... | @@ -1989,16 +2004,6 @@ fn initSyntheticSections(self: *Elf) !void { |
| 1989 | 2004 | }); |
| 1990 | 2005 | } |
| 1991 | 2006 | |
| 1992 | const is_exe_or_dyn_lib = switch (comp.config.output_mode) { | |
| 1993 | .Exe => true, | |
| 1994 | .Lib => comp.config.link_mode == .dynamic, | |
| 1995 | .Obj => false, | |
| 1996 | }; | |
| 1997 | const have_dynamic_linker = comp.config.link_mode == .dynamic and is_exe_or_dyn_lib and !target.dynamic_linker.eql(.none); | |
| 1998 | ||
| 1999 | const needs_interp = have_dynamic_linker and | |
| 2000 | (comp.config.link_libc or comp.root_mod.resolved_target.is_explicit_dynamic_linker); | |
| 2001 | ||
| 2002 | 2007 | if (needs_interp and self.section_indexes.interp == null) { |
| 2003 | 2008 | self.section_indexes.interp = try self.addSection(.{ |
| 2004 | 2009 | .name = try self.insertShString(".interp"), |
src/link/Elf/eh_frame.zig+51-41| ... | ... | @@ -234,7 +234,14 @@ pub fn calcEhFrameSize(elf_file: *Elf) !usize { |
| 234 | 234 | return offset; |
| 235 | 235 | } |
| 236 | 236 | |
| 237 | fn haveEhFrameHdrSearchTable(elf_file: *Elf) bool { | |
| 238 | // Seach table generation is not implemented for the ZigObject. Also, it would be wasteful to | |
| 239 | // re-do this work on every single incremental update. | |
| 240 | return elf_file.zigObjectPtr() == null; | |
| 241 | } | |
| 242 | ||
| 237 | 243 | pub fn calcEhFrameHdrSize(elf_file: *Elf) usize { |
| 244 | if (!haveEhFrameHdrSearchTable(elf_file)) return 8; | |
| 238 | 245 | var count: usize = 0; |
| 239 | 246 | for (elf_file.objects.items) |index| { |
| 240 | 247 | for (elf_file.file(index).?.object.fdes.items) |fde| { |
| ... | ... | @@ -242,7 +249,7 @@ pub fn calcEhFrameHdrSize(elf_file: *Elf) usize { |
| 242 | 249 | count += 1; |
| 243 | 250 | } |
| 244 | 251 | } |
| 245 | return eh_frame_hdr_header_size + count * 8; | |
| 252 | return 12 + count * 8; | |
| 246 | 253 | } |
| 247 | 254 | |
| 248 | 255 | pub fn calcEhFrameRelocs(elf_file: *Elf) usize { |
| ... | ... | @@ -455,76 +462,77 @@ pub fn writeEhFrameRelocs(elf_file: *Elf, relocs: *std.array_list.Managed(elf.El |
| 455 | 462 | } |
| 456 | 463 | |
| 457 | 464 | pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void { |
| 458 | const comp = elf_file.base.comp; | |
| 459 | const gpa = comp.gpa; | |
| 465 | const endian = elf_file.getTarget().cpu.arch.endian(); | |
| 466 | const have_table = haveEhFrameHdrSearchTable(elf_file); | |
| 460 | 467 | |
| 461 | 468 | try writer.writeByte(1); // version |
| 462 | try writer.writeByte(DW_EH_PE.pcrel | DW_EH_PE.sdata4); | |
| 463 | try writer.writeByte(DW_EH_PE.udata4); | |
| 464 | try writer.writeByte(DW_EH_PE.datarel | DW_EH_PE.sdata4); | |
| 469 | try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .sdata4, .rel = .pcrel }))); // eh_frame_ptr_enc | |
| 470 | if (have_table) { | |
| 471 | try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .udata4, .rel = .abs }))); // fde_count_enc | |
| 472 | try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .sdata4, .rel = .datarel }))); // table_enc | |
| 473 | } else { | |
| 474 | try writer.writeByte(@bitCast(DW_EH_PE.omit)); // fde_count_enc | |
| 475 | try writer.writeByte(@bitCast(DW_EH_PE.omit)); // table_enc | |
| 476 | } | |
| 465 | 477 | |
| 466 | 478 | const shdrs = elf_file.sections.items(.shdr); |
| 467 | 479 | const eh_frame_shdr = shdrs[elf_file.section_indexes.eh_frame.?]; |
| 468 | 480 | const eh_frame_hdr_shdr = shdrs[elf_file.section_indexes.eh_frame_hdr.?]; |
| 469 | const num_fdes = @as(u32, @intCast(@divExact(eh_frame_hdr_shdr.sh_size - eh_frame_hdr_header_size, 8))); | |
| 470 | const existing_size = existing_size: { | |
| 471 | const zo = elf_file.zigObjectPtr() orelse break :existing_size 0; | |
| 472 | const sym = zo.symbol(zo.eh_frame_index orelse break :existing_size 0); | |
| 473 | break :existing_size sym.atom(elf_file).?.size; | |
| 474 | }; | |
| 481 | // eh_frame_ptr | |
| 475 | 482 | try writer.writeInt( |
| 476 | 483 | u32, |
| 477 | 484 | @as(u32, @bitCast(@as( |
| 478 | 485 | i32, |
| 479 | @truncate(@as(i64, @intCast(eh_frame_shdr.sh_addr + existing_size)) - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)) - 4), | |
| 486 | @truncate(@as(i64, @intCast(eh_frame_shdr.sh_addr)) - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)) - 4), | |
| 480 | 487 | ))), |
| 481 | 488 | .little, |
| 482 | 489 | ); |
| 483 | try writer.writeInt(u32, num_fdes, .little); | |
| 484 | 490 | |
| 485 | const Entry = extern struct { | |
| 486 | init_addr: u32, | |
| 487 | fde_addr: u32, | |
| 491 | if (!have_table) return; | |
| 488 | 492 | |
| 489 | pub fn lessThan(ctx: void, lhs: @This(), rhs: @This()) bool { | |
| 490 | _ = ctx; | |
| 491 | return lhs.init_addr < rhs.init_addr; | |
| 493 | const gpa = elf_file.base.comp.gpa; | |
| 494 | ||
| 495 | // This must be an `extern struct` because we will write the bytes directly to the file. | |
| 496 | const Entry = extern struct { | |
| 497 | first_pc_rel: i32, | |
| 498 | fde_addr_rel: i32, | |
| 499 | fn lessThan(_: void, lhs: @This(), rhs: @This()) bool { | |
| 500 | return lhs.first_pc_rel < rhs.first_pc_rel; | |
| 492 | 501 | } |
| 493 | 502 | }; |
| 494 | ||
| 495 | var entries = std.array_list.Managed(Entry).init(gpa); | |
| 496 | defer entries.deinit(); | |
| 497 | try entries.ensureTotalCapacityPrecise(num_fdes); | |
| 498 | ||
| 499 | for (elf_file.objects.items) |index| { | |
| 500 | const object = elf_file.file(index).?.object; | |
| 503 | // The number of entries was already computed by `calcEhFrameHdrSize`. | |
| 504 | const num_fdes: u32 = @intCast(@divExact(eh_frame_hdr_shdr.sh_size - 12, 8)); | |
| 505 | try writer.writeInt(u32, num_fdes, endian); | |
| 506 | ||
| 507 | var entries: std.ArrayList(Entry) = try .initCapacity(gpa, num_fdes); | |
| 508 | defer entries.deinit(gpa); | |
| 509 | for (elf_file.objects.items) |file_index| { | |
| 510 | const object = elf_file.file(file_index).?.object; | |
| 501 | 511 | for (object.fdes.items) |fde| { |
| 502 | 512 | if (!fde.alive) continue; |
| 503 | ||
| 504 | 513 | const relocs = fde.relocs(object); |
| 505 | assert(relocs.len > 0); // Should this be an error? Things are completely broken anyhow if this trips... | |
| 514 | // Should `relocs.len == 0` be an error? Things are completely broken anyhow in that case... | |
| 506 | 515 | const rel = relocs[0]; |
| 507 | 516 | const ref = object.resolveSymbol(rel.r_sym(), elf_file); |
| 508 | 517 | const sym = elf_file.symbol(ref).?; |
| 509 | const P = @as(i64, @intCast(fde.address(elf_file))); | |
| 510 | const S = @as(i64, @intCast(sym.address(.{}, elf_file))); | |
| 511 | const A = rel.r_addend; | |
| 518 | const fde_addr_abs: i64 = @intCast(fde.address(elf_file)); | |
| 519 | const fde_addr_rel: i64 = fde_addr_abs - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)); | |
| 520 | const first_pc_abs: i64 = @as(i64, @intCast(sym.address(.{}, elf_file))) + rel.r_addend; | |
| 521 | const first_pc_rel: i64 = first_pc_abs - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)); | |
| 512 | 522 | entries.appendAssumeCapacity(.{ |
| 513 | .init_addr = @bitCast(@as(i32, @truncate(S + A - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))), | |
| 514 | .fde_addr = @as( | |
| 515 | u32, | |
| 516 | @bitCast(@as(i32, @truncate(P - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))), | |
| 517 | ), | |
| 523 | .first_pc_rel = @truncate(first_pc_rel), | |
| 524 | .fde_addr_rel = @truncate(fde_addr_rel), | |
| 518 | 525 | }); |
| 519 | 526 | } |
| 520 | 527 | } |
| 521 | ||
| 528 | assert(entries.items.len == num_fdes); | |
| 522 | 529 | std.mem.sort(Entry, entries.items, {}, Entry.lessThan); |
| 523 | try writer.writeSliceEndian(Entry, entries.items, .little); | |
| 530 | if (endian != builtin.cpu.arch.endian()) { | |
| 531 | std.mem.byteSwapAllElements(Entry, entries.items); | |
| 532 | } | |
| 533 | try writer.writeAll(@ptrCast(entries.items)); | |
| 524 | 534 | } |
| 525 | 535 | |
| 526 | const eh_frame_hdr_header_size: usize = 12; | |
| 527 | ||
| 528 | 536 | const x86_64 = struct { |
| 529 | 537 | fn resolveReloc(rec: anytype, elf_file: *Elf, rel: elf.Elf64_Rela, source: i64, target: i64, data: []u8) !void { |
| 530 | 538 | const r_type: elf.R_X86_64 = @enumFromInt(rel.r_type()); |
| ... | ... | @@ -587,3 +595,5 @@ const DW_EH_PE = std.dwarf.EH.PE; |
| 587 | 595 | const Elf = @import("../Elf.zig"); |
| 588 | 596 | const Object = @import("Object.zig"); |
| 589 | 597 | const Symbol = @import("Symbol.zig"); |
| 598 | ||
| 599 | const builtin = @import("builtin"); |
src/link/MachO.zig+3-3| ... | ... | @@ -4149,9 +4149,9 @@ pub const SymtabCtx = struct { |
| 4149 | 4149 | |
| 4150 | 4150 | pub const null_sym = macho.nlist_64{ |
| 4151 | 4151 | .n_strx = 0, |
| 4152 | .n_type = 0, | |
| 4152 | .n_type = @bitCast(@as(u8, 0)), | |
| 4153 | 4153 | .n_sect = 0, |
| 4154 | .n_desc = 0, | |
| 4154 | .n_desc = @bitCast(@as(u16, 0)), | |
| 4155 | 4155 | .n_value = 0, |
| 4156 | 4156 | }; |
| 4157 | 4157 | |
| ... | ... | @@ -5070,7 +5070,7 @@ pub fn getKernError(err: std.c.kern_return_t) KernE { |
| 5070 | 5070 | pub fn unexpectedKernError(err: KernE) std.posix.UnexpectedError { |
| 5071 | 5071 | if (std.posix.unexpected_error_tracing) { |
| 5072 | 5072 | std.debug.print("unexpected error: {d}\n", .{@intFromEnum(err)}); |
| 5073 | std.debug.dumpCurrentStackTrace(null); | |
| 5073 | std.debug.dumpCurrentStackTrace(.{}); | |
| 5074 | 5074 | } |
| 5075 | 5075 | return error.Unexpected; |
| 5076 | 5076 | } |
src/link/MachO/InternalObject.zig+15-15| ... | ... | @@ -69,9 +69,9 @@ pub fn initSymbols(self: *InternalObject, macho_file: *MachO) !void { |
| 69 | 69 | const nlist = obj.symtab.addOneAssumeCapacity(); |
| 70 | 70 | nlist.* = .{ |
| 71 | 71 | .n_strx = name.pos, |
| 72 | .n_type = args.type, | |
| 72 | .n_type = @bitCast(args.type), | |
| 73 | 73 | .n_sect = 0, |
| 74 | .n_desc = args.desc, | |
| 74 | .n_desc = @bitCast(args.desc), | |
| 75 | 75 | .n_value = 0, |
| 76 | 76 | }; |
| 77 | 77 | symbol.nlist_idx = nlist_idx; |
| ... | ... | @@ -143,7 +143,7 @@ pub fn resolveSymbols(self: *InternalObject, macho_file: *MachO) !void { |
| 143 | 143 | } |
| 144 | 144 | global.* = gop.index; |
| 145 | 145 | |
| 146 | if (nlist.undf()) continue; | |
| 146 | if (nlist.n_type.bits.type == .undf) continue; | |
| 147 | 147 | if (gop.ref.getFile(macho_file) == null) { |
| 148 | 148 | gop.ref.* = .{ .index = @intCast(i), .file = self.index }; |
| 149 | 149 | continue; |
| ... | ... | @@ -171,7 +171,7 @@ pub fn resolveBoundarySymbols(self: *InternalObject, macho_file: *MachO) !void { |
| 171 | 171 | const object = macho_file.getFile(index).?.object; |
| 172 | 172 | for (object.symbols.items, 0..) |sym, i| { |
| 173 | 173 | const nlist = object.symtab.items(.nlist)[i]; |
| 174 | if (!nlist.undf() or !nlist.ext()) continue; | |
| 174 | if (nlist.n_type.bits.type != .undf or !nlist.n_type.bits.ext) continue; | |
| 175 | 175 | const ref = object.getSymbolRef(@intCast(i), macho_file); |
| 176 | 176 | if (ref.getFile(macho_file) != null) continue; |
| 177 | 177 | const name = sym.getName(macho_file); |
| ... | ... | @@ -206,9 +206,9 @@ pub fn resolveBoundarySymbols(self: *InternalObject, macho_file: *MachO) !void { |
| 206 | 206 | const nlist = self.symtab.addOneAssumeCapacity(); |
| 207 | 207 | nlist.* = .{ |
| 208 | 208 | .n_strx = name_str.pos, |
| 209 | .n_type = macho.N_SECT, | |
| 209 | .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } }, | |
| 210 | 210 | .n_sect = 0, |
| 211 | .n_desc = 0, | |
| 211 | .n_desc = @bitCast(@as(u16, 0)), | |
| 212 | 212 | .n_value = 0, |
| 213 | 213 | }; |
| 214 | 214 | sym.nlist_idx = nlist_idx; |
| ... | ... | @@ -226,7 +226,7 @@ pub fn markLive(self: *InternalObject, macho_file: *MachO) void { |
| 226 | 226 | |
| 227 | 227 | for (0..self.symbols.items.len) |i| { |
| 228 | 228 | const nlist = self.symtab.items[i]; |
| 229 | if (!nlist.ext()) continue; | |
| 229 | if (!nlist.n_type.bits.ext) continue; | |
| 230 | 230 | |
| 231 | 231 | const ref = self.getSymbolRef(@intCast(i), macho_file); |
| 232 | 232 | const file = ref.getFile(macho_file) orelse continue; |
| ... | ... | @@ -273,9 +273,9 @@ fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_fil |
| 273 | 273 | const nlist = try self.symtab.addOne(gpa); |
| 274 | 274 | nlist.* = .{ |
| 275 | 275 | .n_strx = name_str.pos, |
| 276 | .n_type = macho.N_SECT, | |
| 276 | .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } }, | |
| 277 | 277 | .n_sect = @intCast(n_sect + 1), |
| 278 | .n_desc = 0, | |
| 278 | .n_desc = @bitCast(@as(u16, 0)), | |
| 279 | 279 | .n_value = 0, |
| 280 | 280 | }; |
| 281 | 281 | sym.nlist_idx = nlist_idx; |
| ... | ... | @@ -326,9 +326,9 @@ fn addObjcSelrefsSection(self: *InternalObject, methname_sym_index: Symbol.Index |
| 326 | 326 | const nlist = try self.symtab.addOne(gpa); |
| 327 | 327 | nlist.* = .{ |
| 328 | 328 | .n_strx = 0, |
| 329 | .n_type = macho.N_SECT, | |
| 329 | .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } }, | |
| 330 | 330 | .n_sect = @intCast(n_sect + 1), |
| 331 | .n_desc = 0, | |
| 331 | .n_desc = @bitCast(@as(u16, 0)), | |
| 332 | 332 | .n_value = 0, |
| 333 | 333 | }; |
| 334 | 334 | sym.nlist_idx = nlist_idx; |
| ... | ... | @@ -352,8 +352,8 @@ pub fn resolveObjcMsgSendSymbols(self: *InternalObject, macho_file: *MachO) !voi |
| 352 | 352 | |
| 353 | 353 | for (object.symbols.items, 0..) |sym, i| { |
| 354 | 354 | const nlist = object.symtab.items(.nlist)[i]; |
| 355 | if (!nlist.ext()) continue; | |
| 356 | if (!nlist.undf()) continue; | |
| 355 | if (!nlist.n_type.bits.ext) continue; | |
| 356 | if (nlist.n_type.bits.type != .undf) continue; | |
| 357 | 357 | |
| 358 | 358 | const ref = object.getSymbolRef(@intCast(i), macho_file); |
| 359 | 359 | if (ref.getFile(macho_file) != null) continue; |
| ... | ... | @@ -381,9 +381,9 @@ pub fn resolveObjcMsgSendSymbols(self: *InternalObject, macho_file: *MachO) !voi |
| 381 | 381 | const nlist = try self.symtab.addOne(gpa); |
| 382 | 382 | nlist.* = .{ |
| 383 | 383 | .n_strx = name_str.pos, |
| 384 | .n_type = macho.N_SECT | macho.N_EXT | macho.N_PEXT, | |
| 384 | .n_type = .{ .bits = .{ .ext = true, .type = .sect, .pext = true, .is_stab = 0 } }, | |
| 385 | 385 | .n_sect = 0, |
| 386 | .n_desc = 0, | |
| 386 | .n_desc = @bitCast(@as(u16, 0)), | |
| 387 | 387 | .n_value = 0, |
| 388 | 388 | }; |
| 389 | 389 | sym.nlist_idx = nlist_idx; |
src/link/MachO/Object.zig+82-80| ... | ... | @@ -179,13 +179,13 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { |
| 179 | 179 | idx: usize, |
| 180 | 180 | |
| 181 | 181 | fn rank(ctx: *const Object, nl: macho.nlist_64) u8 { |
| 182 | if (!nl.ext()) { | |
| 182 | if (!nl.n_type.bits.ext) { | |
| 183 | 183 | const name = ctx.getNStrx(nl.n_strx); |
| 184 | 184 | if (name.len == 0) return 5; |
| 185 | 185 | if (name[0] == 'l' or name[0] == 'L') return 4; |
| 186 | 186 | return 3; |
| 187 | 187 | } |
| 188 | return if (nl.weakDef()) 2 else 1; | |
| 188 | return if (nl.n_desc.weak_def_or_ref_to_weak) 2 else 1; | |
| 189 | 189 | } |
| 190 | 190 | |
| 191 | 191 | fn lessThan(ctx: *const Object, lhs: @This(), rhs: @This()) bool { |
| ... | ... | @@ -202,7 +202,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { |
| 202 | 202 | var nlists = try std.array_list.Managed(NlistIdx).initCapacity(gpa, self.symtab.items(.nlist).len); |
| 203 | 203 | defer nlists.deinit(); |
| 204 | 204 | for (self.symtab.items(.nlist), 0..) |nlist, i| { |
| 205 | if (nlist.stab() or !nlist.sect()) continue; | |
| 205 | if (nlist.n_type.bits.is_stab != 0 or nlist.n_type.bits.type != .sect) continue; | |
| 206 | 206 | nlists.appendAssumeCapacity(.{ .nlist = nlist, .idx = i }); |
| 207 | 207 | } |
| 208 | 208 | mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan); |
| ... | ... | @@ -488,9 +488,9 @@ fn initCstringLiterals(self: *Object, allocator: Allocator, file: File.Handle, m |
| 488 | 488 | self.symtab.set(nlist_index, .{ |
| 489 | 489 | .nlist = .{ |
| 490 | 490 | .n_strx = name_str.pos, |
| 491 | .n_type = macho.N_SECT, | |
| 491 | .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } }, | |
| 492 | 492 | .n_sect = @intCast(atom.n_sect + 1), |
| 493 | .n_desc = 0, | |
| 493 | .n_desc = @bitCast(@as(u16, 0)), | |
| 494 | 494 | .n_value = atom.getInputAddress(macho_file), |
| 495 | 495 | }, |
| 496 | 496 | .size = atom.size, |
| ... | ... | @@ -555,9 +555,9 @@ fn initFixedSizeLiterals(self: *Object, allocator: Allocator, macho_file: *MachO |
| 555 | 555 | self.symtab.set(nlist_index, .{ |
| 556 | 556 | .nlist = .{ |
| 557 | 557 | .n_strx = name_str.pos, |
| 558 | .n_type = macho.N_SECT, | |
| 558 | .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } }, | |
| 559 | 559 | .n_sect = @intCast(atom.n_sect + 1), |
| 560 | .n_desc = 0, | |
| 560 | .n_desc = @bitCast(@as(u16, 0)), | |
| 561 | 561 | .n_value = atom.getInputAddress(macho_file), |
| 562 | 562 | }, |
| 563 | 563 | .size = atom.size, |
| ... | ... | @@ -613,9 +613,9 @@ fn initPointerLiterals(self: *Object, allocator: Allocator, macho_file: *MachO) |
| 613 | 613 | self.symtab.set(nlist_index, .{ |
| 614 | 614 | .nlist = .{ |
| 615 | 615 | .n_strx = name_str.pos, |
| 616 | .n_type = macho.N_SECT, | |
| 616 | .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } }, | |
| 617 | 617 | .n_sect = @intCast(atom.n_sect + 1), |
| 618 | .n_desc = 0, | |
| 618 | .n_desc = @bitCast(@as(u16, 0)), | |
| 619 | 619 | .n_value = atom.getInputAddress(macho_file), |
| 620 | 620 | }, |
| 621 | 621 | .size = atom.size, |
| ... | ... | @@ -805,7 +805,7 @@ fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void { |
| 805 | 805 | const tracy = trace(@src()); |
| 806 | 806 | defer tracy.end(); |
| 807 | 807 | for (self.symtab.items(.nlist), self.symtab.items(.atom)) |nlist, *atom| { |
| 808 | if (!nlist.stab() and nlist.sect()) { | |
| 808 | if (nlist.n_type.bits.is_stab == 0 and nlist.n_type.bits.type == .sect) { | |
| 809 | 809 | const sect = self.sections.items(.header)[nlist.n_sect - 1]; |
| 810 | 810 | const subs = self.sections.items(.subsections)[nlist.n_sect - 1].items; |
| 811 | 811 | if (nlist.n_value == sect.addr) { |
| ... | ... | @@ -852,30 +852,30 @@ fn initSymbols(self: *Object, allocator: Allocator, macho_file: *MachO) !void { |
| 852 | 852 | symbol.extra = self.addSymbolExtraAssumeCapacity(.{}); |
| 853 | 853 | |
| 854 | 854 | if (self.getAtom(atom_index)) |atom| { |
| 855 | assert(!nlist.abs()); | |
| 855 | assert(nlist.n_type.bits.type != .abs); | |
| 856 | 856 | symbol.value -= atom.getInputAddress(macho_file); |
| 857 | 857 | symbol.atom_ref = .{ .index = atom_index, .file = self.index }; |
| 858 | 858 | } |
| 859 | 859 | |
| 860 | symbol.flags.weak = nlist.weakDef(); | |
| 861 | symbol.flags.abs = nlist.abs(); | |
| 860 | symbol.flags.weak = nlist.n_desc.weak_def_or_ref_to_weak; | |
| 861 | symbol.flags.abs = nlist.n_type.bits.type == .abs; | |
| 862 | 862 | symbol.flags.tentative = nlist.tentative(); |
| 863 | symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip(); | |
| 864 | symbol.flags.dyn_ref = nlist.n_desc & macho.REFERENCED_DYNAMICALLY != 0; | |
| 863 | symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.n_desc.discarded_or_no_dead_strip; | |
| 864 | symbol.flags.dyn_ref = nlist.n_desc.referenced_dynamically; | |
| 865 | 865 | symbol.flags.interposable = false; |
| 866 | 866 | // TODO |
| 867 | // symbol.flags.interposable = nlist.ext() and (nlist.sect() or nlist.abs()) and macho_file.base.isDynLib() and macho_file.options.namespace == .flat and !nlist.pext(); | |
| 867 | // symbol.flags.interposable = nlist.ext() and (nlist.n_type.bits.type == .sect or nlist.n_type.bits.type == .abs) and macho_file.base.isDynLib() and macho_file.options.namespace == .flat and !nlist.pext(); | |
| 868 | 868 | |
| 869 | if (nlist.sect() and | |
| 869 | if (nlist.n_type.bits.type == .sect and | |
| 870 | 870 | self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES) |
| 871 | 871 | { |
| 872 | 872 | symbol.flags.tlv = true; |
| 873 | 873 | } |
| 874 | 874 | |
| 875 | if (nlist.ext()) { | |
| 876 | if (nlist.undf()) { | |
| 877 | symbol.flags.weak_ref = nlist.weakRef(); | |
| 878 | } else if (nlist.pext() or (nlist.weakDef() and nlist.weakRef()) or self.hidden) { | |
| 875 | if (nlist.n_type.bits.ext) { | |
| 876 | if (nlist.n_type.bits.type == .undf) { | |
| 877 | symbol.flags.weak_ref = nlist.n_desc.weak_ref; | |
| 878 | } else if (nlist.n_type.bits.pext or (nlist.n_desc.weak_def_or_ref_to_weak and nlist.n_desc.weak_ref) or self.hidden) { | |
| 879 | 879 | symbol.visibility = .hidden; |
| 880 | 880 | } else { |
| 881 | 881 | symbol.visibility = .global; |
| ... | ... | @@ -902,10 +902,10 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f |
| 902 | 902 | }; |
| 903 | 903 | |
| 904 | 904 | const start: u32 = for (self.symtab.items(.nlist), 0..) |nlist, i| { |
| 905 | if (nlist.stab()) break @intCast(i); | |
| 905 | if (nlist.n_type.bits.is_stab != 0) break @intCast(i); | |
| 906 | 906 | } else @intCast(self.symtab.items(.nlist).len); |
| 907 | 907 | const end: u32 = for (self.symtab.items(.nlist)[start..], start..) |nlist, i| { |
| 908 | if (!nlist.stab()) break @intCast(i); | |
| 908 | if (nlist.n_type.bits.is_stab == 0) break @intCast(i); | |
| 909 | 909 | } else @intCast(self.symtab.items(.nlist).len); |
| 910 | 910 | |
| 911 | 911 | if (start == end) return; |
| ... | ... | @@ -919,7 +919,7 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f |
| 919 | 919 | var addr_lookup = std.StringHashMap(u64).init(allocator); |
| 920 | 920 | defer addr_lookup.deinit(); |
| 921 | 921 | for (syms) |sym| { |
| 922 | if (sym.sect() and (sym.ext() or sym.pext())) { | |
| 922 | if (sym.n_type.bits.type == .sect and (sym.n_type.bits.ext or sym.n_type.bits.pext)) { | |
| 923 | 923 | try addr_lookup.putNoClobber(self.getNStrx(sym.n_strx), sym.n_value); |
| 924 | 924 | } |
| 925 | 925 | } |
| ... | ... | @@ -927,41 +927,43 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f |
| 927 | 927 | var i: u32 = start; |
| 928 | 928 | while (i < end) : (i += 1) { |
| 929 | 929 | const open = syms[i]; |
| 930 | if (open.n_type != macho.N_SO) { | |
| 930 | if (open.n_type.stab != .so) { | |
| 931 | 931 | try macho_file.reportParseError2(self.index, "unexpected symbol stab type 0x{x} as the first entry", .{ |
| 932 | open.n_type, | |
| 932 | @intFromEnum(open.n_type.stab), | |
| 933 | 933 | }); |
| 934 | 934 | return error.MalformedObject; |
| 935 | 935 | } |
| 936 | 936 | |
| 937 | while (i < end and syms[i].n_type == macho.N_SO and syms[i].n_sect != 0) : (i += 1) {} | |
| 937 | while (i < end and syms[i].n_type.stab == .so and syms[i].n_sect != 0) : (i += 1) {} | |
| 938 | 938 | |
| 939 | 939 | var sf: StabFile = .{ .comp_dir = i }; |
| 940 | 940 | // TODO validate |
| 941 | 941 | i += 3; |
| 942 | 942 | |
| 943 | while (i < end and syms[i].n_type != macho.N_SO) : (i += 1) { | |
| 943 | while (i < end and syms[i].n_type.stab != .so) : (i += 1) { | |
| 944 | 944 | const nlist = syms[i]; |
| 945 | 945 | var stab: StabFile.Stab = .{}; |
| 946 | switch (nlist.n_type) { | |
| 947 | macho.N_BNSYM => { | |
| 946 | switch (nlist.n_type.stab) { | |
| 947 | .bnsym => { | |
| 948 | 948 | stab.is_func = true; |
| 949 | 949 | stab.index = sym_lookup.find(nlist.n_value); |
| 950 | 950 | // TODO validate |
| 951 | 951 | i += 3; |
| 952 | 952 | }, |
| 953 | macho.N_GSYM => { | |
| 953 | .gsym => { | |
| 954 | 954 | stab.is_func = false; |
| 955 | 955 | stab.index = sym_lookup.find(addr_lookup.get(self.getNStrx(nlist.n_strx)).?); |
| 956 | 956 | }, |
| 957 | macho.N_STSYM => { | |
| 957 | .stsym => { | |
| 958 | 958 | stab.is_func = false; |
| 959 | 959 | stab.index = sym_lookup.find(nlist.n_value); |
| 960 | 960 | }, |
| 961 | _ => { | |
| 962 | try macho_file.reportParseError2(self.index, "unhandled symbol stab type 0x{x}", .{@intFromEnum(nlist.n_type.stab)}); | |
| 963 | return error.MalformedObject; | |
| 964 | }, | |
| 961 | 965 | else => { |
| 962 | try macho_file.reportParseError2(self.index, "unhandled symbol stab type 0x{x}", .{ | |
| 963 | nlist.n_type, | |
| 964 | }); | |
| 966 | try macho_file.reportParseError2(self.index, "unhandled symbol stab type '{t}'", .{nlist.n_type.stab}); | |
| 965 | 967 | return error.MalformedObject; |
| 966 | 968 | }, |
| 967 | 969 | } |
| ... | ... | @@ -1132,7 +1134,7 @@ fn initUnwindRecords(self: *Object, allocator: Allocator, sect_id: u8, file: Fil |
| 1132 | 1134 | fn find(fs: @This(), addr: u64) ?Symbol.Index { |
| 1133 | 1135 | for (0..fs.ctx.symbols.items.len) |i| { |
| 1134 | 1136 | const nlist = fs.ctx.symtab.items(.nlist)[i]; |
| 1135 | if (nlist.ext() and nlist.n_value == addr) return @intCast(i); | |
| 1137 | if (nlist.n_type.bits.ext and nlist.n_value == addr) return @intCast(i); | |
| 1136 | 1138 | } |
| 1137 | 1139 | return null; |
| 1138 | 1140 | } |
| ... | ... | @@ -1241,8 +1243,8 @@ fn parseUnwindRecords(self: *Object, allocator: Allocator, cpu_arch: std.Target. |
| 1241 | 1243 | |
| 1242 | 1244 | const slice = self.symtab.slice(); |
| 1243 | 1245 | for (slice.items(.nlist), slice.items(.atom), slice.items(.size)) |nlist, atom, size| { |
| 1244 | if (nlist.stab()) continue; | |
| 1245 | if (!nlist.sect()) continue; | |
| 1246 | if (nlist.n_type.bits.is_stab != 0) continue; | |
| 1247 | if (nlist.n_type.bits.type != .sect) continue; | |
| 1246 | 1248 | const sect = self.sections.items(.header)[nlist.n_sect - 1]; |
| 1247 | 1249 | if (sect.isCode() and sect.size > 0) { |
| 1248 | 1250 | try superposition.ensureUnusedCapacity(1); |
| ... | ... | @@ -1458,8 +1460,8 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void { |
| 1458 | 1460 | const gpa = macho_file.base.comp.gpa; |
| 1459 | 1461 | |
| 1460 | 1462 | for (self.symtab.items(.nlist), self.symtab.items(.atom), self.globals.items, 0..) |nlist, atom_index, *global, i| { |
| 1461 | if (!nlist.ext()) continue; | |
| 1462 | if (nlist.sect()) { | |
| 1463 | if (!nlist.n_type.bits.ext) continue; | |
| 1464 | if (nlist.n_type.bits.type == .sect) { | |
| 1463 | 1465 | const atom = self.getAtom(atom_index).?; |
| 1464 | 1466 | if (!atom.isAlive()) continue; |
| 1465 | 1467 | } |
| ... | ... | @@ -1473,7 +1475,7 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void { |
| 1473 | 1475 | } |
| 1474 | 1476 | global.* = gop.index; |
| 1475 | 1477 | |
| 1476 | if (nlist.undf() and !nlist.tentative()) continue; | |
| 1478 | if (nlist.n_type.bits.type == .undf and !nlist.tentative()) continue; | |
| 1477 | 1479 | if (gop.ref.getFile(macho_file) == null) { |
| 1478 | 1480 | gop.ref.* = .{ .index = @intCast(i), .file = self.index }; |
| 1479 | 1481 | continue; |
| ... | ... | @@ -1481,7 +1483,7 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void { |
| 1481 | 1483 | |
| 1482 | 1484 | if (self.asFile().getSymbolRank(.{ |
| 1483 | 1485 | .archive = !self.alive, |
| 1484 | .weak = nlist.weakDef(), | |
| 1486 | .weak = nlist.n_desc.weak_def_or_ref_to_weak, | |
| 1485 | 1487 | .tentative = nlist.tentative(), |
| 1486 | 1488 | }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) { |
| 1487 | 1489 | gop.ref.* = .{ .index = @intCast(i), .file = self.index }; |
| ... | ... | @@ -1495,12 +1497,12 @@ pub fn markLive(self: *Object, macho_file: *MachO) void { |
| 1495 | 1497 | |
| 1496 | 1498 | for (0..self.symbols.items.len) |i| { |
| 1497 | 1499 | const nlist = self.symtab.items(.nlist)[i]; |
| 1498 | if (!nlist.ext()) continue; | |
| 1500 | if (!nlist.n_type.bits.ext) continue; | |
| 1499 | 1501 | |
| 1500 | 1502 | const ref = self.getSymbolRef(@intCast(i), macho_file); |
| 1501 | 1503 | const file = ref.getFile(macho_file) orelse continue; |
| 1502 | 1504 | const sym = ref.getSymbol(macho_file).?; |
| 1503 | const should_keep = nlist.undf() or (nlist.tentative() and !sym.flags.tentative); | |
| 1505 | const should_keep = nlist.n_type.bits.type == .undf or (nlist.tentative() and !sym.flags.tentative); | |
| 1504 | 1506 | if (should_keep and file == .object and !file.object.alive) { |
| 1505 | 1507 | file.object.alive = true; |
| 1506 | 1508 | file.object.markLive(macho_file); |
| ... | ... | @@ -1565,7 +1567,7 @@ pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void { |
| 1565 | 1567 | const name = try std.fmt.allocPrintSentinel(gpa, "__DATA$__common${s}", .{sym.getName(macho_file)}, 0); |
| 1566 | 1568 | defer gpa.free(name); |
| 1567 | 1569 | |
| 1568 | const alignment = (nlist.n_desc >> 8) & 0x0f; | |
| 1570 | const alignment = (@as(u16, @bitCast(nlist.n_desc)) >> 8) & 0x0f; | |
| 1569 | 1571 | const n_sect = try self.addSection(gpa, "__DATA", "__common"); |
| 1570 | 1572 | const atom_index = try self.addAtom(gpa, .{ |
| 1571 | 1573 | .name = try self.addString(gpa, name), |
| ... | ... | @@ -1589,9 +1591,9 @@ pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void { |
| 1589 | 1591 | sym.visibility = .global; |
| 1590 | 1592 | |
| 1591 | 1593 | nlist.n_value = 0; |
| 1592 | nlist.n_type = macho.N_EXT | macho.N_SECT; | |
| 1594 | nlist.n_type = .{ .bits = .{ .ext = true, .type = .sect, .pext = false, .is_stab = 0 } }; | |
| 1593 | 1595 | nlist.n_sect = 0; |
| 1594 | nlist.n_desc = 0; | |
| 1596 | nlist.n_desc = @bitCast(@as(u16, 0)); | |
| 1595 | 1597 | nlist_atom.* = atom_index; |
| 1596 | 1598 | } |
| 1597 | 1599 | } |
| ... | ... | @@ -1685,7 +1687,7 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void { |
| 1685 | 1687 | pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, macho_file: *MachO) error{OutOfMemory}!void { |
| 1686 | 1688 | const gpa = macho_file.base.comp.gpa; |
| 1687 | 1689 | for (self.symtab.items(.nlist)) |nlist| { |
| 1688 | if (!nlist.ext() or (nlist.undf() and !nlist.tentative())) continue; | |
| 1690 | if (!nlist.n_type.bits.ext or (nlist.n_type.bits.type == .undf and !nlist.tentative())) continue; | |
| 1689 | 1691 | const off = try ar_symtab.strtab.insert(gpa, self.getNStrx(nlist.n_strx)); |
| 1690 | 1692 | try ar_symtab.entries.append(gpa, .{ .off = off, .file = self.index }); |
| 1691 | 1693 | } |
| ... | ... | @@ -2028,30 +2030,30 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v |
| 2028 | 2030 | ) void { |
| 2029 | 2031 | context.symtab.items[index] = .{ |
| 2030 | 2032 | .n_strx = 0, |
| 2031 | .n_type = macho.N_BNSYM, | |
| 2033 | .n_type = .{ .stab = .bnsym }, | |
| 2032 | 2034 | .n_sect = n_sect, |
| 2033 | .n_desc = 0, | |
| 2035 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2034 | 2036 | .n_value = n_value, |
| 2035 | 2037 | }; |
| 2036 | 2038 | context.symtab.items[index + 1] = .{ |
| 2037 | 2039 | .n_strx = n_strx, |
| 2038 | .n_type = macho.N_FUN, | |
| 2040 | .n_type = .{ .stab = .fun }, | |
| 2039 | 2041 | .n_sect = n_sect, |
| 2040 | .n_desc = 0, | |
| 2042 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2041 | 2043 | .n_value = n_value, |
| 2042 | 2044 | }; |
| 2043 | 2045 | context.symtab.items[index + 2] = .{ |
| 2044 | 2046 | .n_strx = 0, |
| 2045 | .n_type = macho.N_FUN, | |
| 2047 | .n_type = .{ .stab = .fun }, | |
| 2046 | 2048 | .n_sect = 0, |
| 2047 | .n_desc = 0, | |
| 2049 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2048 | 2050 | .n_value = size, |
| 2049 | 2051 | }; |
| 2050 | 2052 | context.symtab.items[index + 3] = .{ |
| 2051 | 2053 | .n_strx = 0, |
| 2052 | .n_type = macho.N_ENSYM, | |
| 2054 | .n_type = .{ .stab = .ensym }, | |
| 2053 | 2055 | .n_sect = n_sect, |
| 2054 | .n_desc = 0, | |
| 2056 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2055 | 2057 | .n_value = size, |
| 2056 | 2058 | }; |
| 2057 | 2059 | } |
| ... | ... | @@ -2068,9 +2070,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v |
| 2068 | 2070 | // N_SO comp_dir |
| 2069 | 2071 | ctx.symtab.items[index] = .{ |
| 2070 | 2072 | .n_strx = n_strx, |
| 2071 | .n_type = macho.N_SO, | |
| 2073 | .n_type = .{ .stab = .so }, | |
| 2072 | 2074 | .n_sect = 0, |
| 2073 | .n_desc = 0, | |
| 2075 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2074 | 2076 | .n_value = 0, |
| 2075 | 2077 | }; |
| 2076 | 2078 | index += 1; |
| ... | ... | @@ -2081,9 +2083,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v |
| 2081 | 2083 | // N_SO tu_name |
| 2082 | 2084 | macho_file.symtab.items[index] = .{ |
| 2083 | 2085 | .n_strx = n_strx, |
| 2084 | .n_type = macho.N_SO, | |
| 2086 | .n_type = .{ .stab = .so }, | |
| 2085 | 2087 | .n_sect = 0, |
| 2086 | .n_desc = 0, | |
| 2088 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2087 | 2089 | .n_value = 0, |
| 2088 | 2090 | }; |
| 2089 | 2091 | index += 1; |
| ... | ... | @@ -2094,9 +2096,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v |
| 2094 | 2096 | // N_OSO path |
| 2095 | 2097 | ctx.symtab.items[index] = .{ |
| 2096 | 2098 | .n_strx = n_strx, |
| 2097 | .n_type = macho.N_OSO, | |
| 2099 | .n_type = .{ .stab = .oso }, | |
| 2098 | 2100 | .n_sect = 0, |
| 2099 | .n_desc = 1, | |
| 2101 | .n_desc = @bitCast(@as(u16, 1)), | |
| 2100 | 2102 | .n_value = self.mtime, |
| 2101 | 2103 | }; |
| 2102 | 2104 | index += 1; |
| ... | ... | @@ -2159,18 +2161,18 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v |
| 2159 | 2161 | } else if (sym.visibility == .global) { |
| 2160 | 2162 | ctx.symtab.items[index] = .{ |
| 2161 | 2163 | .n_strx = sym_n_strx, |
| 2162 | .n_type = macho.N_GSYM, | |
| 2164 | .n_type = .{ .stab = .gsym }, | |
| 2163 | 2165 | .n_sect = sym_n_sect, |
| 2164 | .n_desc = 0, | |
| 2166 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2165 | 2167 | .n_value = 0, |
| 2166 | 2168 | }; |
| 2167 | 2169 | index += 1; |
| 2168 | 2170 | } else { |
| 2169 | 2171 | ctx.symtab.items[index] = .{ |
| 2170 | 2172 | .n_strx = sym_n_strx, |
| 2171 | .n_type = macho.N_STSYM, | |
| 2173 | .n_type = .{ .stab = .stsym }, | |
| 2172 | 2174 | .n_sect = sym_n_sect, |
| 2173 | .n_desc = 0, | |
| 2175 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2174 | 2176 | .n_value = sym_n_value, |
| 2175 | 2177 | }; |
| 2176 | 2178 | index += 1; |
| ... | ... | @@ -2181,9 +2183,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v |
| 2181 | 2183 | // N_SO |
| 2182 | 2184 | ctx.symtab.items[index] = .{ |
| 2183 | 2185 | .n_strx = 0, |
| 2184 | .n_type = macho.N_SO, | |
| 2186 | .n_type = .{ .stab = .so }, | |
| 2185 | 2187 | .n_sect = 0, |
| 2186 | .n_desc = 0, | |
| 2188 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2187 | 2189 | .n_value = 0, |
| 2188 | 2190 | }; |
| 2189 | 2191 | } else { |
| ... | ... | @@ -2198,9 +2200,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v |
| 2198 | 2200 | // N_SO comp_dir |
| 2199 | 2201 | ctx.symtab.items[index] = .{ |
| 2200 | 2202 | .n_strx = n_strx, |
| 2201 | .n_type = macho.N_SO, | |
| 2203 | .n_type = .{ .stab = .so }, | |
| 2202 | 2204 | .n_sect = 0, |
| 2203 | .n_desc = 0, | |
| 2205 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2204 | 2206 | .n_value = 0, |
| 2205 | 2207 | }; |
| 2206 | 2208 | index += 1; |
| ... | ... | @@ -2211,9 +2213,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v |
| 2211 | 2213 | // N_SO tu_name |
| 2212 | 2214 | ctx.symtab.items[index] = .{ |
| 2213 | 2215 | .n_strx = n_strx, |
| 2214 | .n_type = macho.N_SO, | |
| 2216 | .n_type = .{ .stab = .so }, | |
| 2215 | 2217 | .n_sect = 0, |
| 2216 | .n_desc = 0, | |
| 2218 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2217 | 2219 | .n_value = 0, |
| 2218 | 2220 | }; |
| 2219 | 2221 | index += 1; |
| ... | ... | @@ -2224,9 +2226,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v |
| 2224 | 2226 | // N_OSO path |
| 2225 | 2227 | ctx.symtab.items[index] = .{ |
| 2226 | 2228 | .n_strx = n_strx, |
| 2227 | .n_type = macho.N_OSO, | |
| 2229 | .n_type = .{ .stab = .so }, | |
| 2228 | 2230 | .n_sect = 0, |
| 2229 | .n_desc = 1, | |
| 2231 | .n_desc = @bitCast(@as(u16, 1)), | |
| 2230 | 2232 | .n_value = sf.getOsoModTime(self), |
| 2231 | 2233 | }; |
| 2232 | 2234 | index += 1; |
| ... | ... | @@ -2254,18 +2256,18 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v |
| 2254 | 2256 | } else if (sym.visibility == .global) { |
| 2255 | 2257 | ctx.symtab.items[index] = .{ |
| 2256 | 2258 | .n_strx = sym_n_strx, |
| 2257 | .n_type = macho.N_GSYM, | |
| 2259 | .n_type = .{ .stab = .gsym }, | |
| 2258 | 2260 | .n_sect = sym_n_sect, |
| 2259 | .n_desc = 0, | |
| 2261 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2260 | 2262 | .n_value = 0, |
| 2261 | 2263 | }; |
| 2262 | 2264 | index += 1; |
| 2263 | 2265 | } else { |
| 2264 | 2266 | ctx.symtab.items[index] = .{ |
| 2265 | 2267 | .n_strx = sym_n_strx, |
| 2266 | .n_type = macho.N_STSYM, | |
| 2268 | .n_type = .{ .stab = .stsym }, | |
| 2267 | 2269 | .n_sect = sym_n_sect, |
| 2268 | .n_desc = 0, | |
| 2270 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2269 | 2271 | .n_value = sym_n_value, |
| 2270 | 2272 | }; |
| 2271 | 2273 | index += 1; |
| ... | ... | @@ -2276,9 +2278,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v |
| 2276 | 2278 | // N_SO |
| 2277 | 2279 | ctx.symtab.items[index] = .{ |
| 2278 | 2280 | .n_strx = 0, |
| 2279 | .n_type = macho.N_SO, | |
| 2281 | .n_type = .{ .stab = .so }, | |
| 2280 | 2282 | .n_sect = 0, |
| 2281 | .n_desc = 0, | |
| 2283 | .n_desc = @bitCast(@as(u16, 0)), | |
| 2282 | 2284 | .n_value = 0, |
| 2283 | 2285 | }; |
| 2284 | 2286 | index += 1; |
src/link/MachO/Symbol.zig+28-14| ... | ... | @@ -36,7 +36,7 @@ pub fn isLocal(symbol: Symbol) bool { |
| 36 | 36 | pub fn isSymbolStab(symbol: Symbol, macho_file: *MachO) bool { |
| 37 | 37 | const file = symbol.getFile(macho_file) orelse return false; |
| 38 | 38 | return switch (file) { |
| 39 | .object => symbol.getNlist(macho_file).stab(), | |
| 39 | .object => symbol.getNlist(macho_file).n_type.bits.is_stab != 0, | |
| 40 | 40 | else => false, |
| 41 | 41 | }; |
| 42 | 42 | } |
| ... | ... | @@ -233,35 +233,49 @@ pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void { |
| 233 | 233 | |
| 234 | 234 | pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void { |
| 235 | 235 | if (symbol.isLocal()) { |
| 236 | out.n_type = if (symbol.flags.abs) macho.N_ABS else macho.N_SECT; | |
| 236 | out.n_type = .{ .bits = .{ | |
| 237 | .ext = false, | |
| 238 | .type = if (symbol.flags.abs) .abs else .sect, | |
| 239 | .pext = false, | |
| 240 | .is_stab = 0, | |
| 241 | } }; | |
| 237 | 242 | out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.getOutputSectionIndex(macho_file) + 1); |
| 238 | out.n_desc = 0; | |
| 243 | out.n_desc = @bitCast(@as(u16, 0)); | |
| 239 | 244 | out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file); |
| 240 | 245 | |
| 241 | 246 | switch (symbol.visibility) { |
| 242 | .hidden => out.n_type |= macho.N_PEXT, | |
| 247 | .hidden => out.n_type.bits.pext = true, | |
| 243 | 248 | else => {}, |
| 244 | 249 | } |
| 245 | 250 | } else if (symbol.flags.@"export") { |
| 246 | 251 | assert(symbol.visibility == .global); |
| 247 | out.n_type = macho.N_EXT; | |
| 248 | out.n_type |= if (symbol.flags.abs) macho.N_ABS else macho.N_SECT; | |
| 252 | out.n_type = .{ .bits = .{ | |
| 253 | .ext = true, | |
| 254 | .type = if (symbol.flags.abs) .abs else .sect, | |
| 255 | .pext = false, | |
| 256 | .is_stab = 0, | |
| 257 | } }; | |
| 249 | 258 | out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.getOutputSectionIndex(macho_file) + 1); |
| 250 | 259 | out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file); |
| 251 | out.n_desc = 0; | |
| 260 | out.n_desc = @bitCast(@as(u16, 0)); | |
| 252 | 261 | |
| 253 | 262 | if (symbol.flags.weak) { |
| 254 | out.n_desc |= macho.N_WEAK_DEF; | |
| 263 | out.n_desc.weak_def_or_ref_to_weak = true; | |
| 255 | 264 | } |
| 256 | 265 | if (symbol.flags.dyn_ref) { |
| 257 | out.n_desc |= macho.REFERENCED_DYNAMICALLY; | |
| 266 | out.n_desc.referenced_dynamically = true; | |
| 258 | 267 | } |
| 259 | 268 | } else { |
| 260 | 269 | assert(symbol.visibility == .global); |
| 261 | out.n_type = macho.N_EXT; | |
| 270 | out.n_type = .{ .bits = .{ | |
| 271 | .ext = true, | |
| 272 | .type = .undf, | |
| 273 | .pext = false, | |
| 274 | .is_stab = 0, | |
| 275 | } }; | |
| 262 | 276 | out.n_sect = 0; |
| 263 | 277 | out.n_value = 0; |
| 264 | out.n_desc = 0; | |
| 278 | out.n_desc = @bitCast(@as(u16, 0)); | |
| 265 | 279 | |
| 266 | 280 | // TODO: |
| 267 | 281 | // const ord: u16 = if (macho_file.options.namespace == .flat) |
| ... | ... | @@ -274,14 +288,14 @@ pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) vo |
| 274 | 288 | ord |
| 275 | 289 | else |
| 276 | 290 | macho.BIND_SPECIAL_DYLIB_SELF; |
| 277 | out.n_desc = macho.N_SYMBOL_RESOLVER * ord; | |
| 291 | out.n_desc = @bitCast(macho.N_SYMBOL_RESOLVER * ord); | |
| 278 | 292 | |
| 279 | 293 | if (symbol.flags.weak) { |
| 280 | out.n_desc |= macho.N_WEAK_DEF; | |
| 294 | out.n_desc.weak_def_or_ref_to_weak = true; | |
| 281 | 295 | } |
| 282 | 296 | |
| 283 | 297 | if (symbol.weakRef(macho_file)) { |
| 284 | out.n_desc |= macho.N_WEAK_REF; | |
| 298 | out.n_desc.weak_ref = true; | |
| 285 | 299 | } |
| 286 | 300 | } |
| 287 | 301 | } |
src/link/MachO/Thunk.zig+2-2| ... | ... | @@ -56,10 +56,10 @@ pub fn writeSymtab(thunk: Thunk, macho_file: *MachO, ctx: anytype) void { |
| 56 | 56 | n_strx += @intCast("__thunk".len); |
| 57 | 57 | ctx.strtab.items[n_strx] = 0; |
| 58 | 58 | n_strx += 1; |
| 59 | out_sym.n_type = macho.N_SECT; | |
| 59 | out_sym.n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } }; | |
| 60 | 60 | out_sym.n_sect = @intCast(thunk.out_n_sect + 1); |
| 61 | 61 | out_sym.n_value = @intCast(thunk.getTargetAddress(ref, macho_file)); |
| 62 | out_sym.n_desc = 0; | |
| 62 | out_sym.n_desc = @bitCast(@as(u16, 0)); | |
| 63 | 63 | } |
| 64 | 64 | } |
| 65 | 65 |
src/link/MachO/ZigObject.zig+16-16| ... | ... | @@ -123,9 +123,9 @@ fn newSymbol(self: *ZigObject, allocator: Allocator, name: MachO.String, args: s |
| 123 | 123 | self.symtab.set(nlist_idx, .{ |
| 124 | 124 | .nlist = .{ |
| 125 | 125 | .n_strx = name.pos, |
| 126 | .n_type = args.type, | |
| 126 | .n_type = @bitCast(args.type), | |
| 127 | 127 | .n_sect = 0, |
| 128 | .n_desc = args.desc, | |
| 128 | .n_desc = @bitCast(args.desc), | |
| 129 | 129 | .n_value = 0, |
| 130 | 130 | }, |
| 131 | 131 | .size = 0, |
| ... | ... | @@ -206,8 +206,8 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void { |
| 206 | 206 | const gpa = macho_file.base.comp.gpa; |
| 207 | 207 | |
| 208 | 208 | for (self.symtab.items(.nlist), self.symtab.items(.atom), self.globals.items, 0..) |nlist, atom_index, *global, i| { |
| 209 | if (!nlist.ext()) continue; | |
| 210 | if (nlist.sect()) { | |
| 209 | if (!nlist.n_type.bits.ext) continue; | |
| 210 | if (nlist.n_type.bits.type == .sect) { | |
| 211 | 211 | const atom = self.getAtom(atom_index).?; |
| 212 | 212 | if (!atom.isAlive()) continue; |
| 213 | 213 | } |
| ... | ... | @@ -221,7 +221,7 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void { |
| 221 | 221 | } |
| 222 | 222 | global.* = gop.index; |
| 223 | 223 | |
| 224 | if (nlist.undf() and !nlist.tentative()) continue; | |
| 224 | if (nlist.n_type.bits.type == .undf and !nlist.tentative()) continue; | |
| 225 | 225 | if (gop.ref.getFile(macho_file) == null) { |
| 226 | 226 | gop.ref.* = .{ .index = @intCast(i), .file = self.index }; |
| 227 | 227 | continue; |
| ... | ... | @@ -229,7 +229,7 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void { |
| 229 | 229 | |
| 230 | 230 | if (self.asFile().getSymbolRank(.{ |
| 231 | 231 | .archive = false, |
| 232 | .weak = nlist.weakDef(), | |
| 232 | .weak = nlist.n_desc.weak_def_or_ref_to_weak, | |
| 233 | 233 | .tentative = nlist.tentative(), |
| 234 | 234 | }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) { |
| 235 | 235 | gop.ref.* = .{ .index = @intCast(i), .file = self.index }; |
| ... | ... | @@ -243,12 +243,12 @@ pub fn markLive(self: *ZigObject, macho_file: *MachO) void { |
| 243 | 243 | |
| 244 | 244 | for (0..self.symbols.items.len) |i| { |
| 245 | 245 | const nlist = self.symtab.items(.nlist)[i]; |
| 246 | if (!nlist.ext()) continue; | |
| 246 | if (!nlist.n_type.bits.ext) continue; | |
| 247 | 247 | |
| 248 | 248 | const ref = self.getSymbolRef(@intCast(i), macho_file); |
| 249 | 249 | const file = ref.getFile(macho_file) orelse continue; |
| 250 | 250 | const sym = ref.getSymbol(macho_file).?; |
| 251 | const should_keep = nlist.undf() or (nlist.tentative() and !sym.flags.tentative); | |
| 251 | const should_keep = nlist.n_type.bits.type == .undf or (nlist.tentative() and !sym.flags.tentative); | |
| 252 | 252 | if (should_keep and file == .object and !file.object.alive) { |
| 253 | 253 | file.object.alive = true; |
| 254 | 254 | file.object.markLive(macho_file); |
| ... | ... | @@ -331,8 +331,8 @@ pub fn claimUnresolved(self: *ZigObject, macho_file: *MachO) void { |
| 331 | 331 | |
| 332 | 332 | for (self.symbols.items, 0..) |*sym, i| { |
| 333 | 333 | const nlist = self.symtab.items(.nlist)[i]; |
| 334 | if (!nlist.ext()) continue; | |
| 335 | if (!nlist.undf()) continue; | |
| 334 | if (!nlist.n_type.bits.ext) continue; | |
| 335 | if (nlist.n_type.bits.type != .undf) continue; | |
| 336 | 336 | |
| 337 | 337 | if (self.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue; |
| 338 | 338 | |
| ... | ... | @@ -974,7 +974,7 @@ fn updateNavCode( |
| 974 | 974 | atom.setAlive(true); |
| 975 | 975 | atom.name = sym.name; |
| 976 | 976 | nlist.n_strx = sym.name.pos; |
| 977 | nlist.n_type = macho.N_SECT; | |
| 977 | nlist.n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } }; | |
| 978 | 978 | nlist.n_sect = sect_index + 1; |
| 979 | 979 | self.symtab.items(.size)[sym.nlist_idx] = code.len; |
| 980 | 980 | |
| ... | ... | @@ -1115,7 +1115,7 @@ fn createTlvDescriptor( |
| 1115 | 1115 | atom.name = sym.name; |
| 1116 | 1116 | nlist.n_strx = sym.name.pos; |
| 1117 | 1117 | nlist.n_sect = sect_index + 1; |
| 1118 | nlist.n_type = macho.N_SECT; | |
| 1118 | nlist.n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } }; | |
| 1119 | 1119 | nlist.n_value = 0; |
| 1120 | 1120 | self.symtab.items(.size)[sym.nlist_idx] = size; |
| 1121 | 1121 | |
| ... | ... | @@ -1322,7 +1322,7 @@ pub fn updateExports( |
| 1322 | 1322 | const global_sym = &self.symbols.items[global_nlist_index]; |
| 1323 | 1323 | global_nlist.n_value = nlist.n_value; |
| 1324 | 1324 | global_nlist.n_sect = nlist.n_sect; |
| 1325 | global_nlist.n_type = macho.N_EXT | macho.N_SECT; | |
| 1325 | global_nlist.n_type = .{ .bits = .{ .ext = true, .type = .sect, .pext = false, .is_stab = 0 } }; | |
| 1326 | 1326 | self.symtab.items(.size)[global_nlist_index] = self.symtab.items(.size)[nlist_idx]; |
| 1327 | 1327 | self.symtab.items(.atom)[global_nlist_index] = atom_index; |
| 1328 | 1328 | global_sym.atom_ref = .{ .index = atom_index, .file = self.index }; |
| ... | ... | @@ -1330,7 +1330,7 @@ pub fn updateExports( |
| 1330 | 1330 | switch (exp.opts.linkage) { |
| 1331 | 1331 | .internal => { |
| 1332 | 1332 | // Symbol should be hidden, or in MachO lingo, private extern. |
| 1333 | global_nlist.n_type |= macho.N_PEXT; | |
| 1333 | global_nlist.n_type.bits.pext = true; | |
| 1334 | 1334 | global_sym.visibility = .hidden; |
| 1335 | 1335 | }, |
| 1336 | 1336 | .strong => { |
| ... | ... | @@ -1339,7 +1339,7 @@ pub fn updateExports( |
| 1339 | 1339 | .weak => { |
| 1340 | 1340 | // Weak linkage is specified as part of n_desc field. |
| 1341 | 1341 | // Symbol's n_type is like for a symbol with strong linkage. |
| 1342 | global_nlist.n_desc |= macho.N_WEAK_DEF; | |
| 1342 | global_nlist.n_desc.weak_def_or_ref_to_weak = true; | |
| 1343 | 1343 | global_sym.visibility = .global; |
| 1344 | 1344 | global_sym.flags.weak = true; |
| 1345 | 1345 | }, |
| ... | ... | @@ -1394,7 +1394,7 @@ fn updateLazySymbol( |
| 1394 | 1394 | |
| 1395 | 1395 | const nlist = &self.symtab.items(.nlist)[sym.nlist_idx]; |
| 1396 | 1396 | nlist.n_strx = name_str.pos; |
| 1397 | nlist.n_type = macho.N_SECT; | |
| 1397 | nlist.n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } }; | |
| 1398 | 1398 | nlist.n_sect = output_section_index + 1; |
| 1399 | 1399 | self.symtab.items(.size)[sym.nlist_idx] = code.len; |
| 1400 | 1400 |
src/link/MachO/eh_frame.zig+10-10| ... | ... | @@ -26,24 +26,24 @@ pub const Cie = struct { |
| 26 | 26 | |
| 27 | 27 | for (aug[1..]) |ch| switch (ch) { |
| 28 | 28 | 'R' => { |
| 29 | const enc = try reader.takeByte(); | |
| 30 | if (enc != DW_EH_PE.pcrel | DW_EH_PE.absptr) { | |
| 29 | const enc: DW.EH.PE = @bitCast(try reader.takeByte()); | |
| 30 | if (enc != @as(DW.EH.PE, .{ .type = .absptr, .rel = .pcrel })) { | |
| 31 | 31 | @panic("unexpected pointer encoding"); // TODO error |
| 32 | 32 | } |
| 33 | 33 | }, |
| 34 | 34 | 'P' => { |
| 35 | const enc = try reader.takeByte(); | |
| 36 | if (enc != DW_EH_PE.pcrel | DW_EH_PE.indirect | DW_EH_PE.sdata4) { | |
| 35 | const enc: DW.EH.PE = @bitCast(try reader.takeByte()); | |
| 36 | if (enc != @as(DW.EH.PE, .{ .type = .sdata4, .rel = .pcrel, .indirect = true })) { | |
| 37 | 37 | @panic("unexpected personality pointer encoding"); // TODO error |
| 38 | 38 | } |
| 39 | 39 | _ = try reader.takeInt(u32, .little); // personality pointer |
| 40 | 40 | }, |
| 41 | 41 | 'L' => { |
| 42 | const enc = try reader.takeByte(); | |
| 43 | switch (enc & DW_EH_PE.type_mask) { | |
| 44 | DW_EH_PE.sdata4 => cie.lsda_size = .p32, | |
| 45 | DW_EH_PE.absptr => cie.lsda_size = .p64, | |
| 46 | else => unreachable, // TODO error | |
| 42 | const enc: DW.EH.PE = @bitCast(try reader.takeByte()); | |
| 43 | switch (enc.type) { | |
| 44 | .sdata4 => cie.lsda_size = .p32, | |
| 45 | .absptr => cie.lsda_size = .p64, | |
| 46 | else => @panic("unexpected lsda encoding"), // TODO error | |
| 47 | 47 | } |
| 48 | 48 | }, |
| 49 | 49 | else => @panic("unexpected augmentation string"), // TODO error |
| ... | ... | @@ -505,7 +505,7 @@ const Writer = std.Io.Writer; |
| 505 | 505 | |
| 506 | 506 | const Allocator = std.mem.Allocator; |
| 507 | 507 | const Atom = @import("Atom.zig"); |
| 508 | const DW_EH_PE = std.dwarf.EH.PE; | |
| 508 | const DW = std.dwarf; | |
| 509 | 509 | const File = @import("file.zig").File; |
| 510 | 510 | const MachO = @import("../MachO.zig"); |
| 511 | 511 | const Object = @import("Object.zig"); |
src/link/MachO/file.zig+8-8| ... | ... | @@ -192,21 +192,21 @@ pub const File = union(enum) { |
| 192 | 192 | assert(file == .object or file == .zig_object); |
| 193 | 193 | |
| 194 | 194 | for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| { |
| 195 | if (!nlist.ext()) continue; | |
| 196 | if (!nlist.undf()) continue; | |
| 195 | if (!nlist.n_type.bits.ext) continue; | |
| 196 | if (nlist.n_type.bits.type != .undf) continue; | |
| 197 | 197 | |
| 198 | 198 | if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue; |
| 199 | 199 | |
| 200 | 200 | const is_import = switch (macho_file.undefined_treatment) { |
| 201 | 201 | .@"error" => false, |
| 202 | .warn, .suppress => nlist.weakRef(), | |
| 202 | .warn, .suppress => nlist.n_desc.weak_ref, | |
| 203 | 203 | .dynamic_lookup => true, |
| 204 | 204 | }; |
| 205 | 205 | if (is_import) { |
| 206 | 206 | sym.value = 0; |
| 207 | 207 | sym.atom_ref = .{ .index = 0, .file = 0 }; |
| 208 | 208 | sym.flags.weak = false; |
| 209 | sym.flags.weak_ref = nlist.weakRef(); | |
| 209 | sym.flags.weak_ref = nlist.n_desc.weak_ref; | |
| 210 | 210 | sym.flags.import = is_import; |
| 211 | 211 | sym.visibility = .global; |
| 212 | 212 | |
| ... | ... | @@ -223,13 +223,13 @@ pub const File = union(enum) { |
| 223 | 223 | assert(file == .object or file == .zig_object); |
| 224 | 224 | |
| 225 | 225 | for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| { |
| 226 | if (!nlist.ext()) continue; | |
| 227 | if (!nlist.undf()) continue; | |
| 226 | if (!nlist.n_type.bits.ext) continue; | |
| 227 | if (nlist.n_type.bits.type != .undf) continue; | |
| 228 | 228 | if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue; |
| 229 | 229 | |
| 230 | 230 | sym.value = 0; |
| 231 | 231 | sym.atom_ref = .{ .index = 0, .file = 0 }; |
| 232 | sym.flags.weak_ref = nlist.weakRef(); | |
| 232 | sym.flags.weak_ref = nlist.n_desc.weak_ref; | |
| 233 | 233 | sym.flags.import = true; |
| 234 | 234 | sym.visibility = .global; |
| 235 | 235 | |
| ... | ... | @@ -247,7 +247,7 @@ pub const File = union(enum) { |
| 247 | 247 | for (file.getSymbols(), file.getNlists(), 0..) |sym, nlist, i| { |
| 248 | 248 | if (sym.visibility != .global) continue; |
| 249 | 249 | if (sym.flags.weak) continue; |
| 250 | if (nlist.undf()) continue; | |
| 250 | if (nlist.n_type.bits.type == .undf) continue; | |
| 251 | 251 | const ref = file.getSymbolRef(@intCast(i), macho_file); |
| 252 | 252 | const ref_file = ref.getFile(macho_file) orelse continue; |
| 253 | 253 | if (ref_file.getIndex() == file.getIndex()) continue; |
src/main.zig+3-3| ... | ... | @@ -43,7 +43,6 @@ const thread_stack_size = 60 << 20; |
| 43 | 43 | pub const std_options: std.Options = .{ |
| 44 | 44 | .wasiCwd = wasi_cwd, |
| 45 | 45 | .logFn = log, |
| 46 | .enable_segfault_handler = false, | |
| 47 | 46 | |
| 48 | 47 | .log_level = switch (builtin.mode) { |
| 49 | 48 | .Debug => .debug, |
| ... | ... | @@ -53,6 +52,7 @@ pub const std_options: std.Options = .{ |
| 53 | 52 | }; |
| 54 | 53 | |
| 55 | 54 | pub const panic = crash_report.panic; |
| 55 | pub const debug = crash_report.debug; | |
| 56 | 56 | |
| 57 | 57 | var wasi_preopens: fs.wasi.Preopens = undefined; |
| 58 | 58 | pub fn wasi_cwd() std.os.wasi.fd_t { |
| ... | ... | @@ -165,8 +165,6 @@ var debug_allocator: std.heap.DebugAllocator(.{ |
| 165 | 165 | }) = .init; |
| 166 | 166 | |
| 167 | 167 | pub fn main() anyerror!void { |
| 168 | crash_report.initialize(); | |
| 169 | ||
| 170 | 168 | const gpa, const is_debug = gpa: { |
| 171 | 169 | if (build_options.debug_gpa) break :gpa .{ debug_allocator.allocator(), true }; |
| 172 | 170 | if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false }; |
| ... | ... | @@ -192,6 +190,8 @@ pub fn main() anyerror!void { |
| 192 | 190 | |
| 193 | 191 | const args = try process.argsAlloc(arena); |
| 194 | 192 | |
| 193 | if (args.len > 0) crash_report.zig_argv0 = args[0]; | |
| 194 | ||
| 195 | 195 | if (tracy.enable_allocation) { |
| 196 | 196 | var gpa_tracy = tracy.tracyAllocator(gpa); |
| 197 | 197 | return mainArgs(gpa_tracy.allocator(), arena, args); |
src/target.zig+1-1| ... | ... | @@ -512,7 +512,7 @@ pub fn defaultUnwindTables(target: *const std.Target, libunwind: bool, libtsan: |
| 512 | 512 | if (target.os.tag.isDarwin()) return .async; |
| 513 | 513 | if (libunwind) return .async; |
| 514 | 514 | if (libtsan) return .async; |
| 515 | if (std.debug.Dwarf.abi.supportsUnwinding(target)) return .async; | |
| 515 | if (std.debug.Dwarf.supportsUnwinding(target)) return .async; | |
| 516 | 516 | return .none; |
| 517 | 517 | } |
| 518 | 518 |
stage1/zig.h+8| ... | ... | @@ -1510,8 +1510,16 @@ static inline zig_u128 zig_shl_u128(zig_u128 lhs, uint8_t rhs) { |
| 1510 | 1510 | } |
| 1511 | 1511 | |
| 1512 | 1512 | static inline zig_i128 zig_shr_i128(zig_i128 lhs, uint8_t rhs) { |
| 1513 | // This works around a GCC miscompilation, but it has the side benefit of | |
| 1514 | // emitting better code. It is behind the `#if` because it depends on | |
| 1515 | // arithmetic right shift, which is implementation-defined in C, but should | |
| 1516 | // be guaranteed on any GCC-compatible compiler. | |
| 1517 | #if defined(zig_gnuc) | |
| 1518 | return lhs >> rhs; | |
| 1519 | #else | |
| 1513 | 1520 | zig_i128 sign_mask = lhs < zig_make_i128(0, 0) ? -zig_make_i128(0, 1) : zig_make_i128(0, 0); |
| 1514 | 1521 | return ((lhs ^ sign_mask) >> rhs) ^ sign_mask; |
| 1522 | #endif | |
| 1515 | 1523 | } |
| 1516 | 1524 | |
| 1517 | 1525 | static inline zig_i128 zig_shl_i128(zig_i128 lhs, uint8_t rhs) { |
test/cases/disable_stack_tracing.zig created+28| ... | ... | @@ -0,0 +1,28 @@ |
| 1 | pub const std_options: std.Options = .{ | |
| 2 | .allow_stack_tracing = false, | |
| 3 | }; | |
| 4 | ||
| 5 | pub fn main() !void { | |
| 6 | var st_buf: [8]usize = undefined; | |
| 7 | var buf: [1024]u8 = undefined; | |
| 8 | var stdout = std.fs.File.stdout().writer(&buf); | |
| 9 | ||
| 10 | const captured_st = try foo(&stdout.interface, &st_buf); | |
| 11 | try std.debug.writeStackTrace(&captured_st, &stdout.interface, .no_color); | |
| 12 | try stdout.interface.print("stack trace index: {d}\n", .{captured_st.index}); | |
| 13 | ||
| 14 | try stdout.interface.flush(); | |
| 15 | } | |
| 16 | fn foo(w: *std.Io.Writer, st_buf: []usize) !std.builtin.StackTrace { | |
| 17 | try std.debug.writeCurrentStackTrace(.{}, w, .no_color); | |
| 18 | return std.debug.captureCurrentStackTrace(.{}, st_buf); | |
| 19 | } | |
| 20 | ||
| 21 | const std = @import("std"); | |
| 22 | ||
| 23 | // run | |
| 24 | // | |
| 25 | // Cannot print stack trace: stack tracing is disabled | |
| 26 | // Cannot print stack trace: stack tracing is disabled | |
| 27 | // stack trace index: 0 | |
| 28 | // |
test/error_traces.zig created+443| ... | ... | @@ -0,0 +1,443 @@ |
| 1 | pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void { | |
| 2 | cases.addCase(.{ | |
| 3 | .name = "return", | |
| 4 | .source = | |
| 5 | \\pub fn main() !void { | |
| 6 | \\ return error.TheSkyIsFalling; | |
| 7 | \\} | |
| 8 | , | |
| 9 | .expect_error = "TheSkyIsFalling", | |
| 10 | .expect_trace = | |
| 11 | \\source.zig:2:5: [address] in main | |
| 12 | \\ return error.TheSkyIsFalling; | |
| 13 | \\ ^ | |
| 14 | , | |
| 15 | }); | |
| 16 | ||
| 17 | cases.addCase(.{ | |
| 18 | .name = "try return", | |
| 19 | .source = | |
| 20 | \\fn foo() !void { | |
| 21 | \\ return error.TheSkyIsFalling; | |
| 22 | \\} | |
| 23 | \\ | |
| 24 | \\pub fn main() !void { | |
| 25 | \\ try foo(); | |
| 26 | \\} | |
| 27 | , | |
| 28 | .expect_error = "TheSkyIsFalling", | |
| 29 | .expect_trace = | |
| 30 | \\source.zig:2:5: [address] in foo | |
| 31 | \\ return error.TheSkyIsFalling; | |
| 32 | \\ ^ | |
| 33 | \\source.zig:6:5: [address] in main | |
| 34 | \\ try foo(); | |
| 35 | \\ ^ | |
| 36 | , | |
| 37 | .disable_trace_optimized = &.{ | |
| 38 | .{ .x86_64, .windows }, | |
| 39 | .{ .x86, .windows }, | |
| 40 | .{ .x86_64, .macos }, | |
| 41 | .{ .aarch64, .macos }, | |
| 42 | }, | |
| 43 | }); | |
| 44 | cases.addCase(.{ | |
| 45 | .name = "non-error return pops error trace", | |
| 46 | .source = | |
| 47 | \\fn bar() !void { | |
| 48 | \\ return error.UhOh; | |
| 49 | \\} | |
| 50 | \\ | |
| 51 | \\fn foo() !void { | |
| 52 | \\ bar() catch { | |
| 53 | \\ return; // non-error result: success | |
| 54 | \\ }; | |
| 55 | \\} | |
| 56 | \\ | |
| 57 | \\pub fn main() !void { | |
| 58 | \\ try foo(); | |
| 59 | \\ return error.UnrelatedError; | |
| 60 | \\} | |
| 61 | , | |
| 62 | .expect_error = "UnrelatedError", | |
| 63 | .expect_trace = | |
| 64 | \\source.zig:13:5: [address] in main | |
| 65 | \\ return error.UnrelatedError; | |
| 66 | \\ ^ | |
| 67 | , | |
| 68 | }); | |
| 69 | ||
| 70 | cases.addCase(.{ | |
| 71 | .name = "continue in while loop", | |
| 72 | .source = | |
| 73 | \\fn foo() !void { | |
| 74 | \\ return error.UhOh; | |
| 75 | \\} | |
| 76 | \\ | |
| 77 | \\pub fn main() !void { | |
| 78 | \\ var i: usize = 0; | |
| 79 | \\ while (i < 3) : (i += 1) { | |
| 80 | \\ foo() catch continue; | |
| 81 | \\ } | |
| 82 | \\ return error.UnrelatedError; | |
| 83 | \\} | |
| 84 | , | |
| 85 | .expect_error = "UnrelatedError", | |
| 86 | .expect_trace = | |
| 87 | \\source.zig:10:5: [address] in main | |
| 88 | \\ return error.UnrelatedError; | |
| 89 | \\ ^ | |
| 90 | , | |
| 91 | }); | |
| 92 | ||
| 93 | cases.addCase(.{ | |
| 94 | .name = "try return + handled catch/if-else", | |
| 95 | .source = | |
| 96 | \\fn foo() !void { | |
| 97 | \\ return error.TheSkyIsFalling; | |
| 98 | \\} | |
| 99 | \\ | |
| 100 | \\pub fn main() !void { | |
| 101 | \\ foo() catch {}; // should not affect error trace | |
| 102 | \\ if (foo()) |_| {} else |_| { | |
| 103 | \\ // should also not affect error trace | |
| 104 | \\ } | |
| 105 | \\ try foo(); | |
| 106 | \\} | |
| 107 | , | |
| 108 | .expect_error = "TheSkyIsFalling", | |
| 109 | .expect_trace = | |
| 110 | \\source.zig:2:5: [address] in foo | |
| 111 | \\ return error.TheSkyIsFalling; | |
| 112 | \\ ^ | |
| 113 | \\source.zig:10:5: [address] in main | |
| 114 | \\ try foo(); | |
| 115 | \\ ^ | |
| 116 | , | |
| 117 | .disable_trace_optimized = &.{ | |
| 118 | .{ .x86_64, .windows }, | |
| 119 | .{ .x86, .windows }, | |
| 120 | .{ .x86_64, .macos }, | |
| 121 | .{ .aarch64, .macos }, | |
| 122 | }, | |
| 123 | }); | |
| 124 | ||
| 125 | cases.addCase(.{ | |
| 126 | .name = "break from inline loop pops error return trace", | |
| 127 | .source = | |
| 128 | \\fn foo() !void { return error.FooBar; } | |
| 129 | \\ | |
| 130 | \\pub fn main() !void { | |
| 131 | \\ comptime var i: usize = 0; | |
| 132 | \\ b: inline while (i < 5) : (i += 1) { | |
| 133 | \\ foo() catch { | |
| 134 | \\ break :b; // non-error break, success | |
| 135 | \\ }; | |
| 136 | \\ } | |
| 137 | \\ // foo() was successfully handled, should not appear in trace | |
| 138 | \\ | |
| 139 | \\ return error.BadTime; | |
| 140 | \\} | |
| 141 | , | |
| 142 | .expect_error = "BadTime", | |
| 143 | .expect_trace = | |
| 144 | \\source.zig:12:5: [address] in main | |
| 145 | \\ return error.BadTime; | |
| 146 | \\ ^ | |
| 147 | , | |
| 148 | }); | |
| 149 | ||
| 150 | cases.addCase(.{ | |
| 151 | .name = "catch and re-throw error", | |
| 152 | .source = | |
| 153 | \\fn foo() !void { | |
| 154 | \\ return error.TheSkyIsFalling; | |
| 155 | \\} | |
| 156 | \\ | |
| 157 | \\pub fn main() !void { | |
| 158 | \\ return foo() catch error.AndMyCarIsOutOfGas; | |
| 159 | \\} | |
| 160 | , | |
| 161 | .expect_error = "AndMyCarIsOutOfGas", | |
| 162 | .expect_trace = | |
| 163 | \\source.zig:2:5: [address] in foo | |
| 164 | \\ return error.TheSkyIsFalling; | |
| 165 | \\ ^ | |
| 166 | \\source.zig:6:5: [address] in main | |
| 167 | \\ return foo() catch error.AndMyCarIsOutOfGas; | |
| 168 | \\ ^ | |
| 169 | , | |
| 170 | .disable_trace_optimized = &.{ | |
| 171 | .{ .x86_64, .windows }, | |
| 172 | .{ .x86, .windows }, | |
| 173 | .{ .x86_64, .macos }, | |
| 174 | .{ .aarch64, .macos }, | |
| 175 | }, | |
| 176 | }); | |
| 177 | ||
| 178 | cases.addCase(.{ | |
| 179 | .name = "errors stored in var do not contribute to error trace", | |
| 180 | .source = | |
| 181 | \\fn foo() !void { | |
| 182 | \\ return error.TheSkyIsFalling; | |
| 183 | \\} | |
| 184 | \\ | |
| 185 | \\pub fn main() !void { | |
| 186 | \\ // Once an error is stored in a variable, it is popped from the trace | |
| 187 | \\ var x = foo(); | |
| 188 | \\ x = {}; | |
| 189 | \\ | |
| 190 | \\ // As a result, this error trace will still be clean | |
| 191 | \\ return error.SomethingUnrelatedWentWrong; | |
| 192 | \\} | |
| 193 | , | |
| 194 | .expect_error = "SomethingUnrelatedWentWrong", | |
| 195 | .expect_trace = | |
| 196 | \\source.zig:11:5: [address] in main | |
| 197 | \\ return error.SomethingUnrelatedWentWrong; | |
| 198 | \\ ^ | |
| 199 | , | |
| 200 | }); | |
| 201 | ||
| 202 | cases.addCase(.{ | |
| 203 | .name = "error stored in const has trace preserved for duration of block", | |
| 204 | .source = | |
| 205 | \\fn foo() !void { return error.TheSkyIsFalling; } | |
| 206 | \\fn bar() !void { return error.InternalError; } | |
| 207 | \\fn baz() !void { return error.UnexpectedReality; } | |
| 208 | \\ | |
| 209 | \\pub fn main() !void { | |
| 210 | \\ const x = foo(); | |
| 211 | \\ const y = b: { | |
| 212 | \\ if (true) | |
| 213 | \\ break :b bar(); | |
| 214 | \\ | |
| 215 | \\ break :b {}; | |
| 216 | \\ }; | |
| 217 | \\ x catch {}; | |
| 218 | \\ y catch {}; | |
| 219 | \\ // foo()/bar() error traces not popped until end of block | |
| 220 | \\ | |
| 221 | \\ { | |
| 222 | \\ const z = baz(); | |
| 223 | \\ z catch {}; | |
| 224 | \\ // baz() error trace still alive here | |
| 225 | \\ } | |
| 226 | \\ // baz() error trace popped, foo(), bar() still alive | |
| 227 | \\ return error.StillUnresolved; | |
| 228 | \\} | |
| 229 | , | |
| 230 | .expect_error = "StillUnresolved", | |
| 231 | .expect_trace = | |
| 232 | \\source.zig:1:18: [address] in foo | |
| 233 | \\fn foo() !void { return error.TheSkyIsFalling; } | |
| 234 | \\ ^ | |
| 235 | \\source.zig:2:18: [address] in bar | |
| 236 | \\fn bar() !void { return error.InternalError; } | |
| 237 | \\ ^ | |
| 238 | \\source.zig:23:5: [address] in main | |
| 239 | \\ return error.StillUnresolved; | |
| 240 | \\ ^ | |
| 241 | , | |
| 242 | .disable_trace_optimized = &.{ | |
| 243 | .{ .x86_64, .windows }, | |
| 244 | .{ .x86, .windows }, | |
| 245 | .{ .x86_64, .macos }, | |
| 246 | .{ .aarch64, .macos }, | |
| 247 | }, | |
| 248 | }); | |
| 249 | ||
| 250 | cases.addCase(.{ | |
| 251 | .name = "error passed to function has its trace preserved for duration of the call", | |
| 252 | .source = | |
| 253 | \\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void { | |
| 254 | \\ actual_error catch |err| { | |
| 255 | \\ if (err == expected_error) return {}; | |
| 256 | \\ }; | |
| 257 | \\ return error.TestExpectedError; | |
| 258 | \\} | |
| 259 | \\ | |
| 260 | \\fn alwaysErrors() !void { return error.ThisErrorShouldNotAppearInAnyTrace; } | |
| 261 | \\fn foo() !void { return error.Foo; } | |
| 262 | \\ | |
| 263 | \\pub fn main() !void { | |
| 264 | \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors()); | |
| 265 | \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors()); | |
| 266 | \\ try expectError(error.Foo, foo()); | |
| 267 | \\ | |
| 268 | \\ // Only the error trace for this failing check should appear: | |
| 269 | \\ try expectError(error.Bar, foo()); | |
| 270 | \\} | |
| 271 | , | |
| 272 | .expect_error = "TestExpectedError", | |
| 273 | .expect_trace = | |
| 274 | \\source.zig:9:18: [address] in foo | |
| 275 | \\fn foo() !void { return error.Foo; } | |
| 276 | \\ ^ | |
| 277 | \\source.zig:5:5: [address] in expectError | |
| 278 | \\ return error.TestExpectedError; | |
| 279 | \\ ^ | |
| 280 | \\source.zig:17:5: [address] in main | |
| 281 | \\ try expectError(error.Bar, foo()); | |
| 282 | \\ ^ | |
| 283 | , | |
| 284 | .disable_trace_optimized = &.{ | |
| 285 | .{ .x86_64, .windows }, | |
| 286 | .{ .x86, .windows }, | |
| 287 | .{ .x86_64, .macos }, | |
| 288 | .{ .aarch64, .macos }, | |
| 289 | }, | |
| 290 | }); | |
| 291 | ||
| 292 | cases.addCase(.{ | |
| 293 | .name = "try return from within catch", | |
| 294 | .source = | |
| 295 | \\fn foo() !void { | |
| 296 | \\ return error.TheSkyIsFalling; | |
| 297 | \\} | |
| 298 | \\ | |
| 299 | \\fn bar() !void { | |
| 300 | \\ return error.AndMyCarIsOutOfGas; | |
| 301 | \\} | |
| 302 | \\ | |
| 303 | \\pub fn main() !void { | |
| 304 | \\ foo() catch { // error trace should include foo() | |
| 305 | \\ try bar(); | |
| 306 | \\ }; | |
| 307 | \\} | |
| 308 | , | |
| 309 | .expect_error = "AndMyCarIsOutOfGas", | |
| 310 | .expect_trace = | |
| 311 | \\source.zig:2:5: [address] in foo | |
| 312 | \\ return error.TheSkyIsFalling; | |
| 313 | \\ ^ | |
| 314 | \\source.zig:6:5: [address] in bar | |
| 315 | \\ return error.AndMyCarIsOutOfGas; | |
| 316 | \\ ^ | |
| 317 | \\source.zig:11:9: [address] in main | |
| 318 | \\ try bar(); | |
| 319 | \\ ^ | |
| 320 | , | |
| 321 | .disable_trace_optimized = &.{ | |
| 322 | .{ .x86_64, .windows }, | |
| 323 | .{ .x86, .windows }, | |
| 324 | .{ .x86_64, .macos }, | |
| 325 | .{ .aarch64, .macos }, | |
| 326 | }, | |
| 327 | }); | |
| 328 | ||
| 329 | cases.addCase(.{ | |
| 330 | .name = "try return from within if-else", | |
| 331 | .source = | |
| 332 | \\fn foo() !void { | |
| 333 | \\ return error.TheSkyIsFalling; | |
| 334 | \\} | |
| 335 | \\ | |
| 336 | \\fn bar() !void { | |
| 337 | \\ return error.AndMyCarIsOutOfGas; | |
| 338 | \\} | |
| 339 | \\ | |
| 340 | \\pub fn main() !void { | |
| 341 | \\ if (foo()) |_| {} else |_| { // error trace should include foo() | |
| 342 | \\ try bar(); | |
| 343 | \\ } | |
| 344 | \\} | |
| 345 | , | |
| 346 | .expect_error = "AndMyCarIsOutOfGas", | |
| 347 | .expect_trace = | |
| 348 | \\source.zig:2:5: [address] in foo | |
| 349 | \\ return error.TheSkyIsFalling; | |
| 350 | \\ ^ | |
| 351 | \\source.zig:6:5: [address] in bar | |
| 352 | \\ return error.AndMyCarIsOutOfGas; | |
| 353 | \\ ^ | |
| 354 | \\source.zig:11:9: [address] in main | |
| 355 | \\ try bar(); | |
| 356 | \\ ^ | |
| 357 | , | |
| 358 | .disable_trace_optimized = &.{ | |
| 359 | .{ .x86_64, .windows }, | |
| 360 | .{ .x86, .windows }, | |
| 361 | .{ .x86_64, .macos }, | |
| 362 | .{ .aarch64, .macos }, | |
| 363 | }, | |
| 364 | }); | |
| 365 | ||
| 366 | cases.addCase(.{ | |
| 367 | .name = "try try return return", | |
| 368 | .source = | |
| 369 | \\fn foo() !void { | |
| 370 | \\ try bar(); | |
| 371 | \\} | |
| 372 | \\ | |
| 373 | \\fn bar() !void { | |
| 374 | \\ return make_error(); | |
| 375 | \\} | |
| 376 | \\ | |
| 377 | \\fn make_error() !void { | |
| 378 | \\ return error.TheSkyIsFalling; | |
| 379 | \\} | |
| 380 | \\ | |
| 381 | \\pub fn main() !void { | |
| 382 | \\ try foo(); | |
| 383 | \\} | |
| 384 | , | |
| 385 | .expect_error = "TheSkyIsFalling", | |
| 386 | .expect_trace = | |
| 387 | \\source.zig:10:5: [address] in make_error | |
| 388 | \\ return error.TheSkyIsFalling; | |
| 389 | \\ ^ | |
| 390 | \\source.zig:6:5: [address] in bar | |
| 391 | \\ return make_error(); | |
| 392 | \\ ^ | |
| 393 | \\source.zig:2:5: [address] in foo | |
| 394 | \\ try bar(); | |
| 395 | \\ ^ | |
| 396 | \\source.zig:14:5: [address] in main | |
| 397 | \\ try foo(); | |
| 398 | \\ ^ | |
| 399 | , | |
| 400 | .disable_trace_optimized = &.{ | |
| 401 | .{ .x86_64, .windows }, | |
| 402 | .{ .x86, .windows }, | |
| 403 | .{ .x86_64, .macos }, | |
| 404 | .{ .aarch64, .macos }, | |
| 405 | }, | |
| 406 | }); | |
| 407 | ||
| 408 | cases.addCase(.{ | |
| 409 | .name = "error union switch with call operand", | |
| 410 | .source = | |
| 411 | \\pub fn main() !void { | |
| 412 | \\ try foo(); | |
| 413 | \\ return error.TheSkyIsFalling; | |
| 414 | \\} | |
| 415 | \\ | |
| 416 | \\noinline fn failure() error{ Fatal, NonFatal }!void { | |
| 417 | \\ return error.NonFatal; | |
| 418 | \\} | |
| 419 | \\ | |
| 420 | \\fn foo() error{Fatal}!void { | |
| 421 | \\ return failure() catch |err| switch (err) { | |
| 422 | \\ error.Fatal => return error.Fatal, | |
| 423 | \\ error.NonFatal => return, | |
| 424 | \\ }; | |
| 425 | \\} | |
| 426 | , | |
| 427 | .expect_error = "TheSkyIsFalling", | |
| 428 | .expect_trace = | |
| 429 | \\source.zig:3:5: [address] in main | |
| 430 | \\ return error.TheSkyIsFalling; | |
| 431 | \\ ^ | |
| 432 | , | |
| 433 | .disable_trace_optimized = &.{ | |
| 434 | .{ .x86_64, .linux }, | |
| 435 | .{ .x86, .linux }, | |
| 436 | .{ .aarch64, .linux }, | |
| 437 | .{ .x86_64, .windows }, | |
| 438 | .{ .x86, .windows }, | |
| 439 | .{ .x86_64, .macos }, | |
| 440 | .{ .aarch64, .macos }, | |
| 441 | }, | |
| 442 | }); | |
| 443 | } |
test/src/ErrorTrace.zig created+130| ... | ... | @@ -0,0 +1,130 @@ |
| 1 | b: *std.Build, | |
| 2 | step: *Step, | |
| 3 | test_filters: []const []const u8, | |
| 4 | targets: []const std.Build.ResolvedTarget, | |
| 5 | optimize_modes: []const OptimizeMode, | |
| 6 | convert_exe: *std.Build.Step.Compile, | |
| 7 | ||
| 8 | pub const Case = struct { | |
| 9 | name: []const u8, | |
| 10 | source: []const u8, | |
| 11 | expect_error: []const u8, | |
| 12 | expect_trace: []const u8, | |
| 13 | /// On these arch/OS pairs we will not test the error trace on optimized LLVM builds because the | |
| 14 | /// optimizations break the error trace. We will test the binary with error tracing disabled, | |
| 15 | /// just to ensure that the expected error is still returned from `main`. | |
| 16 | /// | |
| 17 | /// LLVM ReleaseSmall builds always have the trace disabled regardless of this field, because it | |
| 18 | /// seems that LLVM is particularly good at optimizing traces away in those. | |
| 19 | disable_trace_optimized: []const DisableConfig = &.{}, | |
| 20 | ||
| 21 | pub const DisableConfig = struct { std.Target.Cpu.Arch, std.Target.Os.Tag }; | |
| 22 | pub const Backend = enum { llvm, selfhosted }; | |
| 23 | }; | |
| 24 | ||
| 25 | pub fn addCase(self: *ErrorTrace, case: Case) void { | |
| 26 | for (self.targets) |*target| { | |
| 27 | const triple: ?[]const u8 = if (target.query.isNative()) null else t: { | |
| 28 | break :t target.query.zigTriple(self.b.graph.arena) catch @panic("OOM"); | |
| 29 | }; | |
| 30 | for (self.optimize_modes) |optimize| { | |
| 31 | self.addCaseConfig(case, target, triple, optimize, .llvm); | |
| 32 | } | |
| 33 | if (shouldTestNonLlvm(&target.result)) { | |
| 34 | for (self.optimize_modes) |optimize| { | |
| 35 | self.addCaseConfig(case, target, triple, optimize, .selfhosted); | |
| 36 | } | |
| 37 | } | |
| 38 | } | |
| 39 | } | |
| 40 | ||
| 41 | fn shouldTestNonLlvm(target: *const std.Target) bool { | |
| 42 | return switch (target.cpu.arch) { | |
| 43 | .x86_64 => switch (target.ofmt) { | |
| 44 | .elf => true, | |
| 45 | else => false, | |
| 46 | }, | |
| 47 | else => false, | |
| 48 | }; | |
| 49 | } | |
| 50 | ||
| 51 | fn addCaseConfig( | |
| 52 | self: *ErrorTrace, | |
| 53 | case: Case, | |
| 54 | target: *const std.Build.ResolvedTarget, | |
| 55 | triple: ?[]const u8, | |
| 56 | optimize: OptimizeMode, | |
| 57 | backend: Case.Backend, | |
| 58 | ) void { | |
| 59 | const b = self.b; | |
| 60 | ||
| 61 | const error_tracing: bool = tracing: { | |
| 62 | if (optimize == .Debug) break :tracing true; | |
| 63 | if (backend != .llvm) break :tracing true; | |
| 64 | if (optimize == .ReleaseSmall) break :tracing false; | |
| 65 | for (case.disable_trace_optimized) |disable| { | |
| 66 | const d_arch, const d_os = disable; | |
| 67 | if (target.result.cpu.arch == d_arch and target.result.os.tag == d_os) { | |
| 68 | // This particular configuration cannot do error tracing in optimized LLVM builds. | |
| 69 | break :tracing false; | |
| 70 | } | |
| 71 | } | |
| 72 | break :tracing true; | |
| 73 | }; | |
| 74 | ||
| 75 | const annotated_case_name = b.fmt("check {s} ({s}{s}{s} {s})", .{ | |
| 76 | case.name, | |
| 77 | triple orelse "", | |
| 78 | if (triple != null) " " else "", | |
| 79 | @tagName(optimize), | |
| 80 | @tagName(backend), | |
| 81 | }); | |
| 82 | if (self.test_filters.len > 0) { | |
| 83 | for (self.test_filters) |test_filter| { | |
| 84 | if (mem.indexOf(u8, annotated_case_name, test_filter)) |_| break; | |
| 85 | } else return; | |
| 86 | } | |
| 87 | ||
| 88 | const write_files = b.addWriteFiles(); | |
| 89 | const source_zig = write_files.add("source.zig", case.source); | |
| 90 | const exe = b.addExecutable(.{ | |
| 91 | .name = "test", | |
| 92 | .root_module = b.createModule(.{ | |
| 93 | .root_source_file = source_zig, | |
| 94 | .optimize = optimize, | |
| 95 | .target = target.*, | |
| 96 | .error_tracing = error_tracing, | |
| 97 | .strip = false, | |
| 98 | }), | |
| 99 | .use_llvm = switch (backend) { | |
| 100 | .llvm => true, | |
| 101 | .selfhosted => false, | |
| 102 | }, | |
| 103 | }); | |
| 104 | exe.bundle_ubsan_rt = false; | |
| 105 | ||
| 106 | const run = b.addRunArtifact(exe); | |
| 107 | run.removeEnvironmentVariable("CLICOLOR_FORCE"); | |
| 108 | run.setEnvironmentVariable("NO_COLOR", "1"); | |
| 109 | run.expectExitCode(1); | |
| 110 | run.expectStdOutEqual(""); | |
| 111 | ||
| 112 | const expected_stderr = switch (error_tracing) { | |
| 113 | true => b.fmt("error: {s}\n{s}\n", .{ case.expect_error, case.expect_trace }), | |
| 114 | false => b.fmt("error: {s}\n", .{case.expect_error}), | |
| 115 | }; | |
| 116 | ||
| 117 | const check_run = b.addRunArtifact(self.convert_exe); | |
| 118 | check_run.setName(annotated_case_name); | |
| 119 | check_run.addFileArg(run.captureStdErr(.{})); | |
| 120 | check_run.expectStdOutEqual(expected_stderr); | |
| 121 | ||
| 122 | self.step.dependOn(&check_run.step); | |
| 123 | } | |
| 124 | ||
| 125 | const ErrorTrace = @This(); | |
| 126 | const std = @import("std"); | |
| 127 | const builtin = @import("builtin"); | |
| 128 | const Step = std.Build.Step; | |
| 129 | const OptimizeMode = std.builtin.OptimizeMode; | |
| 130 | const mem = std.mem; |
test/src/StackTrace.zig+159-56| ... | ... | @@ -1,75 +1,171 @@ |
| 1 | 1 | b: *std.Build, |
| 2 | 2 | step: *Step, |
| 3 | test_index: usize, | |
| 4 | 3 | test_filters: []const []const u8, |
| 5 | optimize_modes: []const OptimizeMode, | |
| 6 | check_exe: *std.Build.Step.Compile, | |
| 4 | targets: []const std.Build.ResolvedTarget, | |
| 5 | convert_exe: *std.Build.Step.Compile, | |
| 7 | 6 | |
| 8 | 7 | const Config = struct { |
| 9 | 8 | name: []const u8, |
| 10 | 9 | source: []const u8, |
| 11 | Debug: ?PerMode = null, | |
| 12 | ReleaseSmall: ?PerMode = null, | |
| 13 | ReleaseSafe: ?PerMode = null, | |
| 14 | ReleaseFast: ?PerMode = null, | |
| 15 | ||
| 16 | const PerMode = struct { | |
| 17 | expect: []const u8, | |
| 18 | exclude_arch: []const std.Target.Cpu.Arch = &.{}, | |
| 19 | exclude_os: []const std.Target.Os.Tag = &.{}, | |
| 20 | error_tracing: ?bool = null, | |
| 21 | }; | |
| 10 | /// Whether this test case expects to have unwind tables / frame pointers. | |
| 11 | unwind: enum { | |
| 12 | /// This case assumes that some unwind strategy, safe or unsafe, is available. | |
| 13 | any, | |
| 14 | /// This case assumes that no unwinding strategy is available. | |
| 15 | none, | |
| 16 | /// This case assumes that a safe unwind strategy, like DWARF unwinding, is available. | |
| 17 | safe, | |
| 18 | /// This case assumes that at most, unsafe FP unwinding is available. | |
| 19 | no_safe, | |
| 20 | }, | |
| 21 | /// If `true`, the expected exit code is that of the default panic handler, rather than 0. | |
| 22 | expect_panic: bool, | |
| 23 | /// When debug info is not stripped, stdout is expected to **contain** (not equal!) this string. | |
| 24 | expect: []const u8, | |
| 25 | /// When debug info *is* stripped, stdout is expected to **contain** (not equal!) this string. | |
| 26 | expect_strip: []const u8, | |
| 22 | 27 | }; |
| 23 | 28 | |
| 24 | 29 | pub fn addCase(self: *StackTrace, config: Config) void { |
| 25 | self.addCaseInner(config, true); | |
| 26 | if (shouldTestNonLlvm(&self.b.graph.host.result)) { | |
| 27 | self.addCaseInner(config, false); | |
| 30 | for (self.targets) |*target| { | |
| 31 | addCaseTarget( | |
| 32 | self, | |
| 33 | config, | |
| 34 | target, | |
| 35 | if (target.query.isNative()) null else t: { | |
| 36 | break :t target.query.zigTriple(self.b.graph.arena) catch @panic("OOM"); | |
| 37 | }, | |
| 38 | ); | |
| 28 | 39 | } |
| 29 | 40 | } |
| 30 | ||
| 31 | fn addCaseInner(self: *StackTrace, config: Config, use_llvm: bool) void { | |
| 32 | if (config.Debug) |per_mode| | |
| 33 | self.addExpect(config.name, config.source, .Debug, use_llvm, per_mode); | |
| 34 | ||
| 35 | if (config.ReleaseSmall) |per_mode| | |
| 36 | self.addExpect(config.name, config.source, .ReleaseSmall, use_llvm, per_mode); | |
| 37 | ||
| 38 | if (config.ReleaseFast) |per_mode| | |
| 39 | self.addExpect(config.name, config.source, .ReleaseFast, use_llvm, per_mode); | |
| 40 | ||
| 41 | if (config.ReleaseSafe) |per_mode| | |
| 42 | self.addExpect(config.name, config.source, .ReleaseSafe, use_llvm, per_mode); | |
| 43 | } | |
| 44 | ||
| 45 | fn shouldTestNonLlvm(target: *const std.Target) bool { | |
| 46 | return switch (target.cpu.arch) { | |
| 47 | .x86_64 => switch (target.ofmt) { | |
| 48 | .elf => !target.os.tag.isBSD(), | |
| 41 | fn addCaseTarget( | |
| 42 | self: *StackTrace, | |
| 43 | config: Config, | |
| 44 | target: *const std.Build.ResolvedTarget, | |
| 45 | triple: ?[]const u8, | |
| 46 | ) void { | |
| 47 | const both_backends = switch (target.result.cpu.arch) { | |
| 48 | .x86_64 => switch (target.result.ofmt) { | |
| 49 | .elf => true, | |
| 49 | 50 | else => false, |
| 50 | 51 | }, |
| 51 | 52 | else => false, |
| 52 | 53 | }; |
| 54 | const both_pie = switch (target.result.os.tag) { | |
| 55 | .fuchsia, .openbsd => false, | |
| 56 | else => true, | |
| 57 | }; | |
| 58 | const both_libc = switch (target.result.os.tag) { | |
| 59 | .freebsd, .netbsd => false, | |
| 60 | else => !target.result.requiresLibC(), | |
| 61 | }; | |
| 62 | ||
| 63 | // On aarch64-macos, FP unwinding is blessed by Apple to always be reliable, and std.debug knows this. | |
| 64 | const fp_unwind_is_safe = target.result.cpu.arch == .aarch64 and target.result.os.tag.isDarwin(); | |
| 65 | const supports_unwind_tables = switch (target.result.os.tag) { | |
| 66 | // x86-windows just has no way to do stack unwinding other then using frame pointers. | |
| 67 | .windows => target.result.cpu.arch != .x86, | |
| 68 | // We do not yet implement support for the AArch32 exception table section `.ARM.exidx`. | |
| 69 | else => !target.result.cpu.arch.isArm(), | |
| 70 | }; | |
| 71 | ||
| 72 | const use_llvm_vals: []const bool = if (both_backends) &.{ true, false } else &.{true}; | |
| 73 | const pie_vals: []const ?bool = if (both_pie) &.{ true, false } else &.{null}; | |
| 74 | const link_libc_vals: []const ?bool = if (both_libc) &.{ true, false } else &.{null}; | |
| 75 | const strip_debug_vals: []const bool = &.{ true, false }; | |
| 76 | ||
| 77 | const UnwindInfo = packed struct(u2) { | |
| 78 | tables: bool, | |
| 79 | fp: bool, | |
| 80 | const none: @This() = .{ .tables = false, .fp = false }; | |
| 81 | const both: @This() = .{ .tables = true, .fp = true }; | |
| 82 | const only_tables: @This() = .{ .tables = true, .fp = false }; | |
| 83 | const only_fp: @This() = .{ .tables = false, .fp = true }; | |
| 84 | }; | |
| 85 | const unwind_info_vals: []const UnwindInfo = switch (config.unwind) { | |
| 86 | .none => &.{.none}, | |
| 87 | .any => &.{ .only_tables, .only_fp, .both }, | |
| 88 | .safe => if (fp_unwind_is_safe) &.{ .only_tables, .only_fp, .both } else &.{ .only_tables, .both }, | |
| 89 | .no_safe => if (fp_unwind_is_safe) &.{.none} else &.{ .none, .only_fp }, | |
| 90 | }; | |
| 91 | ||
| 92 | for (use_llvm_vals) |use_llvm| { | |
| 93 | for (pie_vals) |pie| { | |
| 94 | for (link_libc_vals) |link_libc| { | |
| 95 | for (strip_debug_vals) |strip_debug| { | |
| 96 | for (unwind_info_vals) |unwind_info| { | |
| 97 | if (unwind_info.tables and !supports_unwind_tables) continue; | |
| 98 | self.addCaseInstance( | |
| 99 | target, | |
| 100 | triple, | |
| 101 | config.name, | |
| 102 | config.source, | |
| 103 | use_llvm, | |
| 104 | pie, | |
| 105 | link_libc, | |
| 106 | strip_debug, | |
| 107 | !unwind_info.tables and supports_unwind_tables, | |
| 108 | !unwind_info.fp, | |
| 109 | config.expect_panic, | |
| 110 | if (strip_debug) config.expect_strip else config.expect, | |
| 111 | ); | |
| 112 | } | |
| 113 | } | |
| 114 | } | |
| 115 | } | |
| 116 | } | |
| 53 | 117 | } |
| 54 | 118 | |
| 55 | fn addExpect( | |
| 119 | fn addCaseInstance( | |
| 56 | 120 | self: *StackTrace, |
| 121 | target: *const std.Build.ResolvedTarget, | |
| 122 | triple: ?[]const u8, | |
| 57 | 123 | name: []const u8, |
| 58 | 124 | source: []const u8, |
| 59 | optimize_mode: OptimizeMode, | |
| 60 | 125 | use_llvm: bool, |
| 61 | mode_config: Config.PerMode, | |
| 126 | pie: ?bool, | |
| 127 | link_libc: ?bool, | |
| 128 | strip_debug: bool, | |
| 129 | strip_unwind: bool, | |
| 130 | omit_frame_pointer: bool, | |
| 131 | expect_panic: bool, | |
| 132 | expect_stderr: []const u8, | |
| 62 | 133 | ) void { |
| 63 | for (mode_config.exclude_arch) |tag| if (tag == builtin.cpu.arch) return; | |
| 64 | for (mode_config.exclude_os) |tag| if (tag == builtin.os.tag) return; | |
| 65 | ||
| 66 | 134 | const b = self.b; |
| 67 | const annotated_case_name = b.fmt("check {s} ({s} {s})", .{ | |
| 68 | name, @tagName(optimize_mode), if (use_llvm) "llvm" else "selfhosted", | |
| 135 | ||
| 136 | if (strip_debug) { | |
| 137 | // To enable this coverage, one of two things needs to happen: | |
| 138 | // * The compiler needs to gain the ability to strip only debug info (not symbols) | |
| 139 | // * `std.Build.Step.ObjCopy` needs to be un-regressed | |
| 140 | return; | |
| 141 | } | |
| 142 | ||
| 143 | if (strip_unwind) { | |
| 144 | // To enable this coverage, `std.Build.Step.ObjCopy` needs to be un-regressed and gain the | |
| 145 | // ability to remove individual sections. `-fno-unwind-tables` is insufficient because it | |
| 146 | // does not prevent `.debug_frame` from being emitted. If we could, we would remove the | |
| 147 | // following sections: | |
| 148 | // * `.eh_frame`, `.eh_frame_hdr`, `.debug_frame` (Linux) | |
| 149 | // * `__TEXT,__eh_frame`, `__TEXT,__unwind_info` (macOS) | |
| 150 | return; | |
| 151 | } | |
| 152 | ||
| 153 | const annotated_case_name = b.fmt("check {s} ({s}{s}{s}{s}{s}{s}{s}{s})", .{ | |
| 154 | name, | |
| 155 | triple orelse "", | |
| 156 | if (triple != null) " " else "", | |
| 157 | if (use_llvm) "llvm" else "selfhosted", | |
| 158 | if (pie == true) " pie" else "", | |
| 159 | if (link_libc == true) " libc" else "", | |
| 160 | if (strip_debug) " strip" else "", | |
| 161 | if (strip_unwind) " no_unwind" else "", | |
| 162 | if (omit_frame_pointer) " no_fp" else "", | |
| 69 | 163 | }); |
| 70 | for (self.test_filters) |test_filter| { | |
| 71 | if (mem.indexOf(u8, annotated_case_name, test_filter)) |_| break; | |
| 72 | } else if (self.test_filters.len > 0) return; | |
| 164 | if (self.test_filters.len > 0) { | |
| 165 | for (self.test_filters) |test_filter| { | |
| 166 | if (mem.indexOf(u8, annotated_case_name, test_filter)) |_| break; | |
| 167 | } else return; | |
| 168 | } | |
| 73 | 169 | |
| 74 | 170 | const write_files = b.addWriteFiles(); |
| 75 | 171 | const source_zig = write_files.add("source.zig", source); |
| ... | ... | @@ -77,27 +173,34 @@ fn addExpect( |
| 77 | 173 | .name = "test", |
| 78 | 174 | .root_module = b.createModule(.{ |
| 79 | 175 | .root_source_file = source_zig, |
| 80 | .optimize = optimize_mode, | |
| 81 | .target = b.graph.host, | |
| 82 | .error_tracing = mode_config.error_tracing, | |
| 176 | .optimize = .Debug, | |
| 177 | .target = target.*, | |
| 178 | .omit_frame_pointer = omit_frame_pointer, | |
| 179 | .link_libc = link_libc, | |
| 180 | .unwind_tables = if (strip_unwind) .none else null, | |
| 181 | // make panics single-threaded so that they don't include a thread ID | |
| 182 | .single_threaded = expect_panic, | |
| 83 | 183 | }), |
| 84 | 184 | .use_llvm = use_llvm, |
| 85 | 185 | }); |
| 186 | exe.pie = pie; | |
| 86 | 187 | exe.bundle_ubsan_rt = false; |
| 87 | 188 | |
| 88 | 189 | const run = b.addRunArtifact(exe); |
| 89 | 190 | run.removeEnvironmentVariable("CLICOLOR_FORCE"); |
| 90 | 191 | run.setEnvironmentVariable("NO_COLOR", "1"); |
| 91 | run.expectExitCode(1); | |
| 192 | run.addCheck(.{ .expect_term = term: { | |
| 193 | if (!expect_panic) break :term .{ .Exited = 0 }; | |
| 194 | if (target.result.os.tag == .windows) break :term .{ .Exited = 3 }; | |
| 195 | break :term .{ .Signal = 6 }; | |
| 196 | } }); | |
| 92 | 197 | run.expectStdOutEqual(""); |
| 93 | 198 | |
| 94 | const check_run = b.addRunArtifact(self.check_exe); | |
| 199 | const check_run = b.addRunArtifact(self.convert_exe); | |
| 95 | 200 | check_run.setName(annotated_case_name); |
| 96 | 201 | check_run.addFileArg(run.captureStdErr(.{})); |
| 97 | check_run.addArgs(&.{ | |
| 98 | @tagName(optimize_mode), | |
| 99 | }); | |
| 100 | check_run.expectStdOutEqual(mode_config.expect); | |
| 202 | check_run.expectExitCode(0); | |
| 203 | check_run.addCheck(.{ .expect_stdout_match = expect_stderr }); | |
| 101 | 204 | |
| 102 | 205 | self.step.dependOn(&check_run.step); |
| 103 | 206 | } |
test/src/check-stack-trace.zig deleted-88| ... | ... | @@ -1,88 +0,0 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const std = @import("std"); | |
| 3 | const mem = std.mem; | |
| 4 | const fs = std.fs; | |
| 5 | ||
| 6 | pub fn main() !void { | |
| 7 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
| 8 | defer arena_instance.deinit(); | |
| 9 | const arena = arena_instance.allocator(); | |
| 10 | ||
| 11 | const args = try std.process.argsAlloc(arena); | |
| 12 | ||
| 13 | const input_path = args[1]; | |
| 14 | const optimize_mode_text = args[2]; | |
| 15 | ||
| 16 | const input_bytes = try std.fs.cwd().readFileAlloc(input_path, arena, .limited(5 * 1024 * 1024)); | |
| 17 | const optimize_mode = std.meta.stringToEnum(std.builtin.OptimizeMode, optimize_mode_text).?; | |
| 18 | ||
| 19 | var stderr = input_bytes; | |
| 20 | ||
| 21 | // process result | |
| 22 | // - keep only basename of source file path | |
| 23 | // - replace address with symbolic string | |
| 24 | // - replace function name with symbolic string when optimize_mode != .Debug | |
| 25 | // - skip empty lines | |
| 26 | const got: []const u8 = got_result: { | |
| 27 | var buf = std.array_list.Managed(u8).init(arena); | |
| 28 | defer buf.deinit(); | |
| 29 | if (stderr.len != 0 and stderr[stderr.len - 1] == '\n') stderr = stderr[0 .. stderr.len - 1]; | |
| 30 | var it = mem.splitScalar(u8, stderr, '\n'); | |
| 31 | process_lines: while (it.next()) |line| { | |
| 32 | if (line.len == 0) continue; | |
| 33 | ||
| 34 | // offset search past `[drive]:` on windows | |
| 35 | var pos: usize = if (builtin.os.tag == .windows) 2 else 0; | |
| 36 | // locate delims/anchor | |
| 37 | const delims = [_][]const u8{ ":", ":", ":", " in ", "(", ")" }; | |
| 38 | var marks = [_]usize{0} ** delims.len; | |
| 39 | for (delims, 0..) |delim, i| { | |
| 40 | marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse { | |
| 41 | // unexpected pattern: emit raw line and cont | |
| 42 | try buf.appendSlice(line); | |
| 43 | try buf.appendSlice("\n"); | |
| 44 | continue :process_lines; | |
| 45 | }; | |
| 46 | pos = marks[i] + delim.len; | |
| 47 | } | |
| 48 | // locate source basename | |
| 49 | pos = mem.lastIndexOfScalar(u8, line[0..marks[0]], fs.path.sep) orelse { | |
| 50 | // unexpected pattern: emit raw line and cont | |
| 51 | try buf.appendSlice(line); | |
| 52 | try buf.appendSlice("\n"); | |
| 53 | continue :process_lines; | |
| 54 | }; | |
| 55 | // end processing if source basename changes | |
| 56 | if (!mem.eql(u8, "source.zig", line[pos + 1 .. marks[0]])) break; | |
| 57 | // emit substituted line | |
| 58 | try buf.appendSlice(line[pos + 1 .. marks[2] + delims[2].len]); | |
| 59 | try buf.appendSlice(" [address]"); | |
| 60 | if (optimize_mode == .Debug) { | |
| 61 | try buf.appendSlice(line[marks[3] .. marks[4] + delims[4].len]); | |
| 62 | ||
| 63 | const file_name = line[marks[4] + delims[4].len .. marks[5]]; | |
| 64 | // The LLVM backend currently uses the object file name in the debug info here. | |
| 65 | // This actually violates the DWARF specification (DWARF5 § 3.1.1, lines 24-27). | |
| 66 | // The self-hosted backend uses the root Zig source file of the module (in compilance with the spec). | |
| 67 | if (std.mem.eql(u8, file_name, "test") or | |
| 68 | std.mem.eql(u8, file_name, "test_zcu.obj") or | |
| 69 | std.mem.endsWith(u8, file_name, ".zig")) | |
| 70 | { | |
| 71 | try buf.appendSlice("[main_file]"); | |
| 72 | } else { | |
| 73 | // Something unexpected; include it verbatim. | |
| 74 | try buf.appendSlice(file_name); | |
| 75 | } | |
| 76 | ||
| 77 | try buf.appendSlice(line[marks[5]..]); | |
| 78 | } else { | |
| 79 | try buf.appendSlice(line[marks[3] .. marks[3] + delims[3].len]); | |
| 80 | try buf.appendSlice("[function]"); | |
| 81 | } | |
| 82 | try buf.appendSlice("\n"); | |
| 83 | } | |
| 84 | break :got_result try buf.toOwnedSlice(); | |
| 85 | }; | |
| 86 | ||
| 87 | try std.fs.File.stdout().writeAll(got); | |
| 88 | } |
test/src/convert-stack-trace.zig created+107| ... | ... | @@ -0,0 +1,107 @@ |
| 1 | //! Accepts a stack trace in a file (whose path is given as argv[1]), and removes all | |
| 2 | //! non-reproducible information from it, including addresses, module names, and file | |
| 3 | //! paths. All module names are removed, file paths become just their basename, and | |
| 4 | //! addresses are replaced with a fixed string. So, lines like this: | |
| 5 | //! | |
| 6 | //! /something/foo.zig:1:5: 0x12345678 in bar (main.o) | |
| 7 | //! doThing(); | |
| 8 | //! ^ | |
| 9 | //! ???:?:?: 0x12345678 in qux (other.o) | |
| 10 | //! ???:?:?: 0x12345678 in ??? (???) | |
| 11 | //! | |
| 12 | //! ...are turned into lines like this: | |
| 13 | //! | |
| 14 | //! foo.zig:1:5: [address] in bar | |
| 15 | //! doThing(); | |
| 16 | //! ^ | |
| 17 | //! ???:?:?: [address] in qux | |
| 18 | //! ???:?:?: [address] in ??? | |
| 19 | //! | |
| 20 | //! Additionally, lines reporting unwind errors are removed: | |
| 21 | //! | |
| 22 | //! Unwind error at address `/proc/self/exe:0x1016533` (unwind info unavailable), remaining frames may be incorrect | |
| 23 | //! Cannot print stack trace: safe unwind unavilable for target | |
| 24 | //! | |
| 25 | //! With these transformations, the test harness can safely do string comparisons. | |
| 26 | ||
| 27 | pub fn main() !void { | |
| 28 | var arena_instance: std.heap.ArenaAllocator = .init(std.heap.page_allocator); | |
| 29 | defer arena_instance.deinit(); | |
| 30 | const arena = arena_instance.allocator(); | |
| 31 | ||
| 32 | const args = try std.process.argsAlloc(arena); | |
| 33 | if (args.len != 2) std.process.fatal("usage: convert-stack-trace path/to/test/output", .{}); | |
| 34 | ||
| 35 | var read_buf: [1024]u8 = undefined; | |
| 36 | var write_buf: [1024]u8 = undefined; | |
| 37 | ||
| 38 | const in_file = try std.fs.cwd().openFile(args[1], .{}); | |
| 39 | defer in_file.close(); | |
| 40 | ||
| 41 | const out_file: std.fs.File = .stdout(); | |
| 42 | ||
| 43 | var in_fr = in_file.reader(&read_buf); | |
| 44 | var out_fw = out_file.writer(&write_buf); | |
| 45 | ||
| 46 | const w = &out_fw.interface; | |
| 47 | ||
| 48 | while (in_fr.interface.takeDelimiterInclusive('\n')) |in_line| { | |
| 49 | if (std.mem.eql(u8, in_line, "Cannot print stack trace: safe unwind unavailable for target\n") or | |
| 50 | std.mem.startsWith(u8, in_line, "Unwind error at address `")) | |
| 51 | { | |
| 52 | // Remove these lines from the output. | |
| 53 | continue; | |
| 54 | } | |
| 55 | ||
| 56 | const src_col_end = std.mem.indexOf(u8, in_line, ": 0x") orelse { | |
| 57 | try w.writeAll(in_line); | |
| 58 | continue; | |
| 59 | }; | |
| 60 | const src_row_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_col_end], ':') orelse { | |
| 61 | try w.writeAll(in_line); | |
| 62 | continue; | |
| 63 | }; | |
| 64 | const src_path_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_row_end], ':') orelse { | |
| 65 | try w.writeAll(in_line); | |
| 66 | continue; | |
| 67 | }; | |
| 68 | ||
| 69 | const addr_end = std.mem.indexOfPos(u8, in_line, src_col_end, " in ") orelse { | |
| 70 | try w.writeAll(in_line); | |
| 71 | continue; | |
| 72 | }; | |
| 73 | const symbol_end = std.mem.indexOfPos(u8, in_line, addr_end, " (") orelse { | |
| 74 | try w.writeAll(in_line); | |
| 75 | continue; | |
| 76 | }; | |
| 77 | if (!std.mem.endsWith(u8, std.mem.trimEnd(u8, in_line, "\n"), ")")) { | |
| 78 | try w.writeAll(in_line); | |
| 79 | continue; | |
| 80 | } | |
| 81 | ||
| 82 | // Where '_' is a placeholder for an arbitrary string, we now know the line looks like: | |
| 83 | // | |
| 84 | // _:_:_: 0x_ in _ (_) | |
| 85 | // | |
| 86 | // That seems good enough to assume it's a stack trace frame! We'll rewrite it to: | |
| 87 | // | |
| 88 | // _:_:_: [address] in _ | |
| 89 | // | |
| 90 | // ...with that first '_' being replaced by its basename. | |
| 91 | ||
| 92 | const src_path = in_line[0..src_path_end]; | |
| 93 | const basename_start = if (std.mem.lastIndexOfAny(u8, src_path, "/\\")) |i| i + 1 else 0; | |
| 94 | const symbol_start = addr_end + " in ".len; | |
| 95 | try w.writeAll(in_line[basename_start..src_col_end]); | |
| 96 | try w.writeAll(": [address] in "); | |
| 97 | try w.writeAll(in_line[symbol_start..symbol_end]); | |
| 98 | try w.writeByte('\n'); | |
| 99 | } else |err| switch (err) { | |
| 100 | error.EndOfStream => {}, | |
| 101 | else => |e| return e, | |
| 102 | } | |
| 103 | ||
| 104 | try w.flush(); | |
| 105 | } | |
| 106 | ||
| 107 | const std = @import("std"); |
test/stack_traces.zig+153-807| ... | ... | @@ -1,878 +1,224 @@ |
| 1 | const std = @import("std"); | |
| 2 | const os = std.os; | |
| 3 | const tests = @import("tests.zig"); | |
| 4 | ||
| 5 | pub fn addCases(cases: *tests.StackTracesContext) void { | |
| 1 | pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void { | |
| 6 | 2 | cases.addCase(.{ |
| 7 | .name = "return", | |
| 3 | .name = "simple panic", | |
| 8 | 4 | .source = |
| 9 | \\pub fn main() !void { | |
| 10 | \\ return error.TheSkyIsFalling; | |
| 5 | \\pub fn main() void { | |
| 6 | \\ foo(); | |
| 11 | 7 | \\} |
| 12 | , | |
| 13 | .Debug = .{ | |
| 14 | .expect = | |
| 15 | \\error: TheSkyIsFalling | |
| 16 | \\source.zig:2:5: [address] in main ([main_file]) | |
| 17 | \\ return error.TheSkyIsFalling; | |
| 18 | \\ ^ | |
| 19 | \\ | |
| 20 | , | |
| 21 | }, | |
| 22 | .ReleaseSafe = .{ | |
| 23 | .exclude_os = &.{ | |
| 24 | .windows, // TODO | |
| 25 | .linux, // defeated by aggressive inlining | |
| 26 | }, | |
| 27 | .expect = | |
| 28 | \\error: TheSkyIsFalling | |
| 29 | \\source.zig:2:5: [address] in [function] | |
| 30 | \\ return error.TheSkyIsFalling; | |
| 31 | \\ ^ | |
| 32 | \\ | |
| 33 | , | |
| 34 | .error_tracing = true, | |
| 35 | }, | |
| 36 | .ReleaseFast = .{ | |
| 37 | .expect = | |
| 38 | \\error: TheSkyIsFalling | |
| 39 | \\ | |
| 40 | , | |
| 41 | }, | |
| 42 | .ReleaseSmall = .{ | |
| 43 | .expect = | |
| 44 | \\error: TheSkyIsFalling | |
| 45 | \\ | |
| 46 | , | |
| 47 | }, | |
| 48 | }); | |
| 49 | ||
| 50 | cases.addCase(.{ | |
| 51 | .name = "try return", | |
| 52 | .source = | |
| 53 | \\fn foo() !void { | |
| 54 | \\ return error.TheSkyIsFalling; | |
| 8 | \\fn foo() void { | |
| 9 | \\ @panic("oh no"); | |
| 55 | 10 | \\} |
| 56 | 11 | \\ |
| 57 | \\pub fn main() !void { | |
| 58 | \\ try foo(); | |
| 59 | \\} | |
| 60 | 12 | , |
| 61 | .Debug = .{ | |
| 62 | .expect = | |
| 63 | \\error: TheSkyIsFalling | |
| 64 | \\source.zig:2:5: [address] in foo ([main_file]) | |
| 65 | \\ return error.TheSkyIsFalling; | |
| 66 | \\ ^ | |
| 67 | \\source.zig:6:5: [address] in main ([main_file]) | |
| 68 | \\ try foo(); | |
| 69 | \\ ^ | |
| 70 | \\ | |
| 71 | , | |
| 72 | }, | |
| 73 | .ReleaseSafe = .{ | |
| 74 | .exclude_os = &.{ | |
| 75 | .windows, // TODO | |
| 76 | }, | |
| 77 | .expect = | |
| 78 | \\error: TheSkyIsFalling | |
| 79 | \\source.zig:2:5: [address] in [function] | |
| 80 | \\ return error.TheSkyIsFalling; | |
| 81 | \\ ^ | |
| 82 | \\source.zig:6:5: [address] in [function] | |
| 83 | \\ try foo(); | |
| 84 | \\ ^ | |
| 85 | \\ | |
| 86 | , | |
| 87 | .error_tracing = true, | |
| 88 | }, | |
| 89 | .ReleaseFast = .{ | |
| 90 | .expect = | |
| 91 | \\error: TheSkyIsFalling | |
| 92 | \\ | |
| 93 | , | |
| 94 | }, | |
| 95 | .ReleaseSmall = .{ | |
| 96 | .expect = | |
| 97 | \\error: TheSkyIsFalling | |
| 98 | \\ | |
| 99 | , | |
| 100 | }, | |
| 101 | }); | |
| 102 | cases.addCase(.{ | |
| 103 | .name = "non-error return pops error trace", | |
| 104 | .source = | |
| 105 | \\fn bar() !void { | |
| 106 | \\ return error.UhOh; | |
| 107 | \\} | |
| 13 | .unwind = .any, | |
| 14 | .expect_panic = true, | |
| 15 | .expect = | |
| 16 | \\panic: oh no | |
| 17 | \\source.zig:5:5: [address] in foo | |
| 18 | \\ @panic("oh no"); | |
| 19 | \\ ^ | |
| 20 | \\source.zig:2:8: [address] in main | |
| 21 | \\ foo(); | |
| 22 | \\ ^ | |
| 108 | 23 | \\ |
| 109 | \\fn foo() !void { | |
| 110 | \\ bar() catch { | |
| 111 | \\ return; // non-error result: success | |
| 112 | \\ }; | |
| 113 | \\} | |
| 24 | , | |
| 25 | .expect_strip = | |
| 26 | \\panic: oh no | |
| 27 | \\???:?:?: [address] in source.foo | |
| 28 | \\???:?:?: [address] in source.main | |
| 114 | 29 | \\ |
| 115 | \\pub fn main() !void { | |
| 116 | \\ try foo(); | |
| 117 | \\ return error.UnrelatedError; | |
| 118 | \\} | |
| 119 | 30 | , |
| 120 | .Debug = .{ | |
| 121 | .expect = | |
| 122 | \\error: UnrelatedError | |
| 123 | \\source.zig:13:5: [address] in main ([main_file]) | |
| 124 | \\ return error.UnrelatedError; | |
| 125 | \\ ^ | |
| 126 | \\ | |
| 127 | , | |
| 128 | }, | |
| 129 | .ReleaseSafe = .{ | |
| 130 | .exclude_os = &.{ | |
| 131 | .windows, // TODO | |
| 132 | .linux, // defeated by aggressive inlining | |
| 133 | }, | |
| 134 | .expect = | |
| 135 | \\error: UnrelatedError | |
| 136 | \\source.zig:13:5: [address] in [function] | |
| 137 | \\ return error.UnrelatedError; | |
| 138 | \\ ^ | |
| 139 | \\ | |
| 140 | , | |
| 141 | .error_tracing = true, | |
| 142 | }, | |
| 143 | .ReleaseFast = .{ | |
| 144 | .expect = | |
| 145 | \\error: UnrelatedError | |
| 146 | \\ | |
| 147 | , | |
| 148 | }, | |
| 149 | .ReleaseSmall = .{ | |
| 150 | .expect = | |
| 151 | \\error: UnrelatedError | |
| 152 | \\ | |
| 153 | , | |
| 154 | }, | |
| 155 | 31 | }); |
| 156 | 32 | |
| 157 | 33 | cases.addCase(.{ |
| 158 | .name = "continue in while loop", | |
| 34 | .name = "simple panic with no unwind strategy", | |
| 159 | 35 | .source = |
| 160 | \\fn foo() !void { | |
| 161 | \\ return error.UhOh; | |
| 36 | \\pub fn main() void { | |
| 37 | \\ foo(); | |
| 162 | 38 | \\} |
| 163 | \\ | |
| 164 | \\pub fn main() !void { | |
| 165 | \\ var i: usize = 0; | |
| 166 | \\ while (i < 3) : (i += 1) { | |
| 167 | \\ foo() catch continue; | |
| 168 | \\ } | |
| 169 | \\ return error.UnrelatedError; | |
| 39 | \\fn foo() void { | |
| 40 | \\ @panic("oh no"); | |
| 170 | 41 | \\} |
| 42 | \\ | |
| 171 | 43 | , |
| 172 | .Debug = .{ | |
| 173 | .expect = | |
| 174 | \\error: UnrelatedError | |
| 175 | \\source.zig:10:5: [address] in main ([main_file]) | |
| 176 | \\ return error.UnrelatedError; | |
| 177 | \\ ^ | |
| 178 | \\ | |
| 179 | , | |
| 180 | }, | |
| 181 | .ReleaseSafe = .{ | |
| 182 | .exclude_os = &.{ | |
| 183 | .windows, // TODO | |
| 184 | .linux, // defeated by aggressive inlining | |
| 185 | }, | |
| 186 | .expect = | |
| 187 | \\error: UnrelatedError | |
| 188 | \\source.zig:10:5: [address] in [function] | |
| 189 | \\ return error.UnrelatedError; | |
| 190 | \\ ^ | |
| 191 | \\ | |
| 192 | , | |
| 193 | .error_tracing = true, | |
| 194 | }, | |
| 195 | .ReleaseFast = .{ | |
| 196 | .expect = | |
| 197 | \\error: UnrelatedError | |
| 198 | \\ | |
| 199 | , | |
| 200 | }, | |
| 201 | .ReleaseSmall = .{ | |
| 202 | .expect = | |
| 203 | \\error: UnrelatedError | |
| 204 | \\ | |
| 205 | , | |
| 206 | }, | |
| 44 | .unwind = .none, | |
| 45 | .expect_panic = true, | |
| 46 | .expect = "panic: oh no", | |
| 47 | .expect_strip = "panic: oh no", | |
| 207 | 48 | }); |
| 208 | 49 | |
| 209 | 50 | cases.addCase(.{ |
| 210 | .name = "try return + handled catch/if-else", | |
| 51 | .name = "dump current trace", | |
| 211 | 52 | .source = |
| 212 | \\fn foo() !void { | |
| 213 | \\ return error.TheSkyIsFalling; | |
| 53 | \\pub fn main() void { | |
| 54 | \\ foo(bar()); | |
| 214 | 55 | \\} |
| 215 | \\ | |
| 216 | \\pub fn main() !void { | |
| 217 | \\ foo() catch {}; // should not affect error trace | |
| 218 | \\ if (foo()) |_| {} else |_| { | |
| 219 | \\ // should also not affect error trace | |
| 220 | \\ } | |
| 221 | \\ try foo(); | |
| 56 | \\fn bar() void { | |
| 57 | \\ qux(123); | |
| 222 | 58 | \\} |
| 59 | \\fn foo(_: void) void {} | |
| 60 | \\fn qux(x: u32) void { | |
| 61 | \\ std.debug.dumpCurrentStackTrace(.{}); | |
| 62 | \\ _ = x; | |
| 63 | \\} | |
| 64 | \\const std = @import("std"); | |
| 65 | \\ | |
| 223 | 66 | , |
| 224 | .Debug = .{ | |
| 225 | .expect = | |
| 226 | \\error: TheSkyIsFalling | |
| 227 | \\source.zig:2:5: [address] in foo ([main_file]) | |
| 228 | \\ return error.TheSkyIsFalling; | |
| 229 | \\ ^ | |
| 230 | \\source.zig:10:5: [address] in main ([main_file]) | |
| 231 | \\ try foo(); | |
| 232 | \\ ^ | |
| 233 | \\ | |
| 234 | , | |
| 235 | }, | |
| 236 | .ReleaseSafe = .{ | |
| 237 | .exclude_os = &.{ | |
| 238 | .windows, // TODO | |
| 239 | .linux, // defeated by aggressive inlining | |
| 240 | }, | |
| 241 | .expect = | |
| 242 | \\error: TheSkyIsFalling | |
| 243 | \\source.zig:2:5: [address] in [function] | |
| 244 | \\ return error.TheSkyIsFalling; | |
| 245 | \\ ^ | |
| 246 | \\source.zig:10:5: [address] in [function] | |
| 247 | \\ try foo(); | |
| 248 | \\ ^ | |
| 249 | \\ | |
| 250 | , | |
| 251 | .error_tracing = true, | |
| 252 | }, | |
| 253 | .ReleaseFast = .{ | |
| 254 | .expect = | |
| 255 | \\error: TheSkyIsFalling | |
| 256 | \\ | |
| 257 | , | |
| 258 | }, | |
| 259 | .ReleaseSmall = .{ | |
| 260 | .expect = | |
| 261 | \\error: TheSkyIsFalling | |
| 262 | \\ | |
| 263 | , | |
| 264 | }, | |
| 265 | }); | |
| 266 | ||
| 267 | cases.addCase(.{ | |
| 268 | .name = "break from inline loop pops error return trace", | |
| 269 | .source = | |
| 270 | \\fn foo() !void { return error.FooBar; } | |
| 67 | .unwind = .safe, | |
| 68 | .expect_panic = false, | |
| 69 | .expect = | |
| 70 | \\source.zig:9:36: [address] in qux | |
| 71 | \\ std.debug.dumpCurrentStackTrace(.{}); | |
| 72 | \\ ^ | |
| 73 | \\source.zig:5:8: [address] in bar | |
| 74 | \\ qux(123); | |
| 75 | \\ ^ | |
| 76 | \\source.zig:2:12: [address] in main | |
| 77 | \\ foo(bar()); | |
| 78 | \\ ^ | |
| 271 | 79 | \\ |
| 272 | \\pub fn main() !void { | |
| 273 | \\ comptime var i: usize = 0; | |
| 274 | \\ b: inline while (i < 5) : (i += 1) { | |
| 275 | \\ foo() catch { | |
| 276 | \\ break :b; // non-error break, success | |
| 277 | \\ }; | |
| 278 | \\ } | |
| 279 | \\ // foo() was successfully handled, should not appear in trace | |
| 80 | , | |
| 81 | .expect_strip = | |
| 82 | \\???:?:?: [address] in source.qux | |
| 83 | \\???:?:?: [address] in source.bar | |
| 84 | \\???:?:?: [address] in source.main | |
| 280 | 85 | \\ |
| 281 | \\ return error.BadTime; | |
| 282 | \\} | |
| 283 | 86 | , |
| 284 | .Debug = .{ | |
| 285 | .expect = | |
| 286 | \\error: BadTime | |
| 287 | \\source.zig:12:5: [address] in main ([main_file]) | |
| 288 | \\ return error.BadTime; | |
| 289 | \\ ^ | |
| 290 | \\ | |
| 291 | , | |
| 292 | }, | |
| 293 | .ReleaseSafe = .{ | |
| 294 | .exclude_os = &.{ | |
| 295 | .windows, // TODO | |
| 296 | .linux, // defeated by aggressive inlining | |
| 297 | }, | |
| 298 | .expect = | |
| 299 | \\error: BadTime | |
| 300 | \\source.zig:12:5: [address] in [function] | |
| 301 | \\ return error.BadTime; | |
| 302 | \\ ^ | |
| 303 | \\ | |
| 304 | , | |
| 305 | .error_tracing = true, | |
| 306 | }, | |
| 307 | .ReleaseFast = .{ | |
| 308 | .expect = | |
| 309 | \\error: BadTime | |
| 310 | \\ | |
| 311 | , | |
| 312 | }, | |
| 313 | .ReleaseSmall = .{ | |
| 314 | .expect = | |
| 315 | \\error: BadTime | |
| 316 | \\ | |
| 317 | , | |
| 318 | }, | |
| 319 | 87 | }); |
| 320 | 88 | |
| 321 | 89 | cases.addCase(.{ |
| 322 | .name = "catch and re-throw error", | |
| 90 | .name = "dump current trace with no unwind strategy", | |
| 323 | 91 | .source = |
| 324 | \\fn foo() !void { | |
| 325 | \\ return error.TheSkyIsFalling; | |
| 92 | \\pub fn main() void { | |
| 93 | \\ foo(bar()); | |
| 326 | 94 | \\} |
| 327 | \\ | |
| 328 | \\pub fn main() !void { | |
| 329 | \\ return foo() catch error.AndMyCarIsOutOfGas; | |
| 95 | \\fn bar() void { | |
| 96 | \\ qux(123); | |
| 330 | 97 | \\} |
| 331 | , | |
| 332 | .Debug = .{ | |
| 333 | .expect = | |
| 334 | \\error: AndMyCarIsOutOfGas | |
| 335 | \\source.zig:2:5: [address] in foo ([main_file]) | |
| 336 | \\ return error.TheSkyIsFalling; | |
| 337 | \\ ^ | |
| 338 | \\source.zig:6:5: [address] in main ([main_file]) | |
| 339 | \\ return foo() catch error.AndMyCarIsOutOfGas; | |
| 340 | \\ ^ | |
| 341 | \\ | |
| 342 | , | |
| 343 | }, | |
| 344 | .ReleaseSafe = .{ | |
| 345 | .exclude_os = &.{ | |
| 346 | .windows, // TODO | |
| 347 | .linux, // defeated by aggressive inlining | |
| 348 | }, | |
| 349 | .expect = | |
| 350 | \\error: AndMyCarIsOutOfGas | |
| 351 | \\source.zig:2:5: [address] in [function] | |
| 352 | \\ return error.TheSkyIsFalling; | |
| 353 | \\ ^ | |
| 354 | \\source.zig:6:5: [address] in [function] | |
| 355 | \\ return foo() catch error.AndMyCarIsOutOfGas; | |
| 356 | \\ ^ | |
| 357 | \\ | |
| 358 | , | |
| 359 | .error_tracing = true, | |
| 360 | }, | |
| 361 | .ReleaseFast = .{ | |
| 362 | .expect = | |
| 363 | \\error: AndMyCarIsOutOfGas | |
| 364 | \\ | |
| 365 | , | |
| 366 | }, | |
| 367 | .ReleaseSmall = .{ | |
| 368 | .expect = | |
| 369 | \\error: AndMyCarIsOutOfGas | |
| 370 | \\ | |
| 371 | , | |
| 372 | }, | |
| 373 | }); | |
| 374 | ||
| 375 | cases.addCase(.{ | |
| 376 | .name = "errors stored in var do not contribute to error trace", | |
| 377 | .source = | |
| 378 | \\fn foo() !void { | |
| 379 | \\ return error.TheSkyIsFalling; | |
| 98 | \\fn foo(_: void) void {} | |
| 99 | \\fn qux(x: u32) void { | |
| 100 | \\ std.debug.print("pre\n", .{}); | |
| 101 | \\ std.debug.dumpCurrentStackTrace(.{}); | |
| 102 | \\ std.debug.print("post\n", .{}); | |
| 103 | \\ _ = x; | |
| 380 | 104 | \\} |
| 105 | \\const std = @import("std"); | |
| 381 | 106 | \\ |
| 382 | \\pub fn main() !void { | |
| 383 | \\ // Once an error is stored in a variable, it is popped from the trace | |
| 384 | \\ var x = foo(); | |
| 385 | \\ x = {}; | |
| 386 | \\ | |
| 387 | \\ // As a result, this error trace will still be clean | |
| 388 | \\ return error.SomethingUnrelatedWentWrong; | |
| 389 | \\} | |
| 390 | 107 | , |
| 391 | .Debug = .{ | |
| 392 | .expect = | |
| 393 | \\error: SomethingUnrelatedWentWrong | |
| 394 | \\source.zig:11:5: [address] in main ([main_file]) | |
| 395 | \\ return error.SomethingUnrelatedWentWrong; | |
| 396 | \\ ^ | |
| 397 | \\ | |
| 398 | , | |
| 399 | }, | |
| 400 | .ReleaseSafe = .{ | |
| 401 | .exclude_os = &.{ | |
| 402 | .windows, // TODO | |
| 403 | .linux, // defeated by aggressive inlining | |
| 404 | }, | |
| 405 | .expect = | |
| 406 | \\error: SomethingUnrelatedWentWrong | |
| 407 | \\source.zig:11:5: [address] in [function] | |
| 408 | \\ return error.SomethingUnrelatedWentWrong; | |
| 409 | \\ ^ | |
| 410 | \\ | |
| 411 | , | |
| 412 | .error_tracing = true, | |
| 413 | }, | |
| 414 | .ReleaseFast = .{ | |
| 415 | .expect = | |
| 416 | \\error: SomethingUnrelatedWentWrong | |
| 417 | \\ | |
| 418 | , | |
| 419 | }, | |
| 420 | .ReleaseSmall = .{ | |
| 421 | .expect = | |
| 422 | \\error: SomethingUnrelatedWentWrong | |
| 423 | \\ | |
| 424 | , | |
| 425 | }, | |
| 108 | .unwind = .no_safe, | |
| 109 | .expect_panic = false, | |
| 110 | .expect = "pre\npost\n", | |
| 111 | .expect_strip = "pre\npost\n", | |
| 426 | 112 | }); |
| 427 | 113 | |
| 428 | 114 | cases.addCase(.{ |
| 429 | .name = "error stored in const has trace preserved for duration of block", | |
| 115 | .name = "dump captured trace", | |
| 430 | 116 | .source = |
| 431 | \\fn foo() !void { return error.TheSkyIsFalling; } | |
| 432 | \\fn bar() !void { return error.InternalError; } | |
| 433 | \\fn baz() !void { return error.UnexpectedReality; } | |
| 434 | \\ | |
| 435 | \\pub fn main() !void { | |
| 436 | \\ const x = foo(); | |
| 437 | \\ const y = b: { | |
| 438 | \\ if (true) | |
| 439 | \\ break :b bar(); | |
| 440 | \\ | |
| 441 | \\ break :b {}; | |
| 442 | \\ }; | |
| 443 | \\ x catch {}; | |
| 444 | \\ y catch {}; | |
| 445 | \\ // foo()/bar() error traces not popped until end of block | |
| 446 | \\ | |
| 447 | \\ { | |
| 448 | \\ const z = baz(); | |
| 449 | \\ z catch {}; | |
| 450 | \\ // baz() error trace still alive here | |
| 451 | \\ } | |
| 452 | \\ // baz() error trace popped, foo(), bar() still alive | |
| 453 | \\ return error.StillUnresolved; | |
| 117 | \\pub fn main() void { | |
| 118 | \\ var stack_trace_buf: [8]usize = undefined; | |
| 119 | \\ dumpIt(&captureIt(&stack_trace_buf)); | |
| 454 | 120 | \\} |
| 455 | , | |
| 456 | .Debug = .{ | |
| 457 | .expect = | |
| 458 | \\error: StillUnresolved | |
| 459 | \\source.zig:1:18: [address] in foo ([main_file]) | |
| 460 | \\fn foo() !void { return error.TheSkyIsFalling; } | |
| 461 | \\ ^ | |
| 462 | \\source.zig:2:18: [address] in bar ([main_file]) | |
| 463 | \\fn bar() !void { return error.InternalError; } | |
| 464 | \\ ^ | |
| 465 | \\source.zig:23:5: [address] in main ([main_file]) | |
| 466 | \\ return error.StillUnresolved; | |
| 467 | \\ ^ | |
| 468 | \\ | |
| 469 | , | |
| 470 | }, | |
| 471 | .ReleaseSafe = .{ | |
| 472 | .exclude_os = &.{ | |
| 473 | .windows, // TODO | |
| 474 | .linux, // defeated by aggressive inlining | |
| 475 | }, | |
| 476 | .expect = | |
| 477 | \\error: StillUnresolved | |
| 478 | \\source.zig:1:18: [address] in [function] | |
| 479 | \\fn foo() !void { return error.TheSkyIsFalling; } | |
| 480 | \\ ^ | |
| 481 | \\source.zig:2:18: [address] in [function] | |
| 482 | \\fn bar() !void { return error.InternalError; } | |
| 483 | \\ ^ | |
| 484 | \\source.zig:23:5: [address] in [function] | |
| 485 | \\ return error.StillUnresolved; | |
| 486 | \\ ^ | |
| 487 | \\ | |
| 488 | , | |
| 489 | .error_tracing = true, | |
| 490 | }, | |
| 491 | .ReleaseFast = .{ | |
| 492 | .expect = | |
| 493 | \\error: StillUnresolved | |
| 494 | \\ | |
| 495 | , | |
| 496 | }, | |
| 497 | .ReleaseSmall = .{ | |
| 498 | .expect = | |
| 499 | \\error: StillUnresolved | |
| 500 | \\ | |
| 501 | , | |
| 502 | }, | |
| 503 | }); | |
| 504 | ||
| 505 | cases.addCase(.{ | |
| 506 | .name = "error passed to function has its trace preserved for duration of the call", | |
| 507 | .source = | |
| 508 | \\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void { | |
| 509 | \\ actual_error catch |err| { | |
| 510 | \\ if (err == expected_error) return {}; | |
| 511 | \\ }; | |
| 512 | \\ return error.TestExpectedError; | |
| 121 | \\fn captureIt(buf: []usize) std.builtin.StackTrace { | |
| 122 | \\ return captureItInner(buf); | |
| 513 | 123 | \\} |
| 124 | \\fn dumpIt(st: *const std.builtin.StackTrace) void { | |
| 125 | \\ std.debug.dumpStackTrace(st); | |
| 126 | \\} | |
| 127 | \\fn captureItInner(buf: []usize) std.builtin.StackTrace { | |
| 128 | \\ return std.debug.captureCurrentStackTrace(.{}, buf); | |
| 129 | \\} | |
| 130 | \\const std = @import("std"); | |
| 514 | 131 | \\ |
| 515 | \\fn alwaysErrors() !void { return error.ThisErrorShouldNotAppearInAnyTrace; } | |
| 516 | \\fn foo() !void { return error.Foo; } | |
| 132 | , | |
| 133 | .unwind = .safe, | |
| 134 | .expect_panic = false, | |
| 135 | .expect = | |
| 136 | \\source.zig:12:46: [address] in captureItInner | |
| 137 | \\ return std.debug.captureCurrentStackTrace(.{}, buf); | |
| 138 | \\ ^ | |
| 139 | \\source.zig:6:26: [address] in captureIt | |
| 140 | \\ return captureItInner(buf); | |
| 141 | \\ ^ | |
| 142 | \\source.zig:3:22: [address] in main | |
| 143 | \\ dumpIt(&captureIt(&stack_trace_buf)); | |
| 144 | \\ ^ | |
| 517 | 145 | \\ |
| 518 | \\pub fn main() !void { | |
| 519 | \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors()); | |
| 520 | \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors()); | |
| 521 | \\ try expectError(error.Foo, foo()); | |
| 146 | , | |
| 147 | .expect_strip = | |
| 148 | \\???:?:?: [address] in source.captureItInner | |
| 149 | \\???:?:?: [address] in source.captureIt | |
| 150 | \\???:?:?: [address] in source.main | |
| 522 | 151 | \\ |
| 523 | \\ // Only the error trace for this failing check should appear: | |
| 524 | \\ try expectError(error.Bar, foo()); | |
| 525 | \\} | |
| 526 | 152 | , |
| 527 | .Debug = .{ | |
| 528 | .expect = | |
| 529 | \\error: TestExpectedError | |
| 530 | \\source.zig:9:18: [address] in foo ([main_file]) | |
| 531 | \\fn foo() !void { return error.Foo; } | |
| 532 | \\ ^ | |
| 533 | \\source.zig:5:5: [address] in expectError ([main_file]) | |
| 534 | \\ return error.TestExpectedError; | |
| 535 | \\ ^ | |
| 536 | \\source.zig:17:5: [address] in main ([main_file]) | |
| 537 | \\ try expectError(error.Bar, foo()); | |
| 538 | \\ ^ | |
| 539 | \\ | |
| 540 | , | |
| 541 | }, | |
| 542 | .ReleaseSafe = .{ | |
| 543 | .exclude_os = &.{ | |
| 544 | .windows, // TODO | |
| 545 | }, | |
| 546 | .expect = | |
| 547 | \\error: TestExpectedError | |
| 548 | \\source.zig:9:18: [address] in [function] | |
| 549 | \\fn foo() !void { return error.Foo; } | |
| 550 | \\ ^ | |
| 551 | \\source.zig:5:5: [address] in [function] | |
| 552 | \\ return error.TestExpectedError; | |
| 553 | \\ ^ | |
| 554 | \\source.zig:17:5: [address] in [function] | |
| 555 | \\ try expectError(error.Bar, foo()); | |
| 556 | \\ ^ | |
| 557 | \\ | |
| 558 | , | |
| 559 | .error_tracing = true, | |
| 560 | }, | |
| 561 | .ReleaseFast = .{ | |
| 562 | .expect = | |
| 563 | \\error: TestExpectedError | |
| 564 | \\ | |
| 565 | , | |
| 566 | }, | |
| 567 | .ReleaseSmall = .{ | |
| 568 | .expect = | |
| 569 | \\error: TestExpectedError | |
| 570 | \\ | |
| 571 | , | |
| 572 | }, | |
| 573 | 153 | }); |
| 574 | 154 | |
| 575 | 155 | cases.addCase(.{ |
| 576 | .name = "try return from within catch", | |
| 156 | .name = "dump captured trace with no unwind strategy", | |
| 577 | 157 | .source = |
| 578 | \\fn foo() !void { | |
| 579 | \\ return error.TheSkyIsFalling; | |
| 158 | \\pub fn main() void { | |
| 159 | \\ var stack_trace_buf: [8]usize = undefined; | |
| 160 | \\ dumpIt(&captureIt(&stack_trace_buf)); | |
| 580 | 161 | \\} |
| 581 | \\ | |
| 582 | \\fn bar() !void { | |
| 583 | \\ return error.AndMyCarIsOutOfGas; | |
| 162 | \\fn captureIt(buf: []usize) std.builtin.StackTrace { | |
| 163 | \\ return captureItInner(buf); | |
| 584 | 164 | \\} |
| 585 | \\ | |
| 586 | \\pub fn main() !void { | |
| 587 | \\ foo() catch { // error trace should include foo() | |
| 588 | \\ try bar(); | |
| 589 | \\ }; | |
| 165 | \\fn dumpIt(st: *const std.builtin.StackTrace) void { | |
| 166 | \\ std.debug.dumpStackTrace(st); | |
| 590 | 167 | \\} |
| 591 | , | |
| 592 | .Debug = .{ | |
| 593 | .expect = | |
| 594 | \\error: AndMyCarIsOutOfGas | |
| 595 | \\source.zig:2:5: [address] in foo ([main_file]) | |
| 596 | \\ return error.TheSkyIsFalling; | |
| 597 | \\ ^ | |
| 598 | \\source.zig:6:5: [address] in bar ([main_file]) | |
| 599 | \\ return error.AndMyCarIsOutOfGas; | |
| 600 | \\ ^ | |
| 601 | \\source.zig:11:9: [address] in main ([main_file]) | |
| 602 | \\ try bar(); | |
| 603 | \\ ^ | |
| 604 | \\ | |
| 605 | , | |
| 606 | }, | |
| 607 | .ReleaseSafe = .{ | |
| 608 | .exclude_os = &.{ | |
| 609 | .windows, // TODO | |
| 610 | }, | |
| 611 | .expect = | |
| 612 | \\error: AndMyCarIsOutOfGas | |
| 613 | \\source.zig:2:5: [address] in [function] | |
| 614 | \\ return error.TheSkyIsFalling; | |
| 615 | \\ ^ | |
| 616 | \\source.zig:6:5: [address] in [function] | |
| 617 | \\ return error.AndMyCarIsOutOfGas; | |
| 618 | \\ ^ | |
| 619 | \\source.zig:11:9: [address] in [function] | |
| 620 | \\ try bar(); | |
| 621 | \\ ^ | |
| 622 | \\ | |
| 623 | , | |
| 624 | .error_tracing = true, | |
| 625 | }, | |
| 626 | .ReleaseFast = .{ | |
| 627 | .expect = | |
| 628 | \\error: AndMyCarIsOutOfGas | |
| 629 | \\ | |
| 630 | , | |
| 631 | }, | |
| 632 | .ReleaseSmall = .{ | |
| 633 | .expect = | |
| 634 | \\error: AndMyCarIsOutOfGas | |
| 635 | \\ | |
| 636 | , | |
| 637 | }, | |
| 638 | }); | |
| 639 | ||
| 640 | cases.addCase(.{ | |
| 641 | .name = "try return from within if-else", | |
| 642 | .source = | |
| 643 | \\fn foo() !void { | |
| 644 | \\ return error.TheSkyIsFalling; | |
| 645 | \\} | |
| 646 | \\ | |
| 647 | \\fn bar() !void { | |
| 648 | \\ return error.AndMyCarIsOutOfGas; | |
| 168 | \\fn captureItInner(buf: []usize) std.builtin.StackTrace { | |
| 169 | \\ return std.debug.captureCurrentStackTrace(.{}, buf); | |
| 649 | 170 | \\} |
| 171 | \\const std = @import("std"); | |
| 650 | 172 | \\ |
| 651 | \\pub fn main() !void { | |
| 652 | \\ if (foo()) |_| {} else |_| { // error trace should include foo() | |
| 653 | \\ try bar(); | |
| 654 | \\ } | |
| 655 | \\} | |
| 656 | 173 | , |
| 657 | .Debug = .{ | |
| 658 | .expect = | |
| 659 | \\error: AndMyCarIsOutOfGas | |
| 660 | \\source.zig:2:5: [address] in foo ([main_file]) | |
| 661 | \\ return error.TheSkyIsFalling; | |
| 662 | \\ ^ | |
| 663 | \\source.zig:6:5: [address] in bar ([main_file]) | |
| 664 | \\ return error.AndMyCarIsOutOfGas; | |
| 665 | \\ ^ | |
| 666 | \\source.zig:11:9: [address] in main ([main_file]) | |
| 667 | \\ try bar(); | |
| 668 | \\ ^ | |
| 669 | \\ | |
| 670 | , | |
| 671 | }, | |
| 672 | .ReleaseSafe = .{ | |
| 673 | .exclude_os = &.{ | |
| 674 | .windows, // TODO | |
| 675 | }, | |
| 676 | .expect = | |
| 677 | \\error: AndMyCarIsOutOfGas | |
| 678 | \\source.zig:2:5: [address] in [function] | |
| 679 | \\ return error.TheSkyIsFalling; | |
| 680 | \\ ^ | |
| 681 | \\source.zig:6:5: [address] in [function] | |
| 682 | \\ return error.AndMyCarIsOutOfGas; | |
| 683 | \\ ^ | |
| 684 | \\source.zig:11:9: [address] in [function] | |
| 685 | \\ try bar(); | |
| 686 | \\ ^ | |
| 687 | \\ | |
| 688 | , | |
| 689 | .error_tracing = true, | |
| 690 | }, | |
| 691 | .ReleaseFast = .{ | |
| 692 | .expect = | |
| 693 | \\error: AndMyCarIsOutOfGas | |
| 694 | \\ | |
| 695 | , | |
| 696 | }, | |
| 697 | .ReleaseSmall = .{ | |
| 698 | .expect = | |
| 699 | \\error: AndMyCarIsOutOfGas | |
| 700 | \\ | |
| 701 | , | |
| 702 | }, | |
| 174 | .unwind = .no_safe, | |
| 175 | .expect_panic = false, | |
| 176 | .expect = "(empty stack trace)\n", | |
| 177 | .expect_strip = "(empty stack trace)\n", | |
| 703 | 178 | }); |
| 704 | 179 | |
| 705 | 180 | cases.addCase(.{ |
| 706 | .name = "try try return return", | |
| 181 | .name = "dump captured trace on thread", | |
| 707 | 182 | .source = |
| 708 | \\fn foo() !void { | |
| 709 | \\ try bar(); | |
| 183 | \\pub fn main() !void { | |
| 184 | \\ var stack_trace_buf: [8]usize = undefined; | |
| 185 | \\ const t = try std.Thread.spawn(.{}, threadMain, .{&stack_trace_buf}); | |
| 186 | \\ t.join(); | |
| 710 | 187 | \\} |
| 711 | \\ | |
| 712 | \\fn bar() !void { | |
| 713 | \\ return make_error(); | |
| 188 | \\fn threadMain(stack_trace_buf: []usize) void { | |
| 189 | \\ dumpIt(&captureIt(stack_trace_buf)); | |
| 714 | 190 | \\} |
| 715 | \\ | |
| 716 | \\fn make_error() !void { | |
| 717 | \\ return error.TheSkyIsFalling; | |
| 191 | \\fn captureIt(buf: []usize) std.builtin.StackTrace { | |
| 192 | \\ return captureItInner(buf); | |
| 718 | 193 | \\} |
| 719 | \\ | |
| 720 | \\pub fn main() !void { | |
| 721 | \\ try foo(); | |
| 194 | \\fn dumpIt(st: *const std.builtin.StackTrace) void { | |
| 195 | \\ std.debug.dumpStackTrace(st); | |
| 196 | \\} | |
| 197 | \\fn captureItInner(buf: []usize) std.builtin.StackTrace { | |
| 198 | \\ return std.debug.captureCurrentStackTrace(.{}, buf); | |
| 722 | 199 | \\} |
| 723 | , | |
| 724 | .Debug = .{ | |
| 725 | .expect = | |
| 726 | \\error: TheSkyIsFalling | |
| 727 | \\source.zig:10:5: [address] in make_error ([main_file]) | |
| 728 | \\ return error.TheSkyIsFalling; | |
| 729 | \\ ^ | |
| 730 | \\source.zig:6:5: [address] in bar ([main_file]) | |
| 731 | \\ return make_error(); | |
| 732 | \\ ^ | |
| 733 | \\source.zig:2:5: [address] in foo ([main_file]) | |
| 734 | \\ try bar(); | |
| 735 | \\ ^ | |
| 736 | \\source.zig:14:5: [address] in main ([main_file]) | |
| 737 | \\ try foo(); | |
| 738 | \\ ^ | |
| 739 | \\ | |
| 740 | , | |
| 741 | }, | |
| 742 | .ReleaseSafe = .{ | |
| 743 | .exclude_os = &.{ | |
| 744 | .windows, // TODO | |
| 745 | }, | |
| 746 | .expect = | |
| 747 | \\error: TheSkyIsFalling | |
| 748 | \\source.zig:10:5: [address] in [function] | |
| 749 | \\ return error.TheSkyIsFalling; | |
| 750 | \\ ^ | |
| 751 | \\source.zig:6:5: [address] in [function] | |
| 752 | \\ return make_error(); | |
| 753 | \\ ^ | |
| 754 | \\source.zig:2:5: [address] in [function] | |
| 755 | \\ try bar(); | |
| 756 | \\ ^ | |
| 757 | \\source.zig:14:5: [address] in [function] | |
| 758 | \\ try foo(); | |
| 759 | \\ ^ | |
| 760 | \\ | |
| 761 | , | |
| 762 | .error_tracing = true, | |
| 763 | }, | |
| 764 | .ReleaseFast = .{ | |
| 765 | .expect = | |
| 766 | \\error: TheSkyIsFalling | |
| 767 | \\ | |
| 768 | , | |
| 769 | }, | |
| 770 | .ReleaseSmall = .{ | |
| 771 | .expect = | |
| 772 | \\error: TheSkyIsFalling | |
| 773 | \\ | |
| 774 | , | |
| 775 | }, | |
| 776 | }); | |
| 777 | ||
| 778 | cases.addCase(.{ | |
| 779 | .name = "dumpCurrentStackTrace", | |
| 780 | .source = | |
| 781 | 200 | \\const std = @import("std"); |
| 782 | 201 | \\ |
| 783 | \\fn bar() void { | |
| 784 | \\ std.debug.dumpCurrentStackTrace(@returnAddress()); | |
| 785 | \\} | |
| 786 | \\fn foo() void { | |
| 787 | \\ bar(); | |
| 788 | \\} | |
| 789 | \\pub fn main() u8 { | |
| 790 | \\ foo(); | |
| 791 | \\ return 1; | |
| 792 | \\} | |
| 793 | 202 | , |
| 794 | .Debug = .{ | |
| 795 | // std.debug.sys_can_stack_trace | |
| 796 | .exclude_arch = &.{ | |
| 797 | .loongarch32, | |
| 798 | .loongarch64, | |
| 799 | .mips, | |
| 800 | .mipsel, | |
| 801 | .mips64, | |
| 802 | .mips64el, | |
| 803 | .s390x, | |
| 804 | }, | |
| 805 | .exclude_os = &.{ | |
| 806 | .freebsd, | |
| 807 | .openbsd, // integer overflow | |
| 808 | .windows, // TODO intermittent failures | |
| 809 | }, | |
| 810 | .expect = | |
| 811 | \\source.zig:7:8: [address] in foo ([main_file]) | |
| 812 | \\ bar(); | |
| 813 | \\ ^ | |
| 814 | \\source.zig:10:8: [address] in main ([main_file]) | |
| 815 | \\ foo(); | |
| 816 | \\ ^ | |
| 817 | \\ | |
| 818 | , | |
| 819 | }, | |
| 820 | }); | |
| 821 | cases.addCase(.{ | |
| 822 | .name = "error union switch with call operand", | |
| 823 | .source = | |
| 824 | \\pub fn main() !void { | |
| 825 | \\ try foo(); | |
| 826 | \\ return error.TheSkyIsFalling; | |
| 827 | \\} | |
| 203 | .unwind = .safe, | |
| 204 | .expect_panic = false, | |
| 205 | .expect = | |
| 206 | \\source.zig:16:46: [address] in captureItInner | |
| 207 | \\ return std.debug.captureCurrentStackTrace(.{}, buf); | |
| 208 | \\ ^ | |
| 209 | \\source.zig:10:26: [address] in captureIt | |
| 210 | \\ return captureItInner(buf); | |
| 211 | \\ ^ | |
| 212 | \\source.zig:7:22: [address] in threadMain | |
| 213 | \\ dumpIt(&captureIt(stack_trace_buf)); | |
| 214 | \\ ^ | |
| 828 | 215 | \\ |
| 829 | \\noinline fn failure() error{ Fatal, NonFatal }!void { | |
| 830 | \\ return error.NonFatal; | |
| 831 | \\} | |
| 216 | , | |
| 217 | .expect_strip = | |
| 218 | \\???:?:?: [address] in source.captureItInner | |
| 219 | \\???:?:?: [address] in source.captureIt | |
| 220 | \\???:?:?: [address] in source.threadMain | |
| 832 | 221 | \\ |
| 833 | \\fn foo() error{Fatal}!void { | |
| 834 | \\ return failure() catch |err| switch (err) { | |
| 835 | \\ error.Fatal => return error.Fatal, | |
| 836 | \\ error.NonFatal => return, | |
| 837 | \\ }; | |
| 838 | \\} | |
| 839 | 222 | , |
| 840 | .Debug = .{ | |
| 841 | .expect = | |
| 842 | \\error: TheSkyIsFalling | |
| 843 | \\source.zig:3:5: [address] in main ([main_file]) | |
| 844 | \\ return error.TheSkyIsFalling; | |
| 845 | \\ ^ | |
| 846 | \\ | |
| 847 | , | |
| 848 | }, | |
| 849 | .ReleaseSafe = .{ | |
| 850 | .exclude_os = &.{ | |
| 851 | .freebsd, | |
| 852 | .windows, // TODO | |
| 853 | .linux, // defeated by aggressive inlining | |
| 854 | .macos, // Broken in LLVM 20. | |
| 855 | }, | |
| 856 | .expect = | |
| 857 | \\error: TheSkyIsFalling | |
| 858 | \\source.zig:3:5: [address] in [function] | |
| 859 | \\ return error.TheSkyIsFalling; | |
| 860 | \\ ^ | |
| 861 | \\ | |
| 862 | , | |
| 863 | .error_tracing = true, | |
| 864 | }, | |
| 865 | .ReleaseFast = .{ | |
| 866 | .expect = | |
| 867 | \\error: TheSkyIsFalling | |
| 868 | \\ | |
| 869 | , | |
| 870 | }, | |
| 871 | .ReleaseSmall = .{ | |
| 872 | .expect = | |
| 873 | \\error: TheSkyIsFalling | |
| 874 | \\ | |
| 875 | , | |
| 876 | }, | |
| 877 | 223 | }); |
| 878 | 224 | } |
test/standalone/coff_dwarf/build.zig+7-6| ... | ... | @@ -1,9 +1,10 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); | |
| 3 | 2 | |
| 4 | 3 | /// This tests the path where DWARF information is embedded in a COFF binary |
| 5 | 4 | pub fn build(b: *std.Build) void { |
| 6 | switch (builtin.cpu.arch) { | |
| 5 | const host = b.graph.host; | |
| 6 | ||
| 7 | switch (host.result.cpu.arch) { | |
| 7 | 8 | .aarch64, |
| 8 | 9 | .x86, |
| 9 | 10 | .x86_64, |
| ... | ... | @@ -15,10 +16,10 @@ pub fn build(b: *std.Build) void { |
| 15 | 16 | b.default_step = test_step; |
| 16 | 17 | |
| 17 | 18 | const optimize: std.builtin.OptimizeMode = .Debug; |
| 18 | const target = if (builtin.os.tag == .windows) | |
| 19 | b.standardTargetOptions(.{}) | |
| 20 | else | |
| 21 | b.resolveTargetQuery(.{ .os_tag = .windows }); | |
| 19 | const target = switch (host.result.os.tag) { | |
| 20 | .windows => host, | |
| 21 | else => b.resolveTargetQuery(.{ .os_tag = .windows }), | |
| 22 | }; | |
| 22 | 23 | |
| 23 | 24 | const exe = b.addExecutable(.{ |
| 24 | 25 | .name = "main", |
test/standalone/coff_dwarf/main.zig+23-16| ... | ... | @@ -1,27 +1,34 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const assert = std.debug.assert; | |
| 3 | const testing = std.testing; | |
| 2 | const fatal = std.process.fatal; | |
| 4 | 3 | |
| 5 | 4 | extern fn add(a: u32, b: u32, addr: *usize) u32; |
| 6 | 5 | |
| 7 | pub fn main() !void { | |
| 8 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; | |
| 9 | defer assert(gpa.deinit() == .ok); | |
| 10 | const allocator = gpa.allocator(); | |
| 6 | pub fn main() void { | |
| 7 | var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init; | |
| 8 | defer std.debug.assert(debug_alloc_inst.deinit() == .ok); | |
| 9 | const gpa = debug_alloc_inst.allocator(); | |
| 11 | 10 | |
| 12 | var debug_info = try std.debug.SelfInfo.open(allocator); | |
| 13 | defer debug_info.deinit(); | |
| 11 | var di: std.debug.SelfInfo = .init; | |
| 12 | defer di.deinit(gpa); | |
| 14 | 13 | |
| 15 | 14 | var add_addr: usize = undefined; |
| 16 | 15 | _ = add(1, 2, &add_addr); |
| 17 | 16 | |
| 18 | const module = try debug_info.getModuleForAddress(add_addr); | |
| 19 | const symbol = try module.getSymbolAtAddress(allocator, add_addr); | |
| 20 | defer if (symbol.source_location) |sl| allocator.free(sl.file_name); | |
| 17 | const symbol = di.getSymbol(gpa, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err}); | |
| 18 | defer if (symbol.source_location) |sl| gpa.free(sl.file_name); | |
| 21 | 19 | |
| 22 | try testing.expectEqualStrings("add", symbol.name); | |
| 23 | try testing.expect(symbol.source_location != null); | |
| 24 | try testing.expectEqualStrings("shared_lib.c", std.fs.path.basename(symbol.source_location.?.file_name)); | |
| 25 | try testing.expectEqual(@as(u64, 3), symbol.source_location.?.line); | |
| 26 | try testing.expectEqual(@as(u64, 0), symbol.source_location.?.column); | |
| 20 | if (symbol.name == null) fatal("failed to resolve symbol name", .{}); | |
| 21 | if (symbol.compile_unit_name == null) fatal("failed to resolve compile unit", .{}); | |
| 22 | if (symbol.source_location == null) fatal("failed to resolve source location", .{}); | |
| 23 | ||
| 24 | if (!std.mem.eql(u8, symbol.name.?, "add")) { | |
| 25 | fatal("incorrect symbol name '{s}'", .{symbol.name.?}); | |
| 26 | } | |
| 27 | const sl = &symbol.source_location.?; | |
| 28 | if (!std.mem.eql(u8, std.fs.path.basename(sl.file_name), "shared_lib.c")) { | |
| 29 | fatal("incorrect file name '{s}'", .{sl.file_name}); | |
| 30 | } | |
| 31 | if (sl.line != 3 or sl.column != 0) { | |
| 32 | fatal("incorrect line/column :{d}:{d}", .{ sl.line, sl.column }); | |
| 33 | } | |
| 27 | 34 | } |
test/standalone/stack_iterator/unwind.zig+31-29| ... | ... | @@ -1,27 +1,19 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | const debug = std.debug; | |
| 4 | const testing = std.testing; | |
| 3 | const fatal = std.process.fatal; | |
| 5 | 4 | |
| 6 | noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void { | |
| 5 | noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { | |
| 7 | 6 | expected[0] = @returnAddress(); |
| 8 | ||
| 9 | var context: debug.ThreadContext = undefined; | |
| 10 | testing.expect(debug.getContext(&context)) catch @panic("failed to getContext"); | |
| 11 | ||
| 12 | const debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo"); | |
| 13 | var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context, @frameAddress()) catch @panic("failed to initWithContext"); | |
| 14 | defer it.deinit(); | |
| 15 | ||
| 16 | for (unwound) |*addr| { | |
| 17 | if (it.next()) |return_address| addr.* = return_address; | |
| 18 | } | |
| 7 | return std.debug.captureCurrentStackTrace(.{ | |
| 8 | .first_address = @returnAddress(), | |
| 9 | .allow_unsafe_unwind = true, | |
| 10 | }, addr_buf); | |
| 19 | 11 | } |
| 20 | 12 | |
| 21 | noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void { | |
| 13 | noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { | |
| 22 | 14 | // Exercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored |
| 23 | 15 | if (builtin.target.ofmt != .c) { |
| 24 | switch (builtin.cpu.arch) { | |
| 16 | switch (builtin.target.cpu.arch) { | |
| 25 | 17 | .x86 => { |
| 26 | 18 | if (builtin.omit_frame_pointer) { |
| 27 | 19 | asm volatile ( |
| ... | ... | @@ -67,10 +59,10 @@ noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void { |
| 67 | 59 | } |
| 68 | 60 | |
| 69 | 61 | expected[1] = @returnAddress(); |
| 70 | frame3(expected, unwound); | |
| 62 | return frame3(expected, addr_buf); | |
| 71 | 63 | } |
| 72 | 64 | |
| 73 | noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void { | |
| 65 | noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { | |
| 74 | 66 | expected[2] = @returnAddress(); |
| 75 | 67 | |
| 76 | 68 | // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding |
| ... | ... | @@ -78,22 +70,32 @@ noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void { |
| 78 | 70 | var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined; |
| 79 | 71 | _ = std.mem.doNotOptimizeAway(&pad); |
| 80 | 72 | |
| 81 | frame2(expected, unwound); | |
| 73 | return frame2(expected, addr_buf); | |
| 82 | 74 | } |
| 83 | 75 | |
| 84 | noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void { | |
| 76 | noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { | |
| 85 | 77 | expected[3] = @returnAddress(); |
| 86 | frame1(expected, unwound); | |
| 78 | return frame1(expected, addr_buf); | |
| 87 | 79 | } |
| 88 | 80 | |
| 89 | pub fn main() !void { | |
| 90 | // Disabled until the DWARF unwinder bugs on .aarch64 are solved | |
| 91 | if (builtin.omit_frame_pointer and comptime builtin.target.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) return; | |
| 92 | ||
| 93 | if (!std.debug.have_ucontext or !std.debug.have_getcontext) return; | |
| 81 | pub fn main() void { | |
| 82 | if (std.debug.cpu_context.Native == noreturn and builtin.omit_frame_pointer) { | |
| 83 | // Stack unwinding is impossible. | |
| 84 | return; | |
| 85 | } | |
| 94 | 86 | |
| 95 | 87 | var expected: [4]usize = undefined; |
| 96 | var unwound: [4]usize = undefined; | |
| 97 | frame0(&expected, &unwound); | |
| 98 | try testing.expectEqual(expected, unwound); | |
| 88 | var addr_buf: [4]usize = undefined; | |
| 89 | const trace = frame0(&expected, &addr_buf); | |
| 90 | // There may be *more* than 4 frames (due to the caller of `main`); that's okay. | |
| 91 | if (trace.index < 4) { | |
| 92 | fatal("expected at least 4 frames, got '{d}':\n{f}", .{ trace.index, &trace }); | |
| 93 | } | |
| 94 | if (!std.mem.eql(usize, trace.instruction_addresses, &expected)) { | |
| 95 | const expected_trace: std.builtin.StackTrace = .{ | |
| 96 | .index = 4, | |
| 97 | .instruction_addresses = &expected, | |
| 98 | }; | |
| 99 | fatal("expected trace:\n{f}\nactual trace:\n{f}", .{ &expected_trace, &trace }); | |
| 100 | } | |
| 99 | 101 | } |
test/standalone/stack_iterator/unwind_freestanding.zig+35-33| ... | ... | @@ -1,26 +1,21 @@ |
| 1 | /// Test StackIterator on 'freestanding' target. Based on unwind.zig. | |
| 1 | //! Test StackIterator on 'freestanding' target. Based on unwind.zig. | |
| 2 | ||
| 2 | 3 | const std = @import("std"); |
| 3 | const builtin = @import("builtin"); | |
| 4 | const debug = std.debug; | |
| 5 | 4 | |
| 6 | noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void { | |
| 5 | noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { | |
| 7 | 6 | expected[0] = @returnAddress(); |
| 8 | ||
| 9 | var it = debug.StackIterator.init(@returnAddress(), @frameAddress()); | |
| 10 | defer it.deinit(); | |
| 11 | ||
| 12 | // Save StackIterator's frame addresses into `unwound`: | |
| 13 | for (unwound) |*addr| { | |
| 14 | if (it.next()) |return_address| addr.* = return_address; | |
| 15 | } | |
| 7 | return std.debug.captureCurrentStackTrace(.{ | |
| 8 | .first_address = @returnAddress(), | |
| 9 | .allow_unsafe_unwind = true, | |
| 10 | }, addr_buf); | |
| 16 | 11 | } |
| 17 | 12 | |
| 18 | noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void { | |
| 13 | noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { | |
| 19 | 14 | expected[1] = @returnAddress(); |
| 20 | frame3(expected, unwound); | |
| 15 | return frame3(expected, addr_buf); | |
| 21 | 16 | } |
| 22 | 17 | |
| 23 | noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void { | |
| 18 | noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { | |
| 24 | 19 | expected[2] = @returnAddress(); |
| 25 | 20 | |
| 26 | 21 | // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding |
| ... | ... | @@ -28,37 +23,44 @@ noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void { |
| 28 | 23 | var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined; |
| 29 | 24 | _ = std.mem.doNotOptimizeAway(&pad); |
| 30 | 25 | |
| 31 | frame2(expected, unwound); | |
| 26 | return frame2(expected, addr_buf); | |
| 32 | 27 | } |
| 33 | 28 | |
| 34 | noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void { | |
| 29 | noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { | |
| 35 | 30 | expected[3] = @returnAddress(); |
| 36 | frame1(expected, unwound); | |
| 31 | return frame1(expected, addr_buf); | |
| 37 | 32 | } |
| 38 | 33 | |
| 39 | // No-OS entrypoint | |
| 34 | /// No-OS entrypoint | |
| 40 | 35 | export fn _start() callconv(.withStackAlign(.c, 1)) noreturn { |
| 41 | 36 | var expected: [4]usize = undefined; |
| 42 | var unwound: [4]usize = undefined; | |
| 43 | frame0(&expected, &unwound); | |
| 37 | var addr_buf: [4]usize = undefined; | |
| 38 | const trace = frame0(&expected, &addr_buf); | |
| 44 | 39 | |
| 45 | // Verify result (no std.testing in freestanding) | |
| 46 | var missed: c_int = 0; | |
| 47 | for (expected, unwound) |expectFA, actualFA| { | |
| 48 | if (expectFA != actualFA) { | |
| 49 | missed += 1; | |
| 50 | } | |
| 51 | } | |
| 40 | // Since we can't print from this freestanding test, we'll just use the exit | |
| 41 | // code to communicate error conditions. | |
| 42 | ||
| 43 | // Unlike `unwind.zig`, we can expect *exactly* 4 frames, as we are the | |
| 44 | // actual entry point and the frame pointer will be 0 on entry. | |
| 45 | if (trace.index != 4) exit(1); | |
| 46 | if (trace.instruction_addresses[0] != expected[0]) exit(2); | |
| 47 | if (trace.instruction_addresses[1] != expected[1]) exit(3); | |
| 48 | if (trace.instruction_addresses[2] != expected[2]) exit(4); | |
| 49 | if (trace.instruction_addresses[3] != expected[3]) exit(5); | |
| 50 | ||
| 51 | exit(0); | |
| 52 | } | |
| 52 | 53 | |
| 53 | // Need to compile with the target OS as "freestanding" or "other" to | |
| 54 | // exercise the StackIterator code, but when run as a regression test | |
| 55 | // need to actually exit. So assume we're running on x86_64-linux ... | |
| 54 | fn exit(code: u8) noreturn { | |
| 55 | // We are intentionally compiling with the target OS being "freestanding" to | |
| 56 | // exercise std.debug, but we still need to exit the process somehow; so do | |
| 57 | // the appropriate x86_64-linux syscall. | |
| 56 | 58 | asm volatile ( |
| 57 | 59 | \\movl $60, %%eax |
| 58 | 60 | \\syscall |
| 59 | 61 | : |
| 60 | : [missed] "{edi}" (missed), | |
| 62 | : [code] "{edi}" (code), | |
| 61 | 63 | : .{ .edi = true, .eax = true }); |
| 62 | 64 | |
| 63 | while (true) {} // unreached | |
| 65 | unreachable; | |
| 64 | 66 | } |
test/standalone/windows_bat_args/fuzz.zig+40-35| ... | ... | @@ -3,14 +3,14 @@ const builtin = @import("builtin"); |
| 3 | 3 | const Allocator = std.mem.Allocator; |
| 4 | 4 | |
| 5 | 5 | pub fn main() anyerror!void { |
| 6 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; | |
| 7 | defer if (gpa.deinit() == .leak) @panic("found memory leaks"); | |
| 8 | const allocator = gpa.allocator(); | |
| 6 | var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init; | |
| 7 | defer std.debug.assert(debug_alloc_inst.deinit() == .ok); | |
| 8 | const gpa = debug_alloc_inst.allocator(); | |
| 9 | 9 | |
| 10 | var it = try std.process.argsWithAllocator(allocator); | |
| 10 | var it = try std.process.argsWithAllocator(gpa); | |
| 11 | 11 | defer it.deinit(); |
| 12 | 12 | _ = it.next() orelse unreachable; // skip binary name |
| 13 | const child_exe_path = it.next() orelse unreachable; | |
| 13 | const child_exe_path_orig = it.next() orelse unreachable; | |
| 14 | 14 | |
| 15 | 15 | const iterations: u64 = iterations: { |
| 16 | 16 | const arg = it.next() orelse "0"; |
| ... | ... | @@ -42,58 +42,63 @@ pub fn main() anyerror!void { |
| 42 | 42 | try tmp.dir.setAsCwd(); |
| 43 | 43 | defer tmp.parent_dir.setAsCwd() catch {}; |
| 44 | 44 | |
| 45 | var buf = try std.array_list.Managed(u8).initCapacity(allocator, 128); | |
| 46 | defer buf.deinit(); | |
| 47 | try buf.appendSlice("@echo off\n"); | |
| 48 | try buf.append('"'); | |
| 49 | try buf.appendSlice(child_exe_path); | |
| 50 | try buf.append('"'); | |
| 45 | // `child_exe_path_orig` might be relative; make it relative to our new cwd. | |
| 46 | const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig }); | |
| 47 | defer gpa.free(child_exe_path); | |
| 48 | ||
| 49 | var buf: std.ArrayList(u8) = .empty; | |
| 50 | defer buf.deinit(gpa); | |
| 51 | try buf.print(gpa, | |
| 52 | \\@echo off | |
| 53 | \\"{s}" | |
| 54 | , .{child_exe_path}); | |
| 55 | // Trailing newline intentionally omitted above so we can add args. | |
| 51 | 56 | const preamble_len = buf.items.len; |
| 52 | 57 | |
| 53 | try buf.appendSlice(" %*"); | |
| 58 | try buf.appendSlice(gpa, " %*"); | |
| 54 | 59 | try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items }); |
| 55 | 60 | buf.shrinkRetainingCapacity(preamble_len); |
| 56 | 61 | |
| 57 | try buf.appendSlice(" %1 %2 %3 %4 %5 %6 %7 %8 %9"); | |
| 62 | try buf.appendSlice(gpa, " %1 %2 %3 %4 %5 %6 %7 %8 %9"); | |
| 58 | 63 | try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items }); |
| 59 | 64 | buf.shrinkRetainingCapacity(preamble_len); |
| 60 | 65 | |
| 61 | try buf.appendSlice(" \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\""); | |
| 66 | try buf.appendSlice(gpa, " \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\""); | |
| 62 | 67 | try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items }); |
| 63 | 68 | buf.shrinkRetainingCapacity(preamble_len); |
| 64 | 69 | |
| 65 | 70 | var i: u64 = 0; |
| 66 | 71 | while (iterations == 0 or i < iterations) { |
| 67 | const rand_arg = try randomArg(allocator, rand); | |
| 68 | defer allocator.free(rand_arg); | |
| 72 | const rand_arg = try randomArg(gpa, rand); | |
| 73 | defer gpa.free(rand_arg); | |
| 69 | 74 | |
| 70 | try testExec(allocator, &.{rand_arg}, null); | |
| 75 | try testExec(gpa, &.{rand_arg}, null); | |
| 71 | 76 | |
| 72 | 77 | i += 1; |
| 73 | 78 | } |
| 74 | 79 | } |
| 75 | 80 | |
| 76 | fn testExec(allocator: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 77 | try testExecBat(allocator, "args1.bat", args, env); | |
| 78 | try testExecBat(allocator, "args2.bat", args, env); | |
| 79 | try testExecBat(allocator, "args3.bat", args, env); | |
| 81 | fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 82 | try testExecBat(gpa, "args1.bat", args, env); | |
| 83 | try testExecBat(gpa, "args2.bat", args, env); | |
| 84 | try testExecBat(gpa, "args3.bat", args, env); | |
| 80 | 85 | } |
| 81 | 86 | |
| 82 | fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 83 | var argv = try std.array_list.Managed([]const u8).initCapacity(allocator, 1 + args.len); | |
| 84 | defer argv.deinit(); | |
| 85 | argv.appendAssumeCapacity(bat); | |
| 86 | argv.appendSliceAssumeCapacity(args); | |
| 87 | fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 88 | const argv = try gpa.alloc([]const u8, 1 + args.len); | |
| 89 | defer gpa.free(argv); | |
| 90 | argv[0] = bat; | |
| 91 | @memcpy(argv[1..], args); | |
| 87 | 92 | |
| 88 | 93 | const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat"); |
| 89 | 94 | |
| 90 | 95 | const result = try std.process.Child.run(.{ |
| 91 | .allocator = allocator, | |
| 96 | .allocator = gpa, | |
| 92 | 97 | .env_map = env, |
| 93 | .argv = argv.items, | |
| 98 | .argv = argv, | |
| 94 | 99 | }); |
| 95 | defer allocator.free(result.stdout); | |
| 96 | defer allocator.free(result.stderr); | |
| 100 | defer gpa.free(result.stdout); | |
| 101 | defer gpa.free(result.stderr); | |
| 97 | 102 | |
| 98 | 103 | try std.testing.expectEqualStrings("", result.stderr); |
| 99 | 104 | var it = std.mem.splitScalar(u8, result.stdout, '\x00'); |
| ... | ... | @@ -109,7 +114,7 @@ fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []co |
| 109 | 114 | } |
| 110 | 115 | } |
| 111 | 116 | |
| 112 | fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 { | |
| 117 | fn randomArg(gpa: Allocator, rand: std.Random) ![]const u8 { | |
| 113 | 118 | const Choice = enum { |
| 114 | 119 | backslash, |
| 115 | 120 | quote, |
| ... | ... | @@ -121,8 +126,8 @@ fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 { |
| 121 | 126 | }; |
| 122 | 127 | |
| 123 | 128 | const choices = rand.uintAtMostBiased(u16, 256); |
| 124 | var buf = try std.array_list.Managed(u8).initCapacity(allocator, choices); | |
| 125 | errdefer buf.deinit(); | |
| 129 | var buf: std.ArrayList(u8) = try .initCapacity(gpa, choices); | |
| 130 | errdefer buf.deinit(gpa); | |
| 126 | 131 | |
| 127 | 132 | var last_codepoint: u21 = 0; |
| 128 | 133 | for (0..choices) |_| { |
| ... | ... | @@ -149,12 +154,12 @@ fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 { |
| 149 | 154 | continue; |
| 150 | 155 | } |
| 151 | 156 | } |
| 152 | try buf.ensureUnusedCapacity(4); | |
| 157 | try buf.ensureUnusedCapacity(gpa, 4); | |
| 153 | 158 | const unused_slice = buf.unusedCapacitySlice(); |
| 154 | 159 | const len = std.unicode.wtf8Encode(codepoint, unused_slice) catch unreachable; |
| 155 | 160 | buf.items.len += len; |
| 156 | 161 | last_codepoint = codepoint; |
| 157 | 162 | } |
| 158 | 163 | |
| 159 | return buf.toOwnedSlice(); | |
| 164 | return buf.toOwnedSlice(gpa); | |
| 160 | 165 | } |
test/standalone/windows_bat_args/test.zig+77-72| ... | ... | @@ -1,14 +1,14 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | |
| 3 | 3 | pub fn main() anyerror!void { |
| 4 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; | |
| 5 | defer if (gpa.deinit() == .leak) @panic("found memory leaks"); | |
| 6 | const allocator = gpa.allocator(); | |
| 4 | var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init; | |
| 5 | defer std.debug.assert(debug_alloc_inst.deinit() == .ok); | |
| 6 | const gpa = debug_alloc_inst.allocator(); | |
| 7 | 7 | |
| 8 | var it = try std.process.argsWithAllocator(allocator); | |
| 8 | var it = try std.process.argsWithAllocator(gpa); | |
| 9 | 9 | defer it.deinit(); |
| 10 | 10 | _ = it.next() orelse unreachable; // skip binary name |
| 11 | const child_exe_path = it.next() orelse unreachable; | |
| 11 | const child_exe_path_orig = it.next() orelse unreachable; | |
| 12 | 12 | |
| 13 | 13 | var tmp = std.testing.tmpDir(.{}); |
| 14 | 14 | defer tmp.cleanup(); |
| ... | ... | @@ -16,62 +16,67 @@ pub fn main() anyerror!void { |
| 16 | 16 | try tmp.dir.setAsCwd(); |
| 17 | 17 | defer tmp.parent_dir.setAsCwd() catch {}; |
| 18 | 18 | |
| 19 | var buf = try std.array_list.Managed(u8).initCapacity(allocator, 128); | |
| 20 | defer buf.deinit(); | |
| 21 | try buf.appendSlice("@echo off\n"); | |
| 22 | try buf.append('"'); | |
| 23 | try buf.appendSlice(child_exe_path); | |
| 24 | try buf.append('"'); | |
| 19 | // `child_exe_path_orig` might be relative; make it relative to our new cwd. | |
| 20 | const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig }); | |
| 21 | defer gpa.free(child_exe_path); | |
| 22 | ||
| 23 | var buf: std.ArrayList(u8) = .empty; | |
| 24 | defer buf.deinit(gpa); | |
| 25 | try buf.print(gpa, | |
| 26 | \\@echo off | |
| 27 | \\"{s}" | |
| 28 | , .{child_exe_path}); | |
| 29 | // Trailing newline intentionally omitted above so we can add args. | |
| 25 | 30 | const preamble_len = buf.items.len; |
| 26 | 31 | |
| 27 | try buf.appendSlice(" %*"); | |
| 32 | try buf.appendSlice(gpa, " %*"); | |
| 28 | 33 | try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items }); |
| 29 | 34 | buf.shrinkRetainingCapacity(preamble_len); |
| 30 | 35 | |
| 31 | try buf.appendSlice(" %1 %2 %3 %4 %5 %6 %7 %8 %9"); | |
| 36 | try buf.appendSlice(gpa, " %1 %2 %3 %4 %5 %6 %7 %8 %9"); | |
| 32 | 37 | try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items }); |
| 33 | 38 | buf.shrinkRetainingCapacity(preamble_len); |
| 34 | 39 | |
| 35 | try buf.appendSlice(" \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\""); | |
| 40 | try buf.appendSlice(gpa, " \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\""); | |
| 36 | 41 | try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items }); |
| 37 | 42 | buf.shrinkRetainingCapacity(preamble_len); |
| 38 | 43 | |
| 39 | 44 | // Test cases are from https://github.com/rust-lang/rust/blob/master/tests/ui/std/windows-bat-args.rs |
| 40 | try testExecError(error.InvalidBatchScriptArg, allocator, &.{"\x00"}); | |
| 41 | try testExecError(error.InvalidBatchScriptArg, allocator, &.{"\n"}); | |
| 42 | try testExecError(error.InvalidBatchScriptArg, allocator, &.{"\r"}); | |
| 43 | try testExec(allocator, &.{ "a", "b" }, null); | |
| 44 | try testExec(allocator, &.{ "c is for cat", "d is for dog" }, null); | |
| 45 | try testExec(allocator, &.{ "\"", " \"" }, null); | |
| 46 | try testExec(allocator, &.{ "\\", "\\" }, null); | |
| 47 | try testExec(allocator, &.{">file.txt"}, null); | |
| 48 | try testExec(allocator, &.{"whoami.exe"}, null); | |
| 49 | try testExec(allocator, &.{"&a.exe"}, null); | |
| 50 | try testExec(allocator, &.{"&echo hello "}, null); | |
| 51 | try testExec(allocator, &.{ "&echo hello", "&whoami", ">file.txt" }, null); | |
| 52 | try testExec(allocator, &.{"!TMP!"}, null); | |
| 53 | try testExec(allocator, &.{"key=value"}, null); | |
| 54 | try testExec(allocator, &.{"\"key=value\""}, null); | |
| 55 | try testExec(allocator, &.{"key = value"}, null); | |
| 56 | try testExec(allocator, &.{"key=[\"value\"]"}, null); | |
| 57 | try testExec(allocator, &.{ "", "a=b" }, null); | |
| 58 | try testExec(allocator, &.{"key=\"foo bar\""}, null); | |
| 59 | try testExec(allocator, &.{"key=[\"my_value]"}, null); | |
| 60 | try testExec(allocator, &.{"key=[\"my_value\",\"other-value\"]"}, null); | |
| 61 | try testExec(allocator, &.{"key\\=value"}, null); | |
| 62 | try testExec(allocator, &.{"key=\"&whoami\""}, null); | |
| 63 | try testExec(allocator, &.{"key=\"value\"=5"}, null); | |
| 64 | try testExec(allocator, &.{"key=[\">file.txt\"]"}, null); | |
| 65 | try testExec(allocator, &.{"%hello"}, null); | |
| 66 | try testExec(allocator, &.{"%PATH%"}, null); | |
| 67 | try testExec(allocator, &.{"%%cd:~,%"}, null); | |
| 68 | try testExec(allocator, &.{"%PATH%PATH%"}, null); | |
| 69 | try testExec(allocator, &.{"\">file.txt"}, null); | |
| 70 | try testExec(allocator, &.{"abc\"&echo hello"}, null); | |
| 71 | try testExec(allocator, &.{"123\">file.txt"}, null); | |
| 72 | try testExec(allocator, &.{"\"&echo hello&whoami.exe"}, null); | |
| 73 | try testExec(allocator, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null); | |
| 74 | try testExec(allocator, &.{"&whoami.exe"}, null); | |
| 45 | try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\x00"}); | |
| 46 | try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\n"}); | |
| 47 | try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\r"}); | |
| 48 | try testExec(gpa, &.{ "a", "b" }, null); | |
| 49 | try testExec(gpa, &.{ "c is for cat", "d is for dog" }, null); | |
| 50 | try testExec(gpa, &.{ "\"", " \"" }, null); | |
| 51 | try testExec(gpa, &.{ "\\", "\\" }, null); | |
| 52 | try testExec(gpa, &.{">file.txt"}, null); | |
| 53 | try testExec(gpa, &.{"whoami.exe"}, null); | |
| 54 | try testExec(gpa, &.{"&a.exe"}, null); | |
| 55 | try testExec(gpa, &.{"&echo hello "}, null); | |
| 56 | try testExec(gpa, &.{ "&echo hello", "&whoami", ">file.txt" }, null); | |
| 57 | try testExec(gpa, &.{"!TMP!"}, null); | |
| 58 | try testExec(gpa, &.{"key=value"}, null); | |
| 59 | try testExec(gpa, &.{"\"key=value\""}, null); | |
| 60 | try testExec(gpa, &.{"key = value"}, null); | |
| 61 | try testExec(gpa, &.{"key=[\"value\"]"}, null); | |
| 62 | try testExec(gpa, &.{ "", "a=b" }, null); | |
| 63 | try testExec(gpa, &.{"key=\"foo bar\""}, null); | |
| 64 | try testExec(gpa, &.{"key=[\"my_value]"}, null); | |
| 65 | try testExec(gpa, &.{"key=[\"my_value\",\"other-value\"]"}, null); | |
| 66 | try testExec(gpa, &.{"key\\=value"}, null); | |
| 67 | try testExec(gpa, &.{"key=\"&whoami\""}, null); | |
| 68 | try testExec(gpa, &.{"key=\"value\"=5"}, null); | |
| 69 | try testExec(gpa, &.{"key=[\">file.txt\"]"}, null); | |
| 70 | try testExec(gpa, &.{"%hello"}, null); | |
| 71 | try testExec(gpa, &.{"%PATH%"}, null); | |
| 72 | try testExec(gpa, &.{"%%cd:~,%"}, null); | |
| 73 | try testExec(gpa, &.{"%PATH%PATH%"}, null); | |
| 74 | try testExec(gpa, &.{"\">file.txt"}, null); | |
| 75 | try testExec(gpa, &.{"abc\"&echo hello"}, null); | |
| 76 | try testExec(gpa, &.{"123\">file.txt"}, null); | |
| 77 | try testExec(gpa, &.{"\"&echo hello&whoami.exe"}, null); | |
| 78 | try testExec(gpa, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null); | |
| 79 | try testExec(gpa, &.{"&whoami.exe"}, null); | |
| 75 | 80 | |
| 76 | 81 | // Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution. |
| 77 | 82 | // In many Windows APIs (including CreateProcess), trailing space and . characters are stripped |
| ... | ... | @@ -89,17 +94,17 @@ pub fn main() anyerror!void { |
| 89 | 94 | // > "args1.bat .. " |
| 90 | 95 | // '"args1.bat .. "' is not recognized as an internal or external command, |
| 91 | 96 | // operable program or batch file. |
| 92 | try std.testing.expectError(error.FileNotFound, testExecBat(allocator, "args1.bat .. ", &.{"abc"}, null)); | |
| 97 | try std.testing.expectError(error.FileNotFound, testExecBat(gpa, "args1.bat .. ", &.{"abc"}, null)); | |
| 93 | 98 | const absolute_with_trailing = blk: { |
| 94 | const absolute_path = try std.fs.realpathAlloc(allocator, "args1.bat"); | |
| 95 | defer allocator.free(absolute_path); | |
| 96 | break :blk try std.mem.concat(allocator, u8, &.{ absolute_path, " .. " }); | |
| 99 | const absolute_path = try std.fs.realpathAlloc(gpa, "args1.bat"); | |
| 100 | defer gpa.free(absolute_path); | |
| 101 | break :blk try std.mem.concat(gpa, u8, &.{ absolute_path, " .. " }); | |
| 97 | 102 | }; |
| 98 | defer allocator.free(absolute_with_trailing); | |
| 99 | try std.testing.expectError(error.FileNotFound, testExecBat(allocator, absolute_with_trailing, &.{"abc"}, null)); | |
| 103 | defer gpa.free(absolute_with_trailing); | |
| 104 | try std.testing.expectError(error.FileNotFound, testExecBat(gpa, absolute_with_trailing, &.{"abc"}, null)); | |
| 100 | 105 | |
| 101 | 106 | var env = env: { |
| 102 | var env = try std.process.getEnvMap(allocator); | |
| 107 | var env = try std.process.getEnvMap(gpa); | |
| 103 | 108 | errdefer env.deinit(); |
| 104 | 109 | // No escaping |
| 105 | 110 | try env.put("FOO", "123"); |
| ... | ... | @@ -110,37 +115,37 @@ pub fn main() anyerror!void { |
| 110 | 115 | break :env env; |
| 111 | 116 | }; |
| 112 | 117 | defer env.deinit(); |
| 113 | try testExec(allocator, &.{"%FOO%"}, &env); | |
| 118 | try testExec(gpa, &.{"%FOO%"}, &env); | |
| 114 | 119 | |
| 115 | 120 | // Ensure that none of the `>file.txt`s have caused file.txt to be created |
| 116 | 121 | try std.testing.expectError(error.FileNotFound, tmp.dir.access("file.txt", .{})); |
| 117 | 122 | } |
| 118 | 123 | |
| 119 | fn testExecError(err: anyerror, allocator: std.mem.Allocator, args: []const []const u8) !void { | |
| 120 | return std.testing.expectError(err, testExec(allocator, args, null)); | |
| 124 | fn testExecError(err: anyerror, gpa: std.mem.Allocator, args: []const []const u8) !void { | |
| 125 | return std.testing.expectError(err, testExec(gpa, args, null)); | |
| 121 | 126 | } |
| 122 | 127 | |
| 123 | fn testExec(allocator: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 124 | try testExecBat(allocator, "args1.bat", args, env); | |
| 125 | try testExecBat(allocator, "args2.bat", args, env); | |
| 126 | try testExecBat(allocator, "args3.bat", args, env); | |
| 128 | fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 129 | try testExecBat(gpa, "args1.bat", args, env); | |
| 130 | try testExecBat(gpa, "args2.bat", args, env); | |
| 131 | try testExecBat(gpa, "args3.bat", args, env); | |
| 127 | 132 | } |
| 128 | 133 | |
| 129 | fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 130 | var argv = try std.array_list.Managed([]const u8).initCapacity(allocator, 1 + args.len); | |
| 131 | defer argv.deinit(); | |
| 132 | argv.appendAssumeCapacity(bat); | |
| 133 | argv.appendSliceAssumeCapacity(args); | |
| 134 | fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 135 | const argv = try gpa.alloc([]const u8, 1 + args.len); | |
| 136 | defer gpa.free(argv); | |
| 137 | argv[0] = bat; | |
| 138 | @memcpy(argv[1..], args); | |
| 134 | 139 | |
| 135 | 140 | const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat"); |
| 136 | 141 | |
| 137 | 142 | const result = try std.process.Child.run(.{ |
| 138 | .allocator = allocator, | |
| 143 | .allocator = gpa, | |
| 139 | 144 | .env_map = env, |
| 140 | .argv = argv.items, | |
| 145 | .argv = argv, | |
| 141 | 146 | }); |
| 142 | defer allocator.free(result.stdout); | |
| 143 | defer allocator.free(result.stderr); | |
| 147 | defer gpa.free(result.stdout); | |
| 148 | defer gpa.free(result.stderr); | |
| 144 | 149 | |
| 145 | 150 | try std.testing.expectEqualStrings("", result.stderr); |
| 146 | 151 | var it = std.mem.splitScalar(u8, result.stdout, '\x00'); |
test/tests.zig+72-7| ... | ... | @@ -6,11 +6,13 @@ const OptimizeMode = std.builtin.OptimizeMode; |
| 6 | 6 | const Step = std.Build.Step; |
| 7 | 7 | |
| 8 | 8 | // Cases |
| 9 | const error_traces = @import("error_traces.zig"); | |
| 9 | 10 | const stack_traces = @import("stack_traces.zig"); |
| 10 | 11 | const llvm_ir = @import("llvm_ir.zig"); |
| 11 | 12 | const libc = @import("libc.zig"); |
| 12 | 13 | |
| 13 | 14 | // Implementations |
| 15 | pub const ErrorTracesContext = @import("src/ErrorTrace.zig"); | |
| 14 | 16 | pub const StackTracesContext = @import("src/StackTrace.zig"); |
| 15 | 17 | pub const DebuggerContext = @import("src/Debugger.zig"); |
| 16 | 18 | pub const LlvmIrContext = @import("src/LlvmIr.zig"); |
| ... | ... | @@ -1857,28 +1859,61 @@ const c_abi_targets = blk: { |
| 1857 | 1859 | }; |
| 1858 | 1860 | }; |
| 1859 | 1861 | |
| 1862 | /// For stack trace tests, we only test native, because external executors are pretty unreliable at | |
| 1863 | /// stack tracing. However, if there's a 32-bit equivalent target which the host can trivially run, | |
| 1864 | /// we may as well at least test that! | |
| 1865 | fn nativeAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget { | |
| 1866 | const host = b.graph.host.result; | |
| 1867 | const only_native = (&b.graph.host)[0..1]; | |
| 1868 | if (skip_non_native) return only_native; | |
| 1869 | const arch32: std.Target.Cpu.Arch = switch (host.os.tag) { | |
| 1870 | .windows => switch (host.cpu.arch) { | |
| 1871 | .x86_64 => .x86, | |
| 1872 | .aarch64 => .thumb, | |
| 1873 | .aarch64_be => .thumbeb, | |
| 1874 | else => return only_native, | |
| 1875 | }, | |
| 1876 | .freebsd => switch (host.cpu.arch) { | |
| 1877 | .aarch64 => .arm, | |
| 1878 | .aarch64_be => .armeb, | |
| 1879 | else => return only_native, | |
| 1880 | }, | |
| 1881 | .linux, .netbsd => switch (host.cpu.arch) { | |
| 1882 | .x86_64 => .x86, | |
| 1883 | .aarch64 => .arm, | |
| 1884 | .aarch64_be => .armeb, | |
| 1885 | else => return only_native, | |
| 1886 | }, | |
| 1887 | else => return only_native, | |
| 1888 | }; | |
| 1889 | return b.graph.arena.dupe(std.Build.ResolvedTarget, &.{ | |
| 1890 | b.graph.host, | |
| 1891 | b.resolveTargetQuery(.{ .cpu_arch = arch32, .os_tag = host.os.tag }), | |
| 1892 | }) catch @panic("OOM"); | |
| 1893 | } | |
| 1894 | ||
| 1860 | 1895 | pub fn addStackTraceTests( |
| 1861 | 1896 | b: *std.Build, |
| 1862 | 1897 | test_filters: []const []const u8, |
| 1863 | optimize_modes: []const OptimizeMode, | |
| 1898 | skip_non_native: bool, | |
| 1864 | 1899 | ) *Step { |
| 1865 | const check_exe = b.addExecutable(.{ | |
| 1866 | .name = "check-stack-trace", | |
| 1900 | const convert_exe = b.addExecutable(.{ | |
| 1901 | .name = "convert-stack-trace", | |
| 1867 | 1902 | .root_module = b.createModule(.{ |
| 1868 | .root_source_file = b.path("test/src/check-stack-trace.zig"), | |
| 1903 | .root_source_file = b.path("test/src/convert-stack-trace.zig"), | |
| 1869 | 1904 | .target = b.graph.host, |
| 1870 | 1905 | .optimize = .Debug, |
| 1871 | 1906 | }), |
| 1872 | 1907 | }); |
| 1873 | 1908 | |
| 1874 | 1909 | const cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); |
| 1910 | ||
| 1875 | 1911 | cases.* = .{ |
| 1876 | 1912 | .b = b, |
| 1877 | 1913 | .step = b.step("test-stack-traces", "Run the stack trace tests"), |
| 1878 | .test_index = 0, | |
| 1879 | 1914 | .test_filters = test_filters, |
| 1880 | .optimize_modes = optimize_modes, | |
| 1881 | .check_exe = check_exe, | |
| 1915 | .targets = nativeAndCompatible32bit(b, skip_non_native), | |
| 1916 | .convert_exe = convert_exe, | |
| 1882 | 1917 | }; |
| 1883 | 1918 | |
| 1884 | 1919 | stack_traces.addCases(cases); |
| ... | ... | @@ -1886,6 +1921,36 @@ pub fn addStackTraceTests( |
| 1886 | 1921 | return cases.step; |
| 1887 | 1922 | } |
| 1888 | 1923 | |
| 1924 | pub fn addErrorTraceTests( | |
| 1925 | b: *std.Build, | |
| 1926 | test_filters: []const []const u8, | |
| 1927 | optimize_modes: []const OptimizeMode, | |
| 1928 | skip_non_native: bool, | |
| 1929 | ) *Step { | |
| 1930 | const convert_exe = b.addExecutable(.{ | |
| 1931 | .name = "convert-stack-trace", | |
| 1932 | .root_module = b.createModule(.{ | |
| 1933 | .root_source_file = b.path("test/src/convert-stack-trace.zig"), | |
| 1934 | .target = b.graph.host, | |
| 1935 | .optimize = .Debug, | |
| 1936 | }), | |
| 1937 | }); | |
| 1938 | ||
| 1939 | const cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM"); | |
| 1940 | cases.* = .{ | |
| 1941 | .b = b, | |
| 1942 | .step = b.step("test-error-traces", "Run the error trace tests"), | |
| 1943 | .test_filters = test_filters, | |
| 1944 | .targets = nativeAndCompatible32bit(b, skip_non_native), | |
| 1945 | .optimize_modes = optimize_modes, | |
| 1946 | .convert_exe = convert_exe, | |
| 1947 | }; | |
| 1948 | ||
| 1949 | error_traces.addCases(cases); | |
| 1950 | ||
| 1951 | return cases.step; | |
| 1952 | } | |
| 1953 | ||
| 1889 | 1954 | fn compilerHasPackageManager(b: *std.Build) bool { |
| 1890 | 1955 | // We can only use dependencies if the compiler was built with support for package management. |
| 1891 | 1956 | // (zig2 doesn't support it, but we still need to construct a build graph to build stage3.) |