authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 20:24:58+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-09-30 20:24:58+01:00
logb64535e3c8770db9bf314fc14d1b2b276b864f72
tree4a6f90029d8feff983889a133326fbe2a4e3465d
parent7adb15892eada307b43a6a7844d3e51720f8992d
parent1120546f72405ac263dce7414eb71ca4e6c96fc8
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #25227 from mlugg/capture-stack

The Great `std.debug` Refactor

90 files changed, 8072 insertions(+), 7886 deletions(-)

build.zig+2-1
...@@ -563,7 +563,8 @@ pub fn build(b: *std.Build) !void {...@@ -563,7 +563,8 @@ pub fn build(b: *std.Build) !void {
563 .skip_release = skip_release,563 .skip_release = skip_release,
564 }));564 }));
565 test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, enable_ios_sdk, enable_symlinks_windows));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 test_step.dependOn(tests.addCliTests(b));568 test_step.dependOn(tests.addCliTests(b));
568 if (tests.addDebuggerTests(b, .{569 if (tests.addDebuggerTests(b, .{
569 .test_filters = test_filters,570 .test_filters = test_filters,
lib/compiler/test_runner.zig+4-4
...@@ -140,7 +140,7 @@ fn mainServer() !void {...@@ -140,7 +140,7 @@ fn mainServer() !void {
140 else => {140 else => {
141 fail = true;141 fail = true;
142 if (@errorReturnTrace()) |trace| {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,7 +182,7 @@ fn mainServer() !void {
182 error.SkipZigTest => return,182 error.SkipZigTest => return,
183 else => {183 else => {
184 if (@errorReturnTrace()) |trace| {184 if (@errorReturnTrace()) |trace| {
185 std.debug.dumpStackTrace(trace.*);185 std.debug.dumpStackTrace(trace);
186 }186 }
187 std.debug.print("failed with error.{t}\n", .{err});187 std.debug.print("failed with error.{t}\n", .{err});
188 std.process.exit(1);188 std.process.exit(1);
...@@ -261,7 +261,7 @@ fn mainTerminal() void {...@@ -261,7 +261,7 @@ fn mainTerminal() void {
261 std.debug.print("FAIL ({t})\n", .{err});261 std.debug.print("FAIL ({t})\n", .{err});
262 }262 }
263 if (@errorReturnTrace()) |trace| {263 if (@errorReturnTrace()) |trace| {
264 std.debug.dumpStackTrace(trace.*);264 std.debug.dumpStackTrace(trace);
265 }265 }
266 test_node.end();266 test_node.end();
267 },267 },
...@@ -398,7 +398,7 @@ pub fn fuzz(...@@ -398,7 +398,7 @@ pub fn fuzz(
398 error.SkipZigTest => return,398 error.SkipZigTest => return,
399 else => {399 else => {
400 std.debug.lockStdErr();400 std.debug.lockStdErr();
401 if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace.*);401 if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace);
402 std.debug.print("failed with error.{t}\n", .{err});402 std.debug.print("failed with error.{t}\n", .{err});
403 std.process.exit(1);403 std.process.exit(1);
404 },404 },
lib/std/Build.zig+1-1
...@@ -2195,7 +2195,7 @@ fn dependencyInner(...@@ -2195,7 +2195,7 @@ fn dependencyInner(
2195 sub_builder.runBuild(bz) catch @panic("unhandled error");2195 sub_builder.runBuild(bz) catch @panic("unhandled error");
21962196
2197 if (sub_builder.validateUserInputDidItFail()) {2197 if (sub_builder.validateUserInputDidItFail()) {
2198 std.debug.dumpCurrentStackTrace(@returnAddress());2198 std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
2199 }2199 }
2200 }2200 }
22012201
lib/std/Build/Step.zig+5-33
...@@ -60,7 +60,7 @@ test_results: TestResults,...@@ -60,7 +60,7 @@ test_results: TestResults,
6060
61/// The return address associated with creation of this step that can be useful61/// The return address associated with creation of this step that can be useful
62/// to print along with debugging messages.62/// to print along with debugging messages.
63debug_stack_trace: []usize,63debug_stack_trace: std.builtin.StackTrace,
6464
65pub const TestResults = struct {65pub const TestResults = struct {
66 fail_count: u32 = 0,66 fail_count: u32 = 0,
...@@ -220,16 +220,9 @@ pub fn init(options: StepOptions) Step {...@@ -220,16 +220,9 @@ pub fn init(options: StepOptions) Step {
220 .state = .precheck_unstarted,220 .state = .precheck_unstarted,
221 .max_rss = options.max_rss,221 .max_rss = options.max_rss,
222 .debug_stack_trace = blk: {222 .debug_stack_trace = blk: {
223 if (!std.debug.sys_can_stack_trace) break :blk &.{};223 const addr_buf = arena.alloc(usize, options.owner.debug_stack_frames_count) catch @panic("OOM");
224 const addresses = arena.alloc(usize, options.owner.debug_stack_frames_count) catch @panic("OOM");
225 @memset(addresses, 0);
226 const first_ret_addr = options.first_ret_addr orelse @returnAddress();224 const first_ret_addr = options.first_ret_addr orelse @returnAddress();
227 var stack_trace = std.builtin.StackTrace{225 break :blk std.debug.captureCurrentStackTrace(.{ .first_address = first_ret_addr }, addr_buf);
228 .instruction_addresses = addresses,
229 .index = 0,
230 };
231 std.debug.captureStackTrace(first_ret_addr, &stack_trace);
232 break :blk addresses;
233 },226 },
234 .result_error_msgs = .{},227 .result_error_msgs = .{},
235 .result_error_bundle = std.zig.ErrorBundle.empty,228 .result_error_bundle = std.zig.ErrorBundle.empty,
...@@ -282,18 +275,6 @@ pub fn dependOn(step: *Step, other: *Step) void {...@@ -282,18 +275,6 @@ pub fn dependOn(step: *Step, other: *Step) void {
282 step.dependencies.append(other) catch @panic("OOM");275 step.dependencies.append(other) catch @panic("OOM");
283}276}
284277
285pub 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
297fn makeNoOp(step: *Step, options: MakeOptions) anyerror!void {278fn makeNoOp(step: *Step, options: MakeOptions) anyerror!void {
298 _ = options;279 _ = options;
299280
...@@ -315,18 +296,9 @@ pub fn cast(step: *Step, comptime T: type) ?*T {...@@ -315,18 +296,9 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
315296
316/// For debugging purposes, prints identifying information about this Step.297/// For debugging purposes, prints identifying information about this Step.
317pub fn dump(step: *Step, w: *std.Io.Writer, tty_config: std.Io.tty.Config) void {298pub fn dump(step: *Step, w: *std.Io.Writer, tty_config: std.Io.tty.Config) void {
318 const debug_info = std.debug.getSelfDebugInfo() catch |err| {299 if (step.debug_stack_trace.instruction_addresses.len > 0) {
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| {
325 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};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| {301 std.debug.writeStackTrace(&step.debug_stack_trace, w, tty_config) catch {};
327 w.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch {};
328 return;
329 };
330 } else {302 } else {
331 const field = "debug_stack_frames_count";303 const field = "debug_stack_frames_count";
332 comptime assert(@hasField(Build, field));304 comptime assert(@hasField(Build, field));
lib/std/Build/Step/CheckObject.zig+16-22
...@@ -1097,16 +1097,10 @@ const MachODumper = struct {...@@ -1097,16 +1097,10 @@ const MachODumper = struct {
10971097
1098 for (ctx.symtab.items) |sym| {1098 for (ctx.symtab.items) |sym| {
1099 const sym_name = ctx.getString(sym.n_strx);1099 const sym_name = ctx.getString(sym.n_strx);
1100 if (sym.stab()) {1100 if (sym.n_type.bits.is_stab != 0) {
1101 const tt = switch (sym.n_type) {1101 const tt = switch (sym.n_type.stab) {
1102 macho.N_SO => "SO",1102 _ => "UNKNOWN STAB",
1103 macho.N_OSO => "OSO",1103 else => @tagName(sym.n_type.stab),
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",
1110 };1104 };
1111 try writer.print("{x}", .{sym.n_value});1105 try writer.print("{x}", .{sym.n_value});
1112 if (sym.n_sect > 0) {1106 if (sym.n_sect > 0) {
...@@ -1114,27 +1108,27 @@ const MachODumper = struct {...@@ -1114,27 +1108,27 @@ const MachODumper = struct {
1114 try writer.print(" ({s},{s})", .{ sect.segName(), sect.sectName() });1108 try writer.print(" ({s},{s})", .{ sect.segName(), sect.sectName() });
1115 }1109 }
1116 try writer.print(" {s} (stab) {s}\n", .{ tt, sym_name });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 const sect = ctx.sections.items[sym.n_sect - 1];1112 const sect = ctx.sections.items[sym.n_sect - 1];
1119 try writer.print("{x} ({s},{s})", .{1113 try writer.print("{x} ({s},{s})", .{
1120 sym.n_value,1114 sym.n_value,
1121 sect.segName(),1115 sect.segName(),
1122 sect.sectName(),1116 sect.sectName(),
1123 });1117 });
1124 if (sym.n_desc & macho.REFERENCED_DYNAMICALLY != 0) try writer.writeAll(" [referenced dynamically]");1118 if (sym.n_desc.referenced_dynamically) try writer.writeAll(" [referenced dynamically]");
1125 if (sym.weakDef()) try writer.writeAll(" weak");1119 if (sym.n_desc.weak_def_or_ref_to_weak) try writer.writeAll(" weak");
1126 if (sym.weakRef()) try writer.writeAll(" weakref");1120 if (sym.n_desc.weak_ref) try writer.writeAll(" weakref");
1127 if (sym.ext()) {1121 if (sym.n_type.bits.ext) {
1128 if (sym.pext()) try writer.writeAll(" private");1122 if (sym.n_type.bits.pext) try writer.writeAll(" private");
1129 try writer.writeAll(" external");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 try writer.print(" {s}\n", .{sym_name});1125 try writer.print(" {s}\n", .{sym_name});
1132 } else if (sym.tentative()) {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 try writer.print(" 0x{x:0>16} (common) (alignment 2^{d})", .{ sym.n_value, alignment });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 try writer.print(" {s}\n", .{sym_name});1130 try writer.print(" {s}\n", .{sym_name});
1137 } else if (sym.undf()) {1131 } else if (sym.n_type.bits.type == .undf) {
1138 const ordinal = @divFloor(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER);1132 const ordinal = @divFloor(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER);
1139 const import_name = blk: {1133 const import_name = blk: {
1140 if (ordinal <= 0) {1134 if (ordinal <= 0) {
...@@ -1153,8 +1147,8 @@ const MachODumper = struct {...@@ -1153,8 +1147,8 @@ const MachODumper = struct {
1153 break :blk basename[0..ext];1147 break :blk basename[0..ext];
1154 };1148 };
1155 try writer.writeAll("(undefined)");1149 try writer.writeAll("(undefined)");
1156 if (sym.weakRef()) try writer.writeAll(" weakref");1150 if (sym.n_desc.weak_ref) try writer.writeAll(" weakref");
1157 if (sym.ext()) try writer.writeAll(" external");1151 if (sym.n_type.bits.ext) try writer.writeAll(" external");
1158 try writer.print(" {s} (from {s})\n", .{1152 try writer.print(" {s} (from {s})\n", .{
1159 sym_name,1153 sym_name,
1160 import_name,1154 import_name,
lib/std/Thread.zig+1-1
...@@ -530,7 +530,7 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {...@@ -530,7 +530,7 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
530 @call(.auto, f, args) catch |err| {530 @call(.auto, f, args) catch |err| {
531 std.debug.print("error: {s}\n", .{@errorName(err)});531 std.debug.print("error: {s}\n", .{@errorName(err)});
532 if (@errorReturnTrace()) |trace| {532 if (@errorReturnTrace()) |trace| {
533 std.debug.dumpStackTrace(trace.*);533 std.debug.dumpStackTrace(trace);
534 }534 }
535 };535 };
536536
lib/std/builtin.zig+5-8
...@@ -38,20 +38,17 @@ pub const StackTrace = struct {...@@ -38,20 +38,17 @@ pub const StackTrace = struct {
38 index: usize,38 index: usize,
39 instruction_addresses: []usize,39 instruction_addresses: []usize,
4040
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 // TODO: re-evaluate whether to use format() methods at all.42 // TODO: re-evaluate whether to use format() methods at all.
43 // Until then, avoid an error when using GeneralPurposeAllocator with WebAssembly43 // Until then, avoid an error when using GeneralPurposeAllocator with WebAssembly
44 // where it tries to call detectTTYConfig here.44 // where it tries to call detectTTYConfig here.
45 if (builtin.os.tag == .freestanding) return;45 if (builtin.os.tag == .freestanding) return;
4646
47 const debug_info = std.debug.getSelfDebugInfo() catch |err| {47 // TODO: why on earth are we using stderr's ttyconfig?
48 return writer.print("\nUnable to print stack trace: Unable to open debug info: {s}\n", .{@errorName(err)});48 // If we want colored output, we should just make a formatter out of `writeStackTrace`.
49 };49 const tty_config = std.Io.tty.detectConfig(.stderr());
50 const tty_config = std.Io.tty.detectConfig(std.fs.File.stderr());
51 try writer.writeAll("\n");50 try writer.writeAll("\n");
52 std.debug.writeStackTrace(self, writer, debug_info, tty_config) catch |err| {51 try std.debug.writeStackTrace(st, writer, tty_config);
53 try writer.print("Unable to print stack trace: {s}\n", .{@errorName(err)});
54 };
55 }52 }
56};53};
5754
lib/std/c.zig+3-207
...@@ -7035,205 +7035,6 @@ pub const timezone = switch (native_os) {...@@ -7035,205 +7035,6 @@ pub const timezone = switch (native_os) {
7035 else => void,7035 else => void,
7036};7036};
70377037
7038pub 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};
7102pub 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
7237pub const user_desc = switch (native_os) {7038pub const user_desc = switch (native_os) {
7238 .linux => linux.user_desc,7039 .linux => linux.user_desc,
7239 else => void,7040 else => void,
...@@ -11193,6 +10994,9 @@ pub extern "c" fn dlclose(handle: *anyopaque) c_int;...@@ -11193,6 +10994,9 @@ pub extern "c" fn dlclose(handle: *anyopaque) c_int;
11193pub extern "c" fn dlsym(handle: ?*anyopaque, symbol: [*:0]const u8) ?*anyopaque;10994pub extern "c" fn dlsym(handle: ?*anyopaque, symbol: [*:0]const u8) ?*anyopaque;
11194pub extern "c" fn dlerror() ?[*:0]u8;10995pub extern "c" fn dlerror() ?[*:0]u8;
1119510996
10997pub const dladdr = if (native_os.isDarwin()) darwin.dladdr else {};
10998pub const dl_info = if (native_os.isDarwin()) darwin.dl_info else {};
10999
11196pub extern "c" fn sync() void;11000pub extern "c" fn sync() void;
11197pub extern "c" fn syncfs(fd: c_int) c_int;11001pub extern "c" fn syncfs(fd: c_int) c_int;
11198pub extern "c" fn fsync(fd: c_int) c_int;11002pub extern "c" fn fsync(fd: c_int) c_int;
...@@ -11238,13 +11042,6 @@ pub const LC = enum(c_int) {...@@ -11238,13 +11042,6 @@ pub const LC = enum(c_int) {
1123811042
11239pub extern "c" fn setlocale(category: LC, locale: ?[*:0]const u8) ?[*:0]const u8;11043pub extern "c" fn setlocale(category: LC, locale: ?[*:0]const u8) ?[*:0]const u8;
1124011044
11241pub 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
11248pub const max_align_t = if (native_abi == .msvc or native_abi == .itanium)11045pub const max_align_t = if (native_abi == .msvc or native_abi == .itanium)
11249 f6411046 f64
11250else if (native_os.isDarwin())11047else if (native_os.isDarwin())
...@@ -11668,7 +11465,6 @@ const private = struct {...@@ -11668,7 +11465,6 @@ const private = struct {
11668 extern "c" fn shm_open(name: [*:0]const u8, flag: c_int, mode: mode_t) c_int;11465 extern "c" fn shm_open(name: [*:0]const u8, flag: c_int, mode: mode_t) c_int;
1166911466
11670 extern "c" fn pthread_setname_np(thread: pthread_t, name: [*:0]const u8) c_int;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;
1167211468
11673 extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize;11469 extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize;
11674 extern "c" fn getentropy(buffer: [*]u8, size: usize) c_int;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,113 +348,20 @@ pub const VM = struct {
348348
349pub const exception_type_t = c_int;349pub const exception_type_t = c_int;
350350
351pub 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
365pub 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
380pub 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
422pub const neon_state = extern struct {
423 q: [32]u128,
424 fpsr: u32,
425 fpcr: u32,
426};
427
428pub 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
449pub const stmm_reg = [16]u8;
450pub const xmm_reg = [16]u8;
451
452pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32;351pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32;
453pub extern "c" fn _NSGetExecutablePath(buf: [*:0]u8, bufsize: *u32) c_int;352pub extern "c" fn _NSGetExecutablePath(buf: [*:0]u8, bufsize: *u32) c_int;
454pub extern "c" fn _dyld_image_count() u32;353pub extern "c" fn _dyld_image_count() u32;
455pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header;354pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header;
456pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize;355pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize;
457pub extern "c" fn _dyld_get_image_name(image_index: u32) [*:0]const u8;356pub extern "c" fn _dyld_get_image_name(image_index: u32) [*:0]const u8;
357pub extern "c" fn dladdr(addr: *const anyopaque, info: *dl_info) c_int;
358
359pub const dl_info = extern struct {
360 fname: [*:0]const u8,
361 fbase: *anyopaque,
362 sname: ?[*:0]const u8,
363 saddr: ?*anyopaque,
364};
458365
459pub const COPYFILE = packed struct(u32) {366pub const COPYFILE = packed struct(u32) {
460 ACL: bool = false,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,46 +13,6 @@ pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: caddr_t, data: c_int)
13pub extern "c" fn umtx_sleep(ptr: *const volatile c_int, value: c_int, timeout: c_int) c_int;13pub extern "c" fn umtx_sleep(ptr: *const volatile c_int, value: c_int, timeout: c_int) c_int;
14pub extern "c" fn umtx_wakeup(ptr: *const volatile c_int, count: c_int) c_int;14pub extern "c" fn umtx_wakeup(ptr: *const volatile c_int, count: c_int) c_int;
1515
16pub 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
54pub const register_t = isize;
55
56pub const E = enum(u16) {16pub const E = enum(u16) {
57 /// No error occurred.17 /// No error occurred.
58 SUCCESS = 0,18 SUCCESS = 0,
lib/std/c/haiku.zig-263
...@@ -273,269 +273,6 @@ pub const E = enum(i32) {...@@ -273,269 +273,6 @@ pub const E = enum(i32) {
273273
274pub const status_t = i32;274pub const status_t = i32;
275275
276pub 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
539pub const DirEnt = extern struct {276pub const DirEnt = extern struct {
540 /// device277 /// device
541 dev: dev_t,278 dev: dev_t,
lib/std/c/openbsd.zig-47
...@@ -144,53 +144,6 @@ pub const TCIO = enum(u32) {...@@ -144,53 +144,6 @@ pub const TCIO = enum(u32) {
144 ION = 4,144 ION = 4,
145};145};
146146
147pub 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
194pub const E = enum(u16) {147pub const E = enum(u16) {
195 /// No error occurred.148 /// No error occurred.
196 SUCCESS = 0,149 SUCCESS = 0,
lib/std/coff.zig+10-9
...@@ -1083,26 +1083,27 @@ pub const Coff = struct {...@@ -1083,26 +1083,27 @@ pub const Coff = struct {
1083 age: u32 = undefined,1083 age: u32 = undefined,
10841084
1085 // The lifetime of `data` must be longer than the lifetime of the returned Coff1085 // 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 const pe_pointer_offset = 0x3C;1087 const pe_pointer_offset = 0x3C;
1088 const pe_magic = "PE\x00\x00";1088 const pe_magic = "PE\x00\x00";
10891089
1090 var reader: std.Io.Reader = .fixed(data);1090 if (data.len < pe_pointer_offset + 4) return error.EndOfStream;
1091 reader.seek = pe_pointer_offset;1091 const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little);
1092 const coff_header_offset = try reader.takeInt(u32, .little);1092 if (data.len < header_offset + 4) return error.EndOfStream;
1093 reader.seek = coff_header_offset;1093 const is_image = mem.eql(u8, data[header_offset..][0..4], pe_magic);
1094 const is_image = mem.eql(u8, pe_magic, try reader.takeArray(4));
10951094
1096 var coff = @This(){1095 const coff: Coff = .{
1097 .data = data,1096 .data = data,
1098 .is_image = is_image,1097 .is_image = is_image,
1099 .is_loaded = is_loaded,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 };
11021104
1103 // Do some basic validation upfront1105 // Do some basic validation upfront
1104 if (is_image) {1106 if (is_image) {
1105 coff.coff_header_offset = coff.coff_header_offset + 4;
1106 const coff_header = coff.getCoffHeader();1107 const coff_header = coff.getCoffHeader();
1107 if (coff_header.size_of_optional_header == 0) return error.MissingPEHeader;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,24 +1,102 @@
1const builtin = @import("builtin");
2const std = @import("std.zig");1const std = @import("std.zig");
3const math = std.math;2const math = std.math;
4const mem = std.mem;3const mem = std.mem;
5const posix = std.posix;4const posix = std.posix;
6const fs = std.fs;5const fs = std.fs;
7const testing = std.testing;6const testing = std.testing;
8const root = @import("root");7const Allocator = mem.Allocator;
9const File = std.fs.File;8const File = std.fs.File;
10const windows = std.os.windows;9const windows = std.os.windows;
11const native_arch = builtin.cpu.arch;
12const native_os = builtin.os.tag;
13const native_endian = native_arch.endian();
14const Writer = std.Io.Writer;10const Writer = std.Io.Writer;
15const tty = std.Io.tty;11const tty = std.Io.tty;
1612
13const builtin = @import("builtin");
14const native_arch = builtin.cpu.arch;
15const native_os = builtin.os.tag;
16
17const root = @import("root");
18
17pub const Dwarf = @import("debug/Dwarf.zig");19pub const Dwarf = @import("debug/Dwarf.zig");
18pub const Pdb = @import("debug/Pdb.zig");20pub const Pdb = @import("debug/Pdb.zig");
19pub const SelfInfo = @import("debug/SelfInfo.zig");21pub const ElfFile = @import("debug/ElfFile.zig");
20pub const Info = @import("debug/Info.zig");22pub const Info = @import("debug/Info.zig");
21pub const Coverage = @import("debug/Coverage.zig");23pub const Coverage = @import("debug/Coverage.zig");
24pub 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/// ```
62pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo"))
63 root.debug.SelfInfo
64else 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
88pub 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};
22100
23pub const simple_panic = @import("debug/simple_panic.zig");101pub const simple_panic = @import("debug/simple_panic.zig");
24pub const no_panic = @import("debug/no_panic.zig");102pub const no_panic = @import("debug/no_panic.zig");
...@@ -153,9 +231,14 @@ pub const SourceLocation = struct {...@@ -153,9 +231,14 @@ pub const SourceLocation = struct {
153};231};
154232
155pub const Symbol = struct {233pub const Symbol = struct {
156 name: []const u8 = "???",234 name: ?[]const u8,
157 compile_unit_name: []const u8 = "???",235 compile_unit_name: ?[]const u8,
158 source_location: ?SourceLocation = null,236 source_location: ?SourceLocation,
237 pub const unknown: Symbol = .{
238 .name = null,
239 .compile_unit_name = null,
240 .source_location = null,
241 };
159};242};
160243
161/// Deprecated because it returns the optimization mode of the standard244/// Deprecated because it returns the optimization mode of the standard
...@@ -166,18 +249,12 @@ pub const runtime_safety = switch (builtin.mode) {...@@ -166,18 +249,12 @@ pub const runtime_safety = switch (builtin.mode) {
166 .ReleaseFast, .ReleaseSmall => false,249 .ReleaseFast, .ReleaseSmall => false,
167};250};
168251
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.
169pub const sys_can_stack_trace = switch (builtin.cpu.arch) {257pub 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 // `@returnAddress()` in LLVM 10 gives258 // `@returnAddress()` in LLVM 10 gives
182 // "Non-Emscripten WebAssembly hasn't implemented __builtin_return_address".259 // "Non-Emscripten WebAssembly hasn't implemented __builtin_return_address".
183 // On Emscripten, Zig only supports `@returnAddress()` in debug builds260 // On Emscripten, Zig only supports `@returnAddress()` in debug builds
...@@ -186,7 +263,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {...@@ -186,7 +263,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
186 .wasm64,263 .wasm64,
187 => native_os == .emscripten and builtin.mode == .Debug,264 => native_os == .emscripten and builtin.mode == .Debug,
188265
189 // `@returnAddress()` is unsupported in LLVM 13.266 // `@returnAddress()` is unsupported in LLVM 21.
190 .bpfel,267 .bpfel,
191 .bpfeb,268 .bpfeb,
192 => false,269 => false,
...@@ -209,8 +286,12 @@ pub fn unlockStdErr() void {...@@ -209,8 +286,12 @@ pub fn unlockStdErr() void {
209///286///
210/// During the lock, any `std.Progress` information is cleared from the terminal.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 is289/// The lock is recursive, so it is valid for the same thread to call `lockStderrWriter` multiple
213/// in fact unbuffered and does not need to be flushed.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.
214pub fn lockStderrWriter(buffer: []u8) *Writer {295pub fn lockStderrWriter(buffer: []u8) *Writer {
215 return std.Progress.lockStderrWriter(buffer);296 return std.Progress.lockStderrWriter(buffer);
216}297}
...@@ -231,16 +312,13 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {...@@ -231,16 +312,13 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
231 nosuspend bw.print(fmt, args) catch return;312 nosuspend bw.print(fmt, args) catch return;
232}313}
233314
234/// TODO multithreaded awareness315/// Marked `inline` to propagate a comptime-known error to callers.
235var self_debug_info: ?SelfInfo = null;316pub inline fn getSelfDebugInfo() !*SelfInfo {
236317 if (SelfInfo == void) return error.UnsupportedTarget;
237pub fn getSelfDebugInfo() !*SelfInfo {318 const S = struct {
238 if (self_debug_info) |*info| {319 var self_info: SelfInfo = .init;
239 return info;320 };
240 } else {321 return &S.self_info;
241 self_debug_info = try SelfInfo.open(getDebugInfoAllocator());
242 return &self_debug_info.?;
243 }
244}322}
245323
246/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.324/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
...@@ -321,226 +399,8 @@ test dumpHexFallible {...@@ -321,226 +399,8 @@ test dumpHexFallible {
321 try std.testing.expectEqualStrings(expected, aw.written());399 try std.testing.expectEqualStrings(expected, aw.written());
322}400}
323401
324/// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned.402/// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic.
325pub fn dumpCurrentStackTrace(start_addr: ?usize) void {403pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *const cpu_context.Native;
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.
332pub 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
353pub 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`.
358pub 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
369pub 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
376pub fn relocateContext(context: *ThreadContext) void {
377 return switch (native_os) {
378 .macos => {
379 context.mcontext = &context.__mcontext_data;
380 },
381 else => {},
382 };
383}
384
385pub 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.
392pub 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
416pub 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.
475pub 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
518pub 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}
544404
545/// Invokes detectable illegal behavior when `ok` is `false`.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,8 +439,8 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn {
579 panicExtra(@returnAddress(), format, args);439 panicExtra(@returnAddress(), format, args);
580}440}
581441
582/// Equivalent to `@panic` but with a formatted message, and with an explicitly442/// Equivalent to `@panic` but with a formatted message and an explicitly provided return address
583/// provided return address.443/// which will be the first address in the stack trace.
584pub fn panicExtra(444pub fn panicExtra(
585 ret_addr: ?usize,445 ret_addr: ?usize,
586 comptime format: []const u8,446 comptime format: []const u8,
...@@ -610,6 +470,24 @@ var panicking = std.atomic.Value(u8).init(0);...@@ -610,6 +470,24 @@ var panicking = std.atomic.Value(u8).init(0);
610/// This is used to catch and handle panics triggered by the panic handler.470/// This is used to catch and handle panics triggered by the panic handler.
611threadlocal var panic_stage: usize = 0;471threadlocal var panic_stage: usize = 0;
612472
473/// For backends that cannot handle the language features depended on by the
474/// default panic handler, we will use a simpler implementation.
475const 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/// Dumps a stack trace to standard error, then aborts.491/// Dumps a stack trace to standard error, then aborts.
614pub fn defaultPanic(492pub fn defaultPanic(
615 msg: []const u8,493 msg: []const u8,
...@@ -617,8 +495,8 @@ pub fn defaultPanic(...@@ -617,8 +495,8 @@ pub fn defaultPanic(
617) noreturn {495) noreturn {
618 @branchHint(.cold);496 @branchHint(.cold);
619497
620 // For backends that cannot handle the language features depended on by the498 if (use_trap_panic) @trap();
621 // default panic handler, we have a simpler panic handler:499
622 switch (builtin.zig_backend) {500 switch (builtin.zig_backend) {
623 .stage2_aarch64,501 .stage2_aarch64,
624 .stage2_arm,502 .stage2_arm,
...@@ -683,41 +561,48 @@ pub fn defaultPanic(...@@ -683,41 +561,48 @@ pub fn defaultPanic(
683 resetSegfaultHandler();561 resetSegfaultHandler();
684 }562 }
685563
686 // Note there is similar logic in handleSegfaultPosix and handleSegfaultWindowsExtra.564 // There is very similar logic to the following in `handleSegfault`.
687 nosuspend switch (panic_stage) {565 switch (panic_stage) {
688 0 => {566 0 => {
689 panic_stage = 1;567 panic_stage = 1;
690
691 _ = panicking.fetchAdd(1, .seq_cst);568 _ = panicking.fetchAdd(1, .seq_cst);
692569
693 {570 trace: {
571 const tty_config = tty.detectConfig(.stderr());
572
694 const stderr = lockStderrWriter(&.{});573 const stderr = lockStderrWriter(&.{});
695 defer unlockStderrWriter();574 defer unlockStderrWriter();
696575
697 if (builtin.single_threaded) {576 if (builtin.single_threaded) {
698 stderr.print("panic: ", .{}) catch posix.abort();577 stderr.print("panic: ", .{}) catch break :trace;
699 } else {578 } else {
700 const current_thread_id = std.Thread.getCurrentId();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;
704583
705 if (@errorReturnTrace()) |t| dumpStackTrace(t.*);584 if (@errorReturnTrace()) |t| if (t.index > 0) {
706 dumpCurrentStackTraceToWriter(first_trace_addr orelse @returnAddress(), stderr) catch {};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 }
708594
709 waitForOtherThreadToFinishPanicking();595 waitForOtherThreadToFinishPanicking();
710 },596 },
711 1 => {597 1 => {
712 panic_stage = 2;598 panic_stage = 2;
713
714 // A panic happened while trying to print a previous panic message.599 // A panic happened while trying to print a previous panic message.
715 // We're still holding the mutex but that's fine as we're going to600 // We're still holding the mutex but that's fine as we're going to
716 // call abort().601 // call abort().
717 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};602 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};
718 },603 },
719 else => {}, // Panicked while printing the recursive panic message.604 else => {}, // Panicked while printing the recursive panic message.
720 };605 }
721606
722 posix.abort();607 posix.abort();
723}608}
...@@ -736,391 +621,417 @@ fn waitForOtherThreadToFinishPanicking() void {...@@ -736,391 +621,417 @@ fn waitForOtherThreadToFinishPanicking() void {
736 }621 }
737}622}
738623
739pub fn writeStackTrace(624pub const StackUnwindOptions = struct {
740 stack_trace: std.builtin.StackTrace,625 /// If not `null`, we will ignore all frames up until this return address. This is typically
741 writer: *Writer,626 /// used to omit intermediate handling code (for instance, a panic handler and its machinery)
742 debug_info: *SelfInfo,627 /// from stack traces.
743 tty_config: tty.Config,628 first_address: ?usize = null,
744) !void {629 /// If not `null`, we will unwind from this `cpu_context.Native` instead of the current top of
745 if (builtin.strip_debug_info) return error.MissingDebugInfo;630 /// the stack. The main use case here is printing stack traces from signal handlers, where the
746 var frame_index: usize = 0;631 /// kernel provides a `*const cpu_context.Native` of the state before the signal.
747 var frames_left: usize = @min(stack_trace.index, stack_trace.instruction_addresses.len);632 context: ?CpuContextPtr = null,
748633 /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort.
749 while (frames_left != 0) : ({634 /// If `false`, only known-safe mechanisms will be attempted.
750 frames_left -= 1;635 allow_unsafe_unwind: bool = false,
751 frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len;636};
752 }) {
753 const return_address = stack_trace.instruction_addresses[frame_index];
754 try printSourceAtAddress(debug_info, writer, return_address - 1, tty_config);
755 }
756637
757 if (stack_trace.index > stack_trace.instruction_addresses.len) {638/// Capture and return the current stack trace. The returned `StackTrace` stores its addresses in
758 const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len;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.
642pub 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.
680pub 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.
768pub 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}
759784
785/// Write a previously captured stack trace to `writer`, annotated with source locations.
786pub 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 tty_config.setColor(writer, .bold) catch {};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 tty_config.setColor(writer, .reset) catch {};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.
819pub 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}
765827
766pub const UnwindError = if (have_ucontext)828const StackIterator = union(enum) {
767 @typeInfo(@typeInfo(@TypeOf(StackIterator.next_unwind)).@"fn".return_type.?).error_union.error_set829 /// Unwinding using debug info (e.g. DWARF CFI).
768else830 di: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn,
769 void;831 /// We will first report the *current* PC of this `UnwindContext`, then we will switch to `di`.
770832 di_first: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn,
771pub const StackIterator = struct {833 /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable.
772 // Skip every frame before this address is found.
773 first_address: ?usize,
774 // Last known value of the frame pointer register.
775 fp: usize,834 fp: usize,
776835
777 // When SelfInfo and a register context is available, this iterator can unwind836 /// It is important that this function is marked `inline` so that it can safely use
778 // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer),837 /// `@frameAddress` and `cpu_context.Native.current` as the caller's stack frame and
779 // using DWARF and MachO unwind info.838 /// our own are one and the same.
780 unwind_state: if (have_ucontext) ?struct {839 inline fn init(opt_context_ptr: ?CpuContextPtr) error{CannotUnwindFromContext}!StackIterator {
781 debug_info: *SelfInfo,840 if (builtin.cpu.arch.isSPARC()) {
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()) {
789 // Flush all the register windows on stack.841 // Flush all the register windows on stack.
790 asm volatile (if (builtin.cpu.has(.sparc, .v9))842 if (builtin.cpu.has(.sparc, .v9)) {
791 "flushw"843 asm volatile ("flushw" ::: .{ .memory = true });
792 else844 } else {
793 "ta 3" // ST_FLUSH_WINDOWS845 asm volatile ("ta 3" ::: .{ .memory = true }); // 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 };
837 }846 }
838 }847 }
839848 if (opt_context_ptr) |context_ptr| {
840 return null;849 if (SelfInfo == void or !SelfInfo.can_unwind) return error.CannotUnwindFromContext;
841 }850 // Use `di_first` here so we report the PC in the context before unwinding any further.
842851 return .{ .di_first = .init(context_ptr) };
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;
875 }852 }
876853 // Workaround the C backend being unable to use inline assembly on MSVC by disabling the
877 return address;854 // call to `current`. This effectively constrains stack trace collection and dumping to FP
878 }855 // unwinding when building with CBE for MSVC.
879856 if (!(builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) and
880 fn next_unwind(it: *StackIterator) !usize {857 SelfInfo != void and
881 const unwind_state = &it.unwind_state.?;858 SelfInfo.can_unwind and
882 const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc);859 cpu_context.Native != noreturn)
883 switch (native_os) {860 {
884 .macos, .ios, .watchos, .tvos, .visionos => {861 // We don't need `di_first` here, because our PC is in `std.debug`; we're only interested
885 // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding862 // in our caller's frame and above.
886 // via DWARF before attempting to use the compact unwind info will produce incorrect results.863 return .{ .di = .init(&.current()) };
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 => {},
902 }864 }
903865 return .{ .fp = @frameAddress() };
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;
913 }866 }
914867 fn deinit(si: *StackIterator) void {
915 fn next_internal(it: *StackIterator) ?usize {868 switch (si.*) {
916 if (have_ucontext) {869 .fp => {},
917 if (it.unwind_state) |*unwind_state| {870 .di, .di_first => |*unwind_context| unwind_context.deinit(getDebugInfoAllocator()),
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 }
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};
960873
961pub fn writeCurrentStackTrace(874 /// On aarch64-macos, Apple mandate that the frame pointer is always used.
962 writer: *Writer,875 /// TODO: are there any other architectures with guarantees like this?
963 debug_info: *SelfInfo,876 const fp_unwind_is_safe = builtin.cpu.arch == .aarch64 and builtin.os.tag.isDarwin();
964 tty_config: tty.Config,877
965 start_addr: ?usize,878 /// Whether the current unwind strategy is allowed given `allow_unsafe`.
966) !void {879 fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool {
967 if (native_os == .windows) {880 return switch (it.*) {
968 var context: ThreadContext = undefined;881 .di, .di_first => true,
969 assert(getContext(&context));882 // If we omitted frame pointers from *this* compilation, FP unwinding would crash
970 return writeStackTraceWindows(writer, debug_info, tty_config, &context, start_addr);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();
979889
980 while (it.next()) |return_address| {890 const Result = union(enum) {
981 printLastUnwindError(&it, debug_info, writer, tty_config);891 /// A stack frame has been found; this is the corresponding return address.
982892 frame: usize,
983 // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,893 /// The end of the stack has been reached.
984 // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid894 end,
985 // an overflow. We do not need to signal `StackIterator` as it will correctly detect this895 /// We were using `SelfInfo.UnwindInfo`, but are now switching to FP unwinding due to this error.
986 // condition on the subsequent iteration and return `null` thus terminating the loop.896 switch_to_fp: struct {
987 // same behaviour for x86-windows-msvc897 address: usize,
988 const address = return_address -| 1;898 err: SelfInfoError,
989 try printSourceAtAddress(debug_info, writer, address, tty_config);899 },
990 } else printLastUnwindError(&it, debug_info, writer, tty_config);900 };
991}
992901
993pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize {902 fn next(it: *StackIterator) Result {
994 if (builtin.cpu.arch == .x86) {903 switch (it.*) {
995 // RtlVirtualUnwind doesn't exist on x86904 .di_first => |unwind_context| {
996 return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null);905 const first_pc = unwind_context.pc;
997 }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
998928
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;
1000931
1001 var context: windows.CONTEXT = undefined;932 if (bp_addr == 0 or !mem.isAligned(bp_addr, @alignOf(usize)) or
1002 if (existing_context) |context_ptr| {933 ra_addr == 0 or !mem.isAligned(ra_addr, @alignOf(usize)))
1003 context = context_ptr.*;934 {
1004 } else {935 // This isn't valid, but it most likely indicates end of stack.
1005 context = std.mem.zeroes(windows.CONTEXT);936 return .end;
1006 windows.ntdll.RtlCaptureContext(&context);937 }
1007 }
1008938
1009 var i: usize = 0;939 const bp_ptr: *const usize = @ptrFromInt(bp_addr);
1010 var image_base: windows.DWORD64 = undefined;940 const ra_ptr: *const usize = @ptrFromInt(ra_addr);
1011 var history_table: windows.UNWIND_HISTORY_TABLE = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE);941 const bp = applyOffset(bp_ptr.*, bp_bias) orelse return .end;
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 }
1033942
1034 const next_regs = context.getRegs();943 // The stack grows downards, so `bp > fp` should always hold. If it doesn't, this
1035 if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) {944 // frame is invalid, so we'll treat it as though it we reached end of stack. The
1036 break;945 // exception is address 0, which is a graceful end-of-stack signal, in which case
1037 }946 // *this* return address is valid and the *next* iteration will be the last.
947 if (bp != 0 and bp <= fp) return .end;
1038948
1039 if (next_regs.ip == 0) {949 it.fp = bp;
1040 break;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 }
1045956
1046 return i;957 /// Offset of the saved base pointer (previous frame pointer) wrt the frame pointer.
1047}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 };
1048967
1049pub fn writeStackTraceWindows(968 /// Offset of the saved return address wrt the frame pointer.
1050 writer: *Writer,969 const ra_offset = off: {
1051 debug_info: *SelfInfo,970 if (native_arch == .powerpc64le) break :off 2 * @sizeOf(usize);
1052 tty_config: tty.Config,971 break :off @sizeOf(usize);
1053 context: *const windows.CONTEXT,972 };
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}
1069973
1070fn printUnknownSource(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {974 /// Value to add to a base pointer after loading it from the stack. Yes, SPARC really does this.
1071 const module_name = debug_info.getModuleNameForAddress(address);975 const bp_bias = bias: {
1072 return printLineInfo(976 if (native_arch.isSPARC()) break :bias 2047;
1073 writer,977 break :bias 0;
1074 null,978 };
1075 address,
1076 "???",
1077 module_name orelse "???",
1078 tty_config,
1079 printLineFromFileAnyOs,
1080 );
1081}
1082979
1083fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: tty.Config) void {980 fn applyOffset(addr: usize, comptime off: comptime_int) ?usize {
1084 if (!have_ucontext) return;981 if (off >= 0) return math.add(usize, addr, off) catch return null;
1085 if (it.getLastError()) |unwind_error| {982 return math.sub(usize, addr, -off) catch return null;
1086 printUnwindError(debug_info, writer, unwind_error.address, unwind_error.err, tty_config) catch {};
1087 }983 }
1088}984};
1089985
1090fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, err: UnwindError, tty_config: tty.Config) !void {986/// Some platforms use pointer authentication: the upper bits of instruction pointers contain a
1091 const module_name = debug_info.getModuleNameForAddress(address) orelse "???";987/// signature. This function clears those signature bits to make the pointer directly usable.
1092 try tty_config.setColor(writer, .dim);988pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
1093 if (err == error.MissingDebugInfo) {989 if (native_arch.isAARCH64()) {
1094 try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });990 // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
1095 } else {991 // The save / restore is because `xpaclri` operates on x30 (LR)
1096 try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err });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}
11001002
1101pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {1003 return ptr;
1102 const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {1004}
1103 error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),
1104 else => return err,
1105 };
11061005
1107 const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) {1006fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
1108 error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),1007 const symbol: Symbol = debug_info.getSymbol(gpa, address) catch |err| switch (err) {
1109 else => return 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);1025 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
1112
1113 return printLineInfo(1026 return printLineInfo(
1114 writer,1027 writer,
1115 symbol_info.source_location,1028 symbol.source_location,
1116 address,1029 address,
1117 symbol_info.name,1030 symbol.name orelse "???",
1118 symbol_info.compile_unit_name,1031 symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",
1119 tty_config,1032 tty_config,
1120 printLineFromFileAnyOs,
1121 );1033 );
1122}1034}
1123
1124fn printLineInfo(1035fn printLineInfo(
1125 writer: *Writer,1036 writer: *Writer,
1126 source_location: ?SourceLocation,1037 source_location: ?SourceLocation,
...@@ -1128,10 +1039,9 @@ fn printLineInfo(...@@ -1128,10 +1039,9 @@ fn printLineInfo(
1128 symbol_name: []const u8,1039 symbol_name: []const u8,
1129 compile_unit_name: []const u8,1040 compile_unit_name: []const u8,
1130 tty_config: tty.Config,1041 tty_config: tty.Config,
1131 comptime printLineFromFile: anytype,1042) Writer.Error!void {
1132) !void {
1133 nosuspend {1043 nosuspend {
1134 try tty_config.setColor(writer, .bold);1044 tty_config.setColor(writer, .bold) catch {};
11351045
1136 if (source_location) |*sl| {1046 if (source_location) |*sl| {
1137 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });1047 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
...@@ -1139,11 +1049,11 @@ fn printLineInfo(...@@ -1139,11 +1049,11 @@ fn printLineInfo(
1139 try writer.writeAll("???:?:?");1049 try writer.writeAll("???:?:?");
1140 }1050 }
11411051
1142 try tty_config.setColor(writer, .reset);1052 tty_config.setColor(writer, .reset) catch {};
1143 try writer.writeAll(": ");1053 try writer.writeAll(": ");
1144 try tty_config.setColor(writer, .dim);1054 tty_config.setColor(writer, .dim) catch {};
1145 try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });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 try writer.writeAll("\n");1057 try writer.writeAll("\n");
11481058
1149 // Show the matching source code line if possible1059 // Show the matching source code line if possible
...@@ -1154,22 +1064,24 @@ fn printLineInfo(...@@ -1154,22 +1064,24 @@ fn printLineInfo(
1154 const space_needed = @as(usize, @intCast(sl.column - 1));1064 const space_needed = @as(usize, @intCast(sl.column - 1));
11551065
1156 try writer.splatByteAll(' ', space_needed);1066 try writer.splatByteAll(' ', space_needed);
1157 try tty_config.setColor(writer, .green);1067 tty_config.setColor(writer, .green) catch {};
1158 try writer.writeAll("^");1068 try writer.writeAll("^");
1159 try tty_config.setColor(writer, .reset);1069 tty_config.setColor(writer, .reset) catch {};
1160 }1070 }
1161 try writer.writeAll("\n");1071 try writer.writeAll("\n");
1162 } else |err| switch (err) {1072 } else |_| {
1163 error.EndOfFile, error.FileNotFound => {},1073 // Ignore all errors; it's a better UX to just print the source location without the
1164 error.BadPathName => {},1074 // corresponding line number. The user can always open the source file themselves.
1165 error.AccessDenied => {},
1166 else => return err,
1167 }1075 }
1168 }1076 }
1169 }1077 }
1170}1078}
1079fn 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 }
11711084
1172fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !void {
1173 // Need this to always block even in async I/O mode, because this could potentially1085 // Need this to always block even in async I/O mode, because this could potentially
1174 // be called from e.g. the event loop code crashing.1086 // be called from e.g. the event loop code crashing.
1175 var f = try fs.cwd().openFile(source_location.file_name, .{});1087 var f = try fs.cwd().openFile(source_location.file_name, .{});
...@@ -1223,7 +1135,7 @@ fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !voi...@@ -1223,7 +1135,7 @@ fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !voi
1223 }1135 }
1224}1136}
12251137
1226test printLineFromFileAnyOs {1138test printLineFromFile {
1227 var aw: Writer.Allocating = .init(std.testing.allocator);1139 var aw: Writer.Allocating = .init(std.testing.allocator);
1228 defer aw.deinit();1140 defer aw.deinit();
1229 const output_stream = &aw.writer;1141 const output_stream = &aw.writer;
...@@ -1245,9 +1157,9 @@ test printLineFromFileAnyOs {...@@ -1245,9 +1157,9 @@ test printLineFromFileAnyOs {
1245 defer allocator.free(path);1157 defer allocator.free(path);
1246 try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });1158 try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });
12471159
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 }));
12491161
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 try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());1163 try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());
1252 aw.clearRetainingCapacity();1164 aw.clearRetainingCapacity();
1253 }1165 }
...@@ -1263,11 +1175,11 @@ test printLineFromFileAnyOs {...@@ -1263,11 +1175,11 @@ test printLineFromFileAnyOs {
1263 ,1175 ,
1264 });1176 });
12651177
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 try expectEqualStrings("1\n", aw.written());1179 try expectEqualStrings("1\n", aw.written());
1268 aw.clearRetainingCapacity();1180 aw.clearRetainingCapacity();
12691181
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 try expectEqualStrings("3\n", aw.written());1183 try expectEqualStrings("3\n", aw.written());
1272 aw.clearRetainingCapacity();1184 aw.clearRetainingCapacity();
1273 }1185 }
...@@ -1286,7 +1198,7 @@ test printLineFromFileAnyOs {...@@ -1286,7 +1198,7 @@ test printLineFromFileAnyOs {
1286 try writer.splatByteAll('a', overlap);1198 try writer.splatByteAll('a', overlap);
1287 try writer.flush();1199 try writer.flush();
12881200
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 try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());1202 try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());
1291 aw.clearRetainingCapacity();1203 aw.clearRetainingCapacity();
1292 }1204 }
...@@ -1300,7 +1212,7 @@ test printLineFromFileAnyOs {...@@ -1300,7 +1212,7 @@ test printLineFromFileAnyOs {
1300 const writer = &file_writer.interface;1212 const writer = &file_writer.interface;
1301 try writer.splatByteAll('a', std.heap.page_size_max);1213 try writer.splatByteAll('a', std.heap.page_size_max);
13021214
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 try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());1216 try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());
1305 aw.clearRetainingCapacity();1217 aw.clearRetainingCapacity();
1306 }1218 }
...@@ -1314,19 +1226,19 @@ test printLineFromFileAnyOs {...@@ -1314,19 +1226,19 @@ test printLineFromFileAnyOs {
1314 const writer = &file_writer.interface;1226 const writer = &file_writer.interface;
1315 try writer.splatByteAll('a', 3 * std.heap.page_size_max);1227 try writer.splatByteAll('a', 3 * std.heap.page_size_max);
13161228
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 }));
13181230
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 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());1232 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());
1321 aw.clearRetainingCapacity();1233 aw.clearRetainingCapacity();
13221234
1323 try writer.writeAll("a\na");1235 try writer.writeAll("a\na");
13241236
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 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());1238 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());
1327 aw.clearRetainingCapacity();1239 aw.clearRetainingCapacity();
13281240
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 try expectEqualStrings("a\n", aw.written());1242 try expectEqualStrings("a\n", aw.written());
1331 aw.clearRetainingCapacity();1243 aw.clearRetainingCapacity();
1332 }1244 }
...@@ -1342,26 +1254,29 @@ test printLineFromFileAnyOs {...@@ -1342,26 +1254,29 @@ test printLineFromFileAnyOs {
1342 try writer.splatByteAll('\n', real_file_start);1254 try writer.splatByteAll('\n', real_file_start);
1343 try writer.writeAll("abc\ndef");1255 try writer.writeAll("abc\ndef");
13441256
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 try expectEqualStrings("abc\n", aw.written());1258 try expectEqualStrings("abc\n", aw.written());
1347 aw.clearRetainingCapacity();1259 aw.clearRetainingCapacity();
13481260
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 try expectEqualStrings("def\n", aw.written());1262 try expectEqualStrings("def\n", aw.written());
1351 aw.clearRetainingCapacity();1263 aw.clearRetainingCapacity();
1352 }1264 }
1353}1265}
13541266
1355/// TODO multithreaded awareness1267/// The returned allocator should be thread-safe if the compilation is multi-threaded, because
1356var debug_info_allocator: ?mem.Allocator = null;1268/// multiple threads could capture and/or print stack traces simultaneously.
1357var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;1269fn getDebugInfoAllocator() Allocator {
1358fn getDebugInfoAllocator() mem.Allocator {1270 // Allow overriding the debug info allocator by exposing `root.debug.getDebugInfoAllocator`.
1359 if (debug_info_allocator) |a| return a;1271 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getDebugInfoAllocator")) {
13601272 return root.debug.getDebugInfoAllocator();
1361 debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);1273 }
1362 const allocator = debug_info_arena_allocator.allocator();1274 // Otherwise, use a global arena backed by the page allocator
1363 debug_info_allocator = allocator;1275 const S = struct {
1364 return allocator;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}
13661281
1367/// Whether or not the current target can print useful debug information when a segfault occurs.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,9 +1287,10 @@ pub const have_segfault_handling_support = switch (native_os) {
1372 .solaris,1287 .solaris,
1373 .illumos,1288 .illumos,
1374 .windows,1289 .windows,
1290 .freebsd,
1291 .openbsd,
1375 => true,1292 => true,
13761293
1377 .freebsd, .openbsd => have_ucontext,
1378 else => false,1294 else => false,
1379};1295};
13801296
...@@ -1396,7 +1312,16 @@ pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) void {...@@ -1396,7 +1312,16 @@ pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) void {
1396 posix.sigaction(posix.SIG.FPE, act, null);1312 posix.sigaction(posix.SIG.FPE, act, null);
1397}1313}
13981314
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)
1400pub fn attachSegfaultHandler() void {1325pub fn attachSegfaultHandler() void {
1401 if (!have_segfault_handling_support) {1326 if (!have_segfault_handling_support) {
1402 @compileError("segfault handler not supported for this target");1327 @compileError("segfault handler not supported for this target");
...@@ -1430,52 +1355,9 @@ fn resetSegfaultHandler() void {...@@ -1430,52 +1355,9 @@ fn resetSegfaultHandler() void {
1430}1355}
14311356
1432fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn {1357fn 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 crash1358 if (use_trap_panic) @trap();
1434 // the process. Also when this handler returns, the original instruction will be repeated1359 const addr: ?usize, const name: []const u8 = info: {
1435 // and the resulting segfault will crash the process rather than continually dump stack traces.1360 if (native_os == .linux and native_arch == .x86_64) {
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
1474fn 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
1479 // x86_64 doesn't have a full 64-bit virtual address space.1361 // x86_64 doesn't have a full 64-bit virtual address space.
1480 // Addresses outside of that address space are non-canonical1362 // Addresses outside of that address space are non-canonical
1481 // and the CPU won't provide the faulting address to us.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,100 +1365,92 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque)
1483 // but can also happen when no addressable memory is involved;1365 // but can also happen when no addressable memory is involved;
1484 // for example when reading/writing model-specific registers1366 // for example when reading/writing model-specific registers
1485 // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode).1367 // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode).
1486 stderr.writeAll("General protection exception (no address available)\n")1368 const SI_KERNEL = 0x80;
1487 else1369 if (sig == posix.SIG.SEGV and info.code == SI_KERNEL) {
1488 stderr.print("Segmentation fault at address 0x{x}\n", .{addr}),1370 break :info .{ null, "General protection exception" };
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;
1522 }1371 }
1523 relocateContext(&new_ctx);1372 }
1524 dumpStackTraceFromBase(&new_ctx, stderr);1373 const addr: usize = switch (native_os) {
1525 },1374 .linux => @intFromPtr(info.fields.sigfault.addr),
1526 else => {},1375 .freebsd, .macos => @intFromPtr(info.addr),
1527 }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}
15291393
1530fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long {1394fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long {
1531 switch (info.ExceptionRecord.ExceptionCode) {1395 if (use_trap_panic) @trap();
1532 windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, 0, "Unaligned Memory Access"),1396 const name: []const u8, const addr: ?usize = switch (info.ExceptionRecord.ExceptionCode) {
1533 windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, 1, null),1397 windows.EXCEPTION_DATATYPE_MISALIGNMENT => .{ "Unaligned memory access", null },
1534 windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, 2, null),1398 windows.EXCEPTION_ACCESS_VIOLATION => .{ "Segmentation fault", info.ExceptionRecord.ExceptionInformation[1] },
1535 windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, 0, "Stack Overflow"),1399 windows.EXCEPTION_ILLEGAL_INSTRUCTION => .{ "Illegal instruction", info.ContextRecord.getRegs().ip },
1400 windows.EXCEPTION_STACK_OVERFLOW => .{ "Stack overflow", null },
1536 else => return windows.EXCEPTION_CONTINUE_SEARCH,1401 else => return windows.EXCEPTION_CONTINUE_SEARCH,
1537 }1402 };
1403 handleSegfault(addr, name, &cpu_context.fromWindowsContext(info.ContextRecord));
1538}1404}
15391405
1540fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) noreturn {1406fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn {
1541 // For backends that cannot handle the language features used by this segfault handler, we have a simpler one,1407 // Allow overriding the target-agnostic segfault handler by exposing `root.debug.handleSegfault`.
1542 switch (builtin.zig_backend) {1408 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "handleSegfault")) {
1543 .stage2_x86_64 => if (builtin.target.ofmt == .coff) @trap(),1409 return root.debug.handleSegfault(addr, name, opt_ctx);
1544 else => {},
1545 }1410 }
1411 return defaultHandleSegfault(addr, name, opt_ctx);
1412}
15461413
1547 comptime assert(windows.CONTEXT != void);1414pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn {
1548 nosuspend switch (panic_stage) {1415 // There is very similar logic to the following in `defaultPanic`.
1416 switch (panic_stage) {
1549 0 => {1417 0 => {
1550 panic_stage = 1;1418 panic_stage = 1;
1551 _ = panicking.fetchAdd(1, .seq_cst);1419 _ = panicking.fetchAdd(1, .seq_cst);
15521420
1553 {1421 trace: {
1422 const tty_config = tty.detectConfig(.stderr());
1423
1554 const stderr = lockStderrWriter(&.{});1424 const stderr = lockStderrWriter(&.{});
1555 defer unlockStderrWriter();1425 defer unlockStderrWriter();
15561426
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 1 => {1440 1 => {
1563 panic_stage = 2;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 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};1445 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};
1565 },1446 },
1566 else => {},1447 else => {}, // Panicked while printing the recursive panic message.
1567 };1448 }
1568 posix.abort();
1569}
1570
1571fn 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();
15781449
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}
15811455
1582pub fn dumpStackPointerAddr(prefix: []const u8) void {1456pub fn dumpStackPointerAddr(prefix: []const u8) void {
...@@ -1587,26 +1461,23 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void {...@@ -1587,26 +1461,23 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void {
1587}1461}
15881462
1589test "manage resources correctly" {1463test "manage resources correctly" {
1590 if (builtin.strip_debug_info) return error.SkipZigTest;1464 if (SelfInfo == void) return error.SkipZigTest;
15911465 const S = struct {
1592 if (native_os == .wasi) return error.SkipZigTest;1466 noinline fn showMyTrace() usize {
15931467 return @returnAddress();
1594 if (native_os == .windows) {1468 }
1595 // https://github.com/ziglang/zig/issues/139631469 };
1596 return error.SkipZigTest;1470 const gpa = std.testing.allocator;
1597 }1471 var discarding: std.Io.Writer.Discarding = .init(&.{});
15981472 var di: SelfInfo = .init;
1599 // self-hosted debug info is still too buggy1473 defer di.deinit(gpa);
1600 if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest;1474 try printSourceAtAddress(
16011475 gpa,
1602 var discarding: Writer.Discarding = .init(&.{});1476 &di,
1603 var di = try SelfInfo.open(testing.allocator);1477 &discarding.writer,
1604 defer di.deinit();1478 S.showMyTrace(),
1605 try printSourceAtAddress(&di, &discarding.writer, showMyTrace(), tty.detectConfig(.stderr()));1479 tty.detectConfig(.stderr()),
1606}1480 );
1607
1608noinline fn showMyTrace() usize {
1609 return @returnAddress();
1610}1481}
16111482
1612/// This API helps you track where a value originated and where it was mutated,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,12 +1524,11 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
16531524
1654 if (t.index < size) {1525 if (t.index < size) {
1655 t.notes[t.index] = note;1526 t.notes[t.index] = note;
1656 t.addrs[t.index] = [1]usize{0} ** stack_frame_count;1527 const addrs = &t.addrs[t.index];
1657 var stack_trace: std.builtin.StackTrace = .{1528 const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs);
1658 .index = 0,1529 if (st.index < addrs.len) {
1659 .instruction_addresses = &t.addrs[t.index],1530 @memset(addrs[st.index..], 0); // zero unused frames to indicate end of trace
1660 };1531 }
1661 captureStackTrace(addr, &stack_trace);
1662 }1532 }
1663 // Keep counting even if the end is reached so that the1533 // Keep counting even if the end is reached so that the
1664 // user can find out how much more size they need.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,13 +1542,6 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
1672 const stderr = lockStderrWriter(&.{});1542 const stderr = lockStderrWriter(&.{});
1673 defer unlockStderrWriter();1543 defer unlockStderrWriter();
1674 const end = @min(t.index, size);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 for (t.addrs[0..end], 0..) |frames_array, i| {1545 for (t.addrs[0..end], 0..) |frames_array, i| {
1683 stderr.print("{s}:\n", .{t.notes[i]}) catch return;1546 stderr.print("{s}:\n", .{t.notes[i]}) catch return;
1684 var frames_array_mutable = frames_array;1547 var frames_array_mutable = frames_array;
...@@ -1687,7 +1550,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize...@@ -1687,7 +1550,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
1687 .index = frames.len,1550 .index = frames.len,
1688 .instruction_addresses = frames,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 if (t.index > end) {1555 if (t.index > end) {
1693 stderr.print("{d} more traces not shown; consider increasing trace size\n", .{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,6 +145,7 @@ pub const ResolveAddressesDwarfError = Dwarf.ScanError;
145pub fn resolveAddressesDwarf(145pub fn resolveAddressesDwarf(
146 cov: *Coverage,146 cov: *Coverage,
147 gpa: Allocator,147 gpa: Allocator,
148 endian: std.builtin.Endian,
148 /// Asserts the addresses are in ascending order.149 /// Asserts the addresses are in ascending order.
149 sorted_pc_addrs: []const u64,150 sorted_pc_addrs: []const u64,
150 /// Asserts its length equals length of `sorted_pc_addrs`.151 /// Asserts its length equals length of `sorted_pc_addrs`.
...@@ -184,7 +185,7 @@ pub fn resolveAddressesDwarf(...@@ -184,7 +185,7 @@ pub fn resolveAddressesDwarf(
184 if (cu.src_loc_cache == null) {185 if (cu.src_loc_cache == null) {
185 cov.mutex.unlock();186 cov.mutex.unlock();
186 defer cov.mutex.lock();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 error.MissingDebugInfo, error.InvalidDebugInfo => {189 error.MissingDebugInfo, error.InvalidDebugInfo => {
189 out.* = SourceLocation.invalid;190 out.* = SourceLocation.invalid;
190 continue :next_pc;191 continue :next_pc;
lib/std/debug/Dwarf.zig+307-1221
...@@ -1,22 +1,18 @@...@@ -1,22 +1,18 @@
1//! Implements parsing, decoding, and caching of DWARF information.1//! Implements parsing, decoding, and caching of DWARF information.
2//!2//!
3//! This API does not assume the current executable is itself the thing being3//! This API makes no assumptions about the relationship between the host and
4//! debugged, however, it does assume the debug info has the same CPU4//! the target being debugged. In other words, any DWARF information can be used
5//! architecture and OS as the current executable. It is planned to remove this5//! from any host via this API. Note, however, that the limits of 32-bit
6//! limitation.6//! addressing can cause very large 64-bit binaries to be impossible to open on
7//! 32-bit hosts.
7//!8//!
8//! For unopinionated types and bits, see `std.dwarf`.9//! For unopinionated types and bits, see `std.dwarf`.
910
10const builtin = @import("builtin");
11const native_endian = builtin.cpu.arch.endian();
12
13const std = @import("../std.zig");11const std = @import("../std.zig");
14const Allocator = std.mem.Allocator;12const Allocator = std.mem.Allocator;
15const elf = std.elf;
16const mem = std.mem;13const mem = std.mem;
17const DW = std.dwarf;14const DW = std.dwarf;
18const AT = DW.AT;15const AT = DW.AT;
19const EH = DW.EH;
20const FORM = DW.FORM;16const FORM = DW.FORM;
21const Format = DW.Format;17const Format = DW.Format;
22const RLE = DW.RLE;18const RLE = DW.RLE;
...@@ -24,7 +20,6 @@ const UT = DW.UT;...@@ -24,7 +20,6 @@ const UT = DW.UT;
24const assert = std.debug.assert;20const assert = std.debug.assert;
25const cast = std.math.cast;21const cast = std.math.cast;
26const maxInt = std.math.maxInt;22const maxInt = std.math.maxInt;
27const Path = std.Build.Cache.Path;
28const ArrayList = std.ArrayList;23const ArrayList = std.ArrayList;
29const Endian = std.builtin.Endian;24const Endian = std.builtin.Endian;
30const Reader = std.Io.Reader;25const Reader = std.Io.Reader;
...@@ -32,15 +27,13 @@ const Reader = std.Io.Reader;...@@ -32,15 +27,13 @@ const Reader = std.Io.Reader;
32const Dwarf = @This();27const Dwarf = @This();
3328
34pub const expression = @import("Dwarf/expression.zig");29pub const expression = @import("Dwarf/expression.zig");
35pub const abi = @import("Dwarf/abi.zig");30pub const Unwind = @import("Dwarf/Unwind.zig");
36pub const call_frame = @import("Dwarf/call_frame.zig");31pub const SelfUnwinder = @import("Dwarf/SelfUnwinder.zig");
3732
38/// Useful to temporarily enable while working on this file.33/// Useful to temporarily enable while working on this file.
39const debug_debug_mode = false;34const debug_debug_mode = false;
4035
41endian: Endian,36sections: SectionArray = @splat(null),
42sections: SectionArray = null_section_array,
43is_macho: bool,
4437
45/// Filled later by the initializer38/// Filled later by the initializer
46abbrev_table_list: ArrayList(Abbrev.Table) = .empty,39abbrev_table_list: ArrayList(Abbrev.Table) = .empty,
...@@ -49,14 +42,6 @@ compile_unit_list: ArrayList(CompileUnit) = .empty,...@@ -49,14 +42,6 @@ compile_unit_list: ArrayList(CompileUnit) = .empty,
49/// Filled later by the initializer42/// Filled later by the initializer
50func_list: ArrayList(Func) = .empty,43func_list: ArrayList(Func) = .empty,
5144
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.
54eh_frame_hdr: ?ExceptionFrameHeader = null,
55/// These lookup tables are only used if `eh_frame_hdr` is null
56cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .empty,
57/// Sorted by start_pc
58fde_list: ArrayList(FrameDescriptionEntry) = .empty,
59
60/// Populated by `populateRanges`.45/// Populated by `populateRanges`.
61ranges: ArrayList(Range) = .empty,46ranges: ArrayList(Range) = .empty,
6247
...@@ -69,10 +54,7 @@ pub const Range = struct {...@@ -69,10 +54,7 @@ pub const Range = struct {
6954
70pub const Section = struct {55pub const Section = struct {
71 data: []const u8,56 data: []const u8,
72 // Module-relative virtual address.57 /// If `data` is owned by this Dwarf.
73 // Only set if the section data was loaded from disk.
74 virtual_address: ?usize = null,
75 // If `data` is owned by this Dwarf.
76 owned: bool,58 owned: bool,
7759
78 pub const Id = enum {60 pub const Id = enum {
...@@ -87,21 +69,7 @@ pub const Section = struct {...@@ -87,21 +69,7 @@ pub const Section = struct {
87 debug_rnglists,69 debug_rnglists,
88 debug_addr,70 debug_addr,
89 debug_names,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};
10674
107pub const Abbrev = struct {75pub const Abbrev = struct {
...@@ -110,8 +78,8 @@ pub const Abbrev = struct {...@@ -110,8 +78,8 @@ pub const Abbrev = struct {
110 has_children: bool,78 has_children: bool,
111 attrs: []Attr,79 attrs: []Attr,
11280
113 fn deinit(abbrev: *Abbrev, allocator: Allocator) void {81 fn deinit(abbrev: *Abbrev, gpa: Allocator) void {
114 allocator.free(abbrev.attrs);82 gpa.free(abbrev.attrs);
115 abbrev.* = undefined;83 abbrev.* = undefined;
116 }84 }
11785
...@@ -127,11 +95,11 @@ pub const Abbrev = struct {...@@ -127,11 +95,11 @@ pub const Abbrev = struct {
127 offset: u64,95 offset: u64,
128 abbrevs: []Abbrev,96 abbrevs: []Abbrev,
12997
130 fn deinit(table: *Table, allocator: Allocator) void {98 fn deinit(table: *Table, gpa: Allocator) void {
131 for (table.abbrevs) |*abbrev| {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 table.* = undefined;103 table.* = undefined;
136 }104 }
137105
...@@ -146,6 +114,7 @@ pub const Abbrev = struct {...@@ -146,6 +114,7 @@ pub const Abbrev = struct {
146pub const CompileUnit = struct {114pub const CompileUnit = struct {
147 version: u16,115 version: u16,
148 format: Format,116 format: Format,
117 addr_size_bytes: u8,
149 die: Die,118 die: Die,
150 pc_range: ?PcRange,119 pc_range: ?PcRange,
151120
...@@ -196,7 +165,7 @@ pub const CompileUnit = struct {...@@ -196,7 +165,7 @@ pub const CompileUnit = struct {
196165
197pub const FormValue = union(enum) {166pub const FormValue = union(enum) {
198 addr: u64,167 addr: u64,
199 addrx: usize,168 addrx: u64,
200 block: []const u8,169 block: []const u8,
201 udata: u64,170 udata: u64,
202 data16: *const [16]u8,171 data16: *const [16]u8,
...@@ -208,7 +177,7 @@ pub const FormValue = union(enum) {...@@ -208,7 +177,7 @@ pub const FormValue = union(enum) {
208 ref_addr: u64,177 ref_addr: u64,
209 string: [:0]const u8,178 string: [:0]const u8,
210 strp: u64,179 strp: u64,
211 strx: usize,180 strx: u64,
212 line_strp: u64,181 line_strp: u64,
213 loclistx: u64,182 loclistx: u64,
214 rnglistx: u64,183 rnglistx: u64,
...@@ -243,8 +212,8 @@ pub const Die = struct {...@@ -243,8 +212,8 @@ pub const Die = struct {
243 value: FormValue,212 value: FormValue,
244 };213 };
245214
246 fn deinit(self: *Die, allocator: Allocator) void {215 fn deinit(self: *Die, gpa: Allocator) void {
247 allocator.free(self.attrs);216 gpa.free(self.attrs);
248 self.* = undefined;217 self.* = undefined;
249 }218 }
250219
...@@ -258,13 +227,14 @@ pub const Die = struct {...@@ -258,13 +227,14 @@ pub const Die = struct {
258 fn getAttrAddr(227 fn getAttrAddr(
259 self: *const Die,228 self: *const Die,
260 di: *const Dwarf,229 di: *const Dwarf,
230 endian: Endian,
261 id: u64,231 id: u64,
262 compile_unit: CompileUnit,232 compile_unit: *const CompileUnit,
263 ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 {233 ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 {
264 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;234 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
265 return switch (form_value.*) {235 return switch (form_value.*) {
266 .addr => |value| value,236 .addr => |value| value,
267 .addrx => |index| di.readDebugAddr(compile_unit, index),237 .addrx => |index| di.readDebugAddr(endian, compile_unit, index),
268 else => bad(),238 else => bad(),
269 };239 };
270 }240 }
...@@ -294,9 +264,10 @@ pub const Die = struct {...@@ -294,9 +264,10 @@ pub const Die = struct {
294 pub fn getAttrString(264 pub fn getAttrString(
295 self: *const Die,265 self: *const Die,
296 di: *Dwarf,266 di: *Dwarf,
267 endian: Endian,
297 id: u64,268 id: u64,
298 opt_str: ?[]const u8,269 opt_str: ?[]const u8,
299 compile_unit: CompileUnit,270 compile_unit: *const CompileUnit,
300 ) error{ InvalidDebugInfo, MissingDebugInfo }![]const u8 {271 ) error{ InvalidDebugInfo, MissingDebugInfo }![]const u8 {
301 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;272 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
302 switch (form_value.*) {273 switch (form_value.*) {
...@@ -309,13 +280,13 @@ pub const Die = struct {...@@ -309,13 +280,13 @@ pub const Die = struct {
309 .@"32" => {280 .@"32" => {
310 const byte_offset = compile_unit.str_offsets_base + 4 * index;281 const byte_offset = compile_unit.str_offsets_base + 4 * index;
311 if (byte_offset + 4 > debug_str_offsets.len) return bad();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 return getStringGeneric(opt_str, offset);284 return getStringGeneric(opt_str, offset);
314 },285 },
315 .@"64" => {286 .@"64" => {
316 const byte_offset = compile_unit.str_offsets_base + 8 * index;287 const byte_offset = compile_unit.str_offsets_base + 8 * index;
317 if (byte_offset + 8 > debug_str_offsets.len) return bad();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 return getStringGeneric(opt_str, offset);290 return getStringGeneric(opt_str, offset);
320 },291 },
321 }292 }
...@@ -326,440 +297,17 @@ pub const Die = struct {...@@ -326,440 +297,17 @@ pub const Die = struct {
326 }297 }
327};298};
328299
329/// This represents the decoded .eh_frame_hdr header
330pub 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
443pub 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
508pub 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
677pub 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
751const num_sections = std.enums.directEnumArrayLen(Section.Id, 0);300const num_sections = std.enums.directEnumArrayLen(Section.Id, 0);
752pub const SectionArray = [num_sections]?Section;301pub const SectionArray = [num_sections]?Section;
753pub const null_section_array = [_]?Section{null} ** num_sections;
754302
755pub const OpenError = ScanError;303pub const OpenError = ScanError;
756304
757/// Initialize DWARF info. The caller has the responsibility to initialize most305/// Initialize DWARF info. The caller has the responsibility to initialize most
758/// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the306/// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the
759/// main binary file (not the secondary debug info file).307/// main binary file (not the secondary debug info file).
760pub fn open(d: *Dwarf, gpa: Allocator) OpenError!void {308pub fn open(d: *Dwarf, gpa: Allocator, endian: Endian) OpenError!void {
761 try d.scanAllFunctions(gpa);309 try d.scanAllFunctions(gpa, endian);
762 try d.scanAllCompileUnits(gpa);310 try d.scanAllCompileUnits(gpa, endian);
763}311}
764312
765const PcRange = struct {313const PcRange = struct {
...@@ -776,10 +324,6 @@ pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 {...@@ -776,10 +324,6 @@ pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 {
776 return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;324 return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;
777}325}
778326
779pub 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
783pub fn deinit(di: *Dwarf, gpa: Allocator) void {327pub fn deinit(di: *Dwarf, gpa: Allocator) void {
784 for (di.sections) |opt_section| {328 for (di.sections) |opt_section| {
785 if (opt_section) |s| if (s.owned) gpa.free(s.data);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,14 +342,18 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void {
798 }342 }
799 di.compile_unit_list.deinit(gpa);343 di.compile_unit_list.deinit(gpa);
800 di.func_list.deinit(gpa);344 di.func_list.deinit(gpa);
801 di.cie_map.deinit(gpa);
802 di.fde_list.deinit(gpa);
803 di.ranges.deinit(gpa);345 di.ranges.deinit(gpa);
804 di.* = undefined;346 di.* = undefined;
805}347}
806348
807pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 {349pub fn getSymbolName(di: *const Dwarf, address: u64) ?[]const u8 {
808 for (di.func_list.items) |*func| {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 if (func.pc_range) |range| {357 if (func.pc_range) |range| {
810 if (address >= range.start and address < range.end) {358 if (address >= range.start and address < range.end) {
811 return func.name;359 return func.name;
...@@ -825,35 +373,33 @@ pub const ScanError = error{...@@ -825,35 +373,33 @@ pub const ScanError = error{
825 StreamTooLong,373 StreamTooLong,
826} || Allocator.Error;374} || Allocator.Error;
827375
828fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {376fn scanAllFunctions(di: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void {
829 const endian = di.endian;377 var fr: Reader = .fixed(di.section(.debug_info).?);
830 var fbr: Reader = .fixed(di.section(.debug_info).?);
831 var this_unit_offset: u64 = 0;378 var this_unit_offset: u64 = 0;
832379
833 while (this_unit_offset < fbr.buffer.len) {380 while (this_unit_offset < fr.buffer.len) {
834 fbr.seek = @intCast(this_unit_offset);381 fr.seek = @intCast(this_unit_offset);
835382
836 const unit_header = try readUnitHeader(&fbr, endian);383 const unit_header = try readUnitHeader(&fr, endian);
837 if (unit_header.unit_length == 0) return;384 if (unit_header.unit_length == 0) return;
838 const next_offset = unit_header.header_length + unit_header.unit_length;385 const next_offset = unit_header.header_length + unit_header.unit_length;
839386
840 const version = try fbr.takeInt(u16, endian);387 const version = try fr.takeInt(u16, endian);
841 if (version < 2 or version > 5) return bad();388 if (version < 2 or version > 5) return bad();
842389
843 var address_size: u8 = undefined;390 var address_size: u8 = undefined;
844 var debug_abbrev_offset: u64 = undefined;391 var debug_abbrev_offset: u64 = undefined;
845 if (version >= 5) {392 if (version >= 5) {
846 const unit_type = try fbr.takeByte();393 const unit_type = try fr.takeByte();
847 if (unit_type != DW.UT.compile) return bad();394 if (unit_type != DW.UT.compile) return bad();
848 address_size = try fbr.takeByte();395 address_size = try fr.takeByte();
849 debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);396 debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian);
850 } else {397 } else {
851 debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);398 debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian);
852 address_size = try fbr.takeByte();399 address_size = try fr.takeByte();
853 }400 }
854 if (address_size != @sizeOf(usize)) return bad();
855401
856 const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset);402 const abbrev_table = try di.getAbbrevTable(gpa, debug_abbrev_offset);
857403
858 var max_attrs: usize = 0;404 var max_attrs: usize = 0;
859 var zig_padding_abbrev_code: u7 = 0;405 var zig_padding_abbrev_code: u7 = 0;
...@@ -868,8 +414,8 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {...@@ -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);417 const attrs_buf = try gpa.alloc(Die.Attr, max_attrs * 3);
872 defer allocator.free(attrs_buf);418 defer gpa.free(attrs_buf);
873 var attrs_bufs: [3][]Die.Attr = undefined;419 var attrs_bufs: [3][]Die.Attr = undefined;
874 for (&attrs_bufs, 0..) |*buf, index| buf.* = attrs_buf[index * max_attrs ..][0..max_attrs];420 for (&attrs_bufs, 0..) |*buf, index| buf.* = attrs_buf[index * max_attrs ..][0..max_attrs];
875421
...@@ -878,6 +424,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {...@@ -878,6 +424,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
878 var compile_unit: CompileUnit = .{424 var compile_unit: CompileUnit = .{
879 .version = version,425 .version = version,
880 .format = unit_header.format,426 .format = unit_header.format,
427 .addr_size_bytes = address_size,
881 .die = undefined,428 .die = undefined,
882 .pc_range = null,429 .pc_range = null,
883430
...@@ -890,16 +437,17 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {...@@ -890,16 +437,17 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
890 };437 };
891438
892 while (true) {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 zig_padding_abbrev_code, 0,441 zig_padding_abbrev_code, 0,
895 }) orelse fbr.buffer.len;442 }) orelse fr.buffer.len;
896 if (fbr.seek >= next_unit_pos) break;443 if (fr.seek >= next_unit_pos) break;
897 var die_obj = (try parseDie(444 var die_obj = (try parseDie(
898 &fbr,445 &fr,
899 attrs_bufs[0],446 attrs_bufs[0],
900 abbrev_table,447 abbrev_table,
901 unit_header.format,448 unit_header.format,
902 endian,449 endian,
450 address_size,
903 )) orelse continue;451 )) orelse continue;
904452
905 switch (die_obj.tag_id) {453 switch (die_obj.tag_id) {
...@@ -920,34 +468,36 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {...@@ -920,34 +468,36 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
920 // Prevent endless loops468 // Prevent endless loops
921 for (0..3) |_| {469 for (0..3) |_| {
922 if (this_die_obj.getAttr(AT.name)) |_| {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 } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| {472 } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| {
925 const after_die_offset = fbr.seek;473 const after_die_offset = fr.seek;
926 defer fbr.seek = after_die_offset;474 defer fr.seek = after_die_offset;
927475
928 // Follow the DIE it points to and repeat476 // Follow the DIE it points to and repeat
929 const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin, this_unit_offset, next_offset);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 this_die_obj = (try parseDie(479 this_die_obj = (try parseDie(
932 &fbr,480 &fr,
933 attrs_bufs[2],481 attrs_bufs[2],
934 abbrev_table, // wrong abbrev table for different cu482 abbrev_table, // wrong abbrev table for different cu
935 unit_header.format,483 unit_header.format,
936 endian,484 endian,
485 address_size,
937 )) orelse return bad();486 )) orelse return bad();
938 } else if (this_die_obj.getAttr(AT.specification)) |_| {487 } else if (this_die_obj.getAttr(AT.specification)) |_| {
939 const after_die_offset = fbr.seek;488 const after_die_offset = fr.seek;
940 defer fbr.seek = after_die_offset;489 defer fr.seek = after_die_offset;
941490
942 // Follow the DIE it points to and repeat491 // Follow the DIE it points to and repeat
943 const ref_offset = try this_die_obj.getAttrRef(AT.specification, this_unit_offset, next_offset);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 this_die_obj = (try parseDie(494 this_die_obj = (try parseDie(
946 &fbr,495 &fr,
947 attrs_bufs[2],496 attrs_bufs[2],
948 abbrev_table, // wrong abbrev table for different cu497 abbrev_table, // wrong abbrev table for different cu
949 unit_header.format,498 unit_header.format,
950 endian,499 endian,
500 address_size,
951 )) orelse return bad();501 )) orelse return bad();
952 } else {502 } else {
953 break :x null;503 break :x null;
...@@ -957,7 +507,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {...@@ -957,7 +507,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
957 break :x null;507 break :x null;
958 };508 };
959509
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 if (die_obj.getAttr(AT.high_pc)) |high_pc_value| {511 if (die_obj.getAttr(AT.high_pc)) |high_pc_value| {
962 const pc_end = switch (high_pc_value.*) {512 const pc_end = switch (high_pc_value.*) {
963 .addr => |value| value,513 .addr => |value| value,
...@@ -965,7 +515,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {...@@ -965,7 +515,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
965 else => return bad(),515 else => return bad(),
966 };516 };
967517
968 try di.func_list.append(allocator, .{518 try di.func_list.append(gpa, .{
969 .name = fn_name,519 .name = fn_name,
970 .pc_range = .{520 .pc_range = .{
971 .start = low_pc,521 .start = low_pc,
...@@ -983,14 +533,14 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {...@@ -983,14 +533,14 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
983 };533 };
984534
985 if (die_obj.getAttr(AT.ranges)) |ranges_value| blk: {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 if (err != error.MissingDebugInfo) return err;537 if (err != error.MissingDebugInfo) return err;
988 break :blk;538 break :blk;
989 };539 };
990540
991 while (try iter.next()) |range| {541 while (try iter.next()) |range| {
992 range_added = true;542 range_added = true;
993 try di.func_list.append(allocator, .{543 try di.func_list.append(gpa, .{
994 .name = fn_name,544 .name = fn_name,
995 .pc_range = .{545 .pc_range = .{
996 .start = range.start,546 .start = range.start,
...@@ -1001,7 +551,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {...@@ -1001,7 +551,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
1001 }551 }
1002552
1003 if (fn_name != null and !range_added) {553 if (fn_name != null and !range_added) {
1004 try di.func_list.append(allocator, .{554 try di.func_list.append(gpa, .{
1005 .name = fn_name,555 .name = fn_name,
1006 .pc_range = null,556 .pc_range = null,
1007 });557 });
...@@ -1015,38 +565,36 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {...@@ -1015,38 +565,36 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
1015 }565 }
1016}566}
1017567
1018fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {568fn scanAllCompileUnits(di: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void {
1019 const endian = di.endian;569 var fr: Reader = .fixed(di.section(.debug_info).?);
1020 var fbr: Reader = .fixed(di.section(.debug_info).?);
1021 var this_unit_offset: u64 = 0;570 var this_unit_offset: u64 = 0;
1022571
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 defer attrs_buf.deinit();573 defer attrs_buf.deinit();
1025574
1026 while (this_unit_offset < fbr.buffer.len) {575 while (this_unit_offset < fr.buffer.len) {
1027 fbr.seek = @intCast(this_unit_offset);576 fr.seek = @intCast(this_unit_offset);
1028577
1029 const unit_header = try readUnitHeader(&fbr, endian);578 const unit_header = try readUnitHeader(&fr, endian);
1030 if (unit_header.unit_length == 0) return;579 if (unit_header.unit_length == 0) return;
1031 const next_offset = unit_header.header_length + unit_header.unit_length;580 const next_offset = unit_header.header_length + unit_header.unit_length;
1032581
1033 const version = try fbr.takeInt(u16, endian);582 const version = try fr.takeInt(u16, endian);
1034 if (version < 2 or version > 5) return bad();583 if (version < 2 or version > 5) return bad();
1035584
1036 var address_size: u8 = undefined;585 var address_size: u8 = undefined;
1037 var debug_abbrev_offset: u64 = undefined;586 var debug_abbrev_offset: u64 = undefined;
1038 if (version >= 5) {587 if (version >= 5) {
1039 const unit_type = try fbr.takeByte();588 const unit_type = try fr.takeByte();
1040 if (unit_type != UT.compile) return bad();589 if (unit_type != UT.compile) return bad();
1041 address_size = try fbr.takeByte();590 address_size = try fr.takeByte();
1042 debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);591 debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian);
1043 } else {592 } else {
1044 debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);593 debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian);
1045 address_size = try fbr.takeByte();594 address_size = try fr.takeByte();
1046 }595 }
1047 if (address_size != @sizeOf(usize)) return bad();
1048596
1049 const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset);597 const abbrev_table = try di.getAbbrevTable(gpa, debug_abbrev_offset);
1050598
1051 var max_attrs: usize = 0;599 var max_attrs: usize = 0;
1052 for (abbrev_table.abbrevs) |abbrev| {600 for (abbrev_table.abbrevs) |abbrev| {
...@@ -1055,20 +603,22 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {...@@ -1055,20 +603,22 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
1055 try attrs_buf.resize(max_attrs);603 try attrs_buf.resize(max_attrs);
1056604
1057 var compile_unit_die = (try parseDie(605 var compile_unit_die = (try parseDie(
1058 &fbr,606 &fr,
1059 attrs_buf.items,607 attrs_buf.items,
1060 abbrev_table,608 abbrev_table,
1061 unit_header.format,609 unit_header.format,
1062 endian,610 endian,
611 address_size,
1063 )) orelse return bad();612 )) orelse return bad();
1064613
1065 if (compile_unit_die.tag_id != DW.TAG.compile_unit) return bad();614 if (compile_unit_die.tag_id != DW.TAG.compile_unit) return bad();
1066615
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);
1068617
1069 var compile_unit: CompileUnit = .{618 var compile_unit: CompileUnit = .{
1070 .version = version,619 .version = version,
1071 .format = unit_header.format,620 .format = unit_header.format,
621 .addr_size_bytes = address_size,
1072 .pc_range = null,622 .pc_range = null,
1073 .die = compile_unit_die,623 .die = compile_unit_die,
1074 .str_offsets_base = if (compile_unit_die.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0,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,7 +630,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
1080 };630 };
1081631
1082 compile_unit.pc_range = x: {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 if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| {634 if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| {
1085 const pc_end = switch (high_pc_value.*) {635 const pc_end = switch (high_pc_value.*) {
1086 .addr => |value| value,636 .addr => |value| value,
...@@ -1100,13 +650,13 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {...@@ -1100,13 +650,13 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
1100 }650 }
1101 };651 };
1102652
1103 try di.compile_unit_list.append(allocator, compile_unit);653 try di.compile_unit_list.append(gpa, compile_unit);
1104654
1105 this_unit_offset += next_offset;655 this_unit_offset += next_offset;
1106 }656 }
1107}657}
1108658
1109pub fn populateRanges(d: *Dwarf, gpa: Allocator) ScanError!void {659pub fn populateRanges(d: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void {
1110 assert(d.ranges.items.len == 0);660 assert(d.ranges.items.len == 0);
1111661
1112 for (d.compile_unit_list.items, 0..) |*cu, cu_index| {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,7 +669,7 @@ pub fn populateRanges(d: *Dwarf, gpa: Allocator) ScanError!void {
1119 continue;669 continue;
1120 }670 }
1121 const ranges_value = cu.die.getAttr(AT.ranges) orelse continue;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 while (try iter.next()) |range| {673 while (try iter.next()) |range| {
1124 // Not sure why LLVM thinks it's OK to emit these...674 // Not sure why LLVM thinks it's OK to emit these...
1125 if (range.start == range.end) continue;675 if (range.start == range.end) continue;
...@@ -1144,10 +694,11 @@ const DebugRangeIterator = struct {...@@ -1144,10 +694,11 @@ const DebugRangeIterator = struct {
1144 base_address: u64,694 base_address: u64,
1145 section_type: Section.Id,695 section_type: Section.Id,
1146 di: *const Dwarf,696 di: *const Dwarf,
697 endian: Endian,
1147 compile_unit: *const CompileUnit,698 compile_unit: *const CompileUnit,
1148 fbr: Reader,699 fr: Reader,
1149700
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 const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges;702 const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges;
1152 const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo;703 const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo;
1153704
...@@ -1156,15 +707,15 @@ const DebugRangeIterator = struct {...@@ -1156,15 +707,15 @@ const DebugRangeIterator = struct {
1156 .rnglistx => |idx| off: {707 .rnglistx => |idx| off: {
1157 switch (compile_unit.format) {708 switch (compile_unit.format) {
1158 .@"32" => {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 if (offset_loc + 4 > debug_ranges.len) return bad();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 break :off compile_unit.rnglists_base + offset;713 break :off compile_unit.rnglists_base + offset;
1163 },714 },
1164 .@"64" => {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 if (offset_loc + 8 > debug_ranges.len) return bad();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 break :off compile_unit.rnglists_base + offset;719 break :off compile_unit.rnglists_base + offset;
1169 },720 },
1170 }721 }
...@@ -1176,42 +727,44 @@ const DebugRangeIterator = struct {...@@ -1176,42 +727,44 @@ const DebugRangeIterator = struct {
1176 // specified by DW_AT.low_pc or to some other value encoded727 // specified by DW_AT.low_pc or to some other value encoded
1177 // in the list itself.728 // in the list itself.
1178 // If no starting value is specified use zero.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 error.MissingDebugInfo => 0,731 error.MissingDebugInfo => 0,
1181 else => return err,732 else => return err,
1182 };733 };
1183734
1184 var fbr: Reader = .fixed(debug_ranges);735 var fr: Reader = .fixed(debug_ranges);
1185 fbr.seek = cast(usize, ranges_offset) orelse return bad();736 fr.seek = cast(usize, ranges_offset) orelse return bad();
1186737
1187 return .{738 return .{
1188 .base_address = base_address,739 .base_address = base_address,
1189 .section_type = section_type,740 .section_type = section_type,
1190 .di = di,741 .di = di,
742 .endian = endian,
1191 .compile_unit = compile_unit,743 .compile_unit = compile_unit,
1192 .fbr = fbr,744 .fr = fr,
1193 };745 };
1194 }746 }
1195747
1196 // Returns the next range in the list, or null if the end was reached.748 // Returns the next range in the list, or null if the end was reached.
1197 pub fn next(self: *@This()) !?PcRange {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 switch (self.section_type) {752 switch (self.section_type) {
1200 .debug_rnglists => {753 .debug_rnglists => {
1201 const kind = try self.fbr.takeByte();754 const kind = try self.fr.takeByte();
1202 switch (kind) {755 switch (kind) {
1203 RLE.end_of_list => return null,756 RLE.end_of_list => return null,
1204 RLE.base_addressx => {757 RLE.base_addressx => {
1205 const index = try self.fbr.takeLeb128(usize);758 const index = try self.fr.takeLeb128(u64);
1206 self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index);759 self.base_address = try self.di.readDebugAddr(endian, self.compile_unit, index);
1207 return try self.next();760 return try self.next();
1208 },761 },
1209 RLE.startx_endx => {762 RLE.startx_endx => {
1210 const start_index = try self.fbr.takeLeb128(usize);763 const start_index = try self.fr.takeLeb128(u64);
1211 const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index);764 const start_addr = try self.di.readDebugAddr(endian, self.compile_unit, start_index);
1212765
1213 const end_index = try self.fbr.takeLeb128(usize);766 const end_index = try self.fr.takeLeb128(u64);
1214 const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index);767 const end_addr = try self.di.readDebugAddr(endian, self.compile_unit, end_index);
1215768
1216 return .{769 return .{
1217 .start = start_addr,770 .start = start_addr,
...@@ -1219,10 +772,10 @@ const DebugRangeIterator = struct {...@@ -1219,10 +772,10 @@ const DebugRangeIterator = struct {
1219 };772 };
1220 },773 },
1221 RLE.startx_length => {774 RLE.startx_length => {
1222 const start_index = try self.fbr.takeLeb128(usize);775 const start_index = try self.fr.takeLeb128(u64);
1223 const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index);776 const start_addr = try self.di.readDebugAddr(endian, self.compile_unit, start_index);
1224777
1225 const len = try self.fbr.takeLeb128(usize);778 const len = try self.fr.takeLeb128(u64);
1226 const end_addr = start_addr + len;779 const end_addr = start_addr + len;
1227780
1228 return .{781 return .{
...@@ -1231,8 +784,8 @@ const DebugRangeIterator = struct {...@@ -1231,8 +784,8 @@ const DebugRangeIterator = struct {
1231 };784 };
1232 },785 },
1233 RLE.offset_pair => {786 RLE.offset_pair => {
1234 const start_addr = try self.fbr.takeLeb128(usize);787 const start_addr = try self.fr.takeLeb128(u64);
1235 const end_addr = try self.fbr.takeLeb128(usize);788 const end_addr = try self.fr.takeLeb128(u64);
1236789
1237 // This is the only kind that uses the base address790 // This is the only kind that uses the base address
1238 return .{791 return .{
...@@ -1241,12 +794,12 @@ const DebugRangeIterator = struct {...@@ -1241,12 +794,12 @@ const DebugRangeIterator = struct {
1241 };794 };
1242 },795 },
1243 RLE.base_address => {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 return try self.next();798 return try self.next();
1246 },799 },
1247 RLE.start_end => {800 RLE.start_end => {
1248 const start_addr = try self.fbr.takeInt(usize, endian);801 const start_addr = try readAddress(&self.fr, endian, addr_size_bytes);
1249 const end_addr = try self.fbr.takeInt(usize, endian);802 const end_addr = try readAddress(&self.fr, endian, addr_size_bytes);
1250803
1251 return .{804 return .{
1252 .start = start_addr,805 .start = start_addr,
...@@ -1254,8 +807,8 @@ const DebugRangeIterator = struct {...@@ -1254,8 +807,8 @@ const DebugRangeIterator = struct {
1254 };807 };
1255 },808 },
1256 RLE.start_length => {809 RLE.start_length => {
1257 const start_addr = try self.fbr.takeInt(usize, endian);810 const start_addr = try readAddress(&self.fr, endian, addr_size_bytes);
1258 const len = try self.fbr.takeLeb128(usize);811 const len = try self.fr.takeLeb128(u64);
1259 const end_addr = start_addr + len;812 const end_addr = start_addr + len;
1260813
1261 return .{814 return .{
...@@ -1267,12 +820,13 @@ const DebugRangeIterator = struct {...@@ -1267,12 +820,13 @@ const DebugRangeIterator = struct {
1267 }820 }
1268 },821 },
1269 .debug_ranges => {822 .debug_ranges => {
1270 const start_addr = try self.fbr.takeInt(usize, endian);823 const start_addr = try readAddress(&self.fr, endian, addr_size_bytes);
1271 const end_addr = try self.fbr.takeInt(usize, endian);824 const end_addr = try readAddress(&self.fr, endian, addr_size_bytes);
1272 if (start_addr == 0 and end_addr == 0) return null;825 if (start_addr == 0 and end_addr == 0) return null;
1273826
1274 // This entry selects a new value for the base address827 // The entry with start_addr = max_representable_address selects a new value for the base address
1275 if (start_addr == maxInt(usize)) {828 const max_representable_address = ~@as(u64, 0) >> @intCast(64 - addr_size_bytes);
829 if (start_addr == max_representable_address) {
1276 self.base_address = end_addr;830 self.base_address = end_addr;
1277 return try self.next();831 return try self.next();
1278 }832 }
...@@ -1288,14 +842,14 @@ const DebugRangeIterator = struct {...@@ -1288,14 +842,14 @@ const DebugRangeIterator = struct {
1288};842};
1289843
1290/// TODO: change this to binary searching the sorted compile unit list844/// TODO: change this to binary searching the sorted compile unit list
1291pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*CompileUnit {845pub fn findCompileUnit(di: *const Dwarf, endian: Endian, target_address: u64) !*CompileUnit {
1292 for (di.compile_unit_list.items) |*compile_unit| {846 for (di.compile_unit_list.items) |*compile_unit| {
1293 if (compile_unit.pc_range) |range| {847 if (compile_unit.pc_range) |range| {
1294 if (target_address >= range.start and target_address < range.end) return compile_unit;848 if (target_address >= range.start and target_address < range.end) return compile_unit;
1295 }849 }
1296850
1297 const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue;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 while (try iter.next()) |range| {853 while (try iter.next()) |range| {
1300 if (target_address >= range.start and target_address < range.end) return compile_unit;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,49 +860,49 @@ pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*CompileUnit {
1306860
1307/// Gets an already existing AbbrevTable given the abbrev_offset, or if not found,861/// Gets an already existing AbbrevTable given the abbrev_offset, or if not found,
1308/// seeks in the stream and parses it.862/// seeks in the stream and parses it.
1309fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const Abbrev.Table {863fn getAbbrevTable(di: *Dwarf, gpa: Allocator, abbrev_offset: u64) !*const Abbrev.Table {
1310 for (di.abbrev_table_list.items) |*table| {864 for (di.abbrev_table_list.items) |*table| {
1311 if (table.offset == abbrev_offset) {865 if (table.offset == abbrev_offset) {
1312 return table;866 return table;
1313 }867 }
1314 }868 }
1315 try di.abbrev_table_list.append(869 try di.abbrev_table_list.append(
1316 allocator,870 gpa,
1317 try di.parseAbbrevTable(allocator, abbrev_offset),871 try di.parseAbbrevTable(gpa, abbrev_offset),
1318 );872 );
1319 return &di.abbrev_table_list.items[di.abbrev_table_list.items.len - 1];873 return &di.abbrev_table_list.items[di.abbrev_table_list.items.len - 1];
1320}874}
1321875
1322fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table {876fn parseAbbrevTable(di: *Dwarf, gpa: Allocator, offset: u64) !Abbrev.Table {
1323 var fbr: Reader = .fixed(di.section(.debug_abbrev).?);877 var fr: Reader = .fixed(di.section(.debug_abbrev).?);
1324 fbr.seek = cast(usize, offset) orelse return bad();878 fr.seek = cast(usize, offset) orelse return bad();
1325879
1326 var abbrevs = std.array_list.Managed(Abbrev).init(allocator);880 var abbrevs = std.array_list.Managed(Abbrev).init(gpa);
1327 defer {881 defer {
1328 for (abbrevs.items) |*abbrev| {882 for (abbrevs.items) |*abbrev| {
1329 abbrev.deinit(allocator);883 abbrev.deinit(gpa);
1330 }884 }
1331 abbrevs.deinit();885 abbrevs.deinit();
1332 }886 }
1333887
1334 var attrs = std.array_list.Managed(Abbrev.Attr).init(allocator);888 var attrs = std.array_list.Managed(Abbrev.Attr).init(gpa);
1335 defer attrs.deinit();889 defer attrs.deinit();
1336890
1337 while (true) {891 while (true) {
1338 const code = try fbr.takeLeb128(u64);892 const code = try fr.takeLeb128(u64);
1339 if (code == 0) break;893 if (code == 0) break;
1340 const tag_id = try fbr.takeLeb128(u64);894 const tag_id = try fr.takeLeb128(u64);
1341 const has_children = (try fbr.takeByte()) == DW.CHILDREN.yes;895 const has_children = (try fr.takeByte()) == DW.CHILDREN.yes;
1342896
1343 while (true) {897 while (true) {
1344 const attr_id = try fbr.takeLeb128(u64);898 const attr_id = try fr.takeLeb128(u64);
1345 const form_id = try fbr.takeLeb128(u64);899 const form_id = try fr.takeLeb128(u64);
1346 if (attr_id == 0 and form_id == 0) break;900 if (attr_id == 0 and form_id == 0) break;
1347 try attrs.append(.{901 try attrs.append(.{
1348 .id = attr_id,902 .id = attr_id,
1349 .form_id = form_id,903 .form_id = form_id,
1350 .payload = switch (form_id) {904 .payload = switch (form_id) {
1351 FORM.implicit_const => try fbr.takeLeb128(i64),905 FORM.implicit_const => try fr.takeLeb128(i64),
1352 else => undefined,906 else => undefined,
1353 },907 },
1354 });908 });
...@@ -1369,20 +923,21 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table...@@ -1369,20 +923,21 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table
1369}923}
1370924
1371fn parseDie(925fn parseDie(
1372 fbr: *Reader,926 fr: *Reader,
1373 attrs_buf: []Die.Attr,927 attrs_buf: []Die.Attr,
1374 abbrev_table: *const Abbrev.Table,928 abbrev_table: *const Abbrev.Table,
1375 format: Format,929 format: Format,
1376 endian: Endian,930 endian: Endian,
931 addr_size_bytes: u8,
1377) ScanError!?Die {932) ScanError!?Die {
1378 const abbrev_code = try fbr.takeLeb128(u64);933 const abbrev_code = try fr.takeLeb128(u64);
1379 if (abbrev_code == 0) return null;934 if (abbrev_code == 0) return null;
1380 const table_entry = abbrev_table.get(abbrev_code) orelse return bad();935 const table_entry = abbrev_table.get(abbrev_code) orelse return bad();
1381936
1382 const attrs = attrs_buf[0..table_entry.attrs.len];937 const attrs = attrs_buf[0..table_entry.attrs.len];
1383 for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = .{938 for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = .{
1384 .id = attr.id,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 return .{942 return .{
1388 .tag_id = table_entry.tag_id,943 .tag_id = table_entry.tag_id,
...@@ -1392,55 +947,50 @@ fn parseDie(...@@ -1392,55 +947,50 @@ fn parseDie(
1392}947}
1393948
1394/// Ensures that addresses in the returned LineTable are monotonically increasing.949/// Ensures that addresses in the returned LineTable are monotonically increasing.
1395fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !CompileUnit.SrcLocCache {950fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit: *const CompileUnit) !CompileUnit.SrcLocCache {
1396 const endian = d.endian;951 const compile_unit_cwd = try compile_unit.die.getAttrString(d, endian, AT.comp_dir, d.section(.debug_line_str), compile_unit);
1397 const compile_unit_cwd = try compile_unit.die.getAttrString(d, AT.comp_dir, d.section(.debug_line_str), compile_unit.*);
1398 const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);952 const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
1399953
1400 var fbr: Reader = .fixed(d.section(.debug_line).?);954 var fr: Reader = .fixed(d.section(.debug_line).?);
1401 fbr.seek = @intCast(line_info_offset);955 fr.seek = @intCast(line_info_offset);
1402956
1403 const unit_header = try readUnitHeader(&fbr, endian);957 const unit_header = try readUnitHeader(&fr, endian);
1404 if (unit_header.unit_length == 0) return missing();958 if (unit_header.unit_length == 0) return missing();
1405959
1406 const next_offset = unit_header.header_length + unit_header.unit_length;960 const next_offset = unit_header.header_length + unit_header.unit_length;
1407961
1408 const version = try fbr.takeInt(u16, endian);962 const version = try fr.takeInt(u16, endian);
1409 if (version < 2) return bad();963 if (version < 2) return bad();
1410964
1411 const addr_size: u8, const seg_size: u8 = if (version >= 5) .{965 const addr_size_bytes: u8, const seg_size: u8 = if (version >= 5) .{
1412 try fbr.takeByte(),966 try fr.takeByte(),
1413 try fbr.takeByte(),967 try fr.takeByte(),
1414 } else .{968 } else .{
1415 switch (unit_header.format) {969 compile_unit.addr_size_bytes,
1416 .@"32" => 4,
1417 .@"64" => 8,
1418 },
1419 0,970 0,
1420 };971 };
1421 _ = addr_size;972 if (seg_size != 0) return bad(); // unsupported
1422 _ = seg_size;
1423973
1424 const prologue_length = try readAddress(&fbr, unit_header.format, endian);974 const prologue_length = try readFormatSizedInt(&fr, unit_header.format, endian);
1425 const prog_start_offset = fbr.seek + prologue_length;975 const prog_start_offset = fr.seek + prologue_length;
1426976
1427 const minimum_instruction_length = try fbr.takeByte();977 const minimum_instruction_length = try fr.takeByte();
1428 if (minimum_instruction_length == 0) return bad();978 if (minimum_instruction_length == 0) return bad();
1429979
1430 if (version >= 4) {980 if (version >= 4) {
1431 const maximum_operations_per_instruction = try fbr.takeByte();981 const maximum_operations_per_instruction = try fr.takeByte();
1432 _ = maximum_operations_per_instruction;982 _ = maximum_operations_per_instruction;
1433 }983 }
1434984
1435 const default_is_stmt = (try fbr.takeByte()) != 0;985 const default_is_stmt = (try fr.takeByte()) != 0;
1436 const line_base = try fbr.takeByteSigned();986 const line_base = try fr.takeByteSigned();
1437987
1438 const line_range = try fbr.takeByte();988 const line_range = try fr.takeByte();
1439 if (line_range == 0) return bad();989 if (line_range == 0) return bad();
1440990
1441 const opcode_base = try fbr.takeByte();991 const opcode_base = try fr.takeByte();
1442992
1443 const standard_opcode_lengths = try fbr.take(opcode_base - 1);993 const standard_opcode_lengths = try fr.take(opcode_base - 1);
1444994
1445 var directories: ArrayList(FileEntry) = .empty;995 var directories: ArrayList(FileEntry) = .empty;
1446 defer directories.deinit(gpa);996 defer directories.deinit(gpa);
...@@ -1451,17 +1001,17 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !...@@ -1451,17 +1001,17 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
1451 try directories.append(gpa, .{ .path = compile_unit_cwd });1001 try directories.append(gpa, .{ .path = compile_unit_cwd });
14521002
1453 while (true) {1003 while (true) {
1454 const dir = try fbr.takeSentinel(0);1004 const dir = try fr.takeSentinel(0);
1455 if (dir.len == 0) break;1005 if (dir.len == 0) break;
1456 try directories.append(gpa, .{ .path = dir });1006 try directories.append(gpa, .{ .path = dir });
1457 }1007 }
14581008
1459 while (true) {1009 while (true) {
1460 const file_name = try fbr.takeSentinel(0);1010 const file_name = try fr.takeSentinel(0);
1461 if (file_name.len == 0) break;1011 if (file_name.len == 0) break;
1462 const dir_index = try fbr.takeLeb128(u32);1012 const dir_index = try fr.takeLeb128(u32);
1463 const mtime = try fbr.takeLeb128(u64);1013 const mtime = try fr.takeLeb128(u64);
1464 const size = try fbr.takeLeb128(u64);1014 const size = try fr.takeLeb128(u64);
1465 try file_entries.append(gpa, .{1015 try file_entries.append(gpa, .{
1466 .path = file_name,1016 .path = file_name,
1467 .dir_index = dir_index,1017 .dir_index = dir_index,
...@@ -1476,21 +1026,21 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !...@@ -1476,21 +1026,21 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
1476 };1026 };
1477 {1027 {
1478 var dir_ent_fmt_buf: [10]FileEntFmt = undefined;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 if (directory_entry_format_count > dir_ent_fmt_buf.len) return bad();1030 if (directory_entry_format_count > dir_ent_fmt_buf.len) return bad();
1481 for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| {1031 for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| {
1482 ent_fmt.* = .{1032 ent_fmt.* = .{
1483 .content_type_code = try fbr.takeLeb128(u8),1033 .content_type_code = try fr.takeLeb128(u8),
1484 .form_code = try fbr.takeLeb128(u16),1034 .form_code = try fr.takeLeb128(u16),
1485 };1035 };
1486 }1036 }
14871037
1488 const directories_count = try fbr.takeLeb128(usize);1038 const directories_count = try fr.takeLeb128(usize);
14891039
1490 for (try directories.addManyAsSlice(gpa, directories_count)) |*e| {1040 for (try directories.addManyAsSlice(gpa, directories_count)) |*e| {
1491 e.* = .{ .path = &.{} };1041 e.* = .{ .path = &.{} };
1492 for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| {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 switch (ent_fmt.content_type_code) {1044 switch (ent_fmt.content_type_code) {
1495 DW.LNCT.path => e.path = try form_value.getString(d.*),1045 DW.LNCT.path => e.path = try form_value.getString(d.*),
1496 DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),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,22 +1057,22 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
1507 }1057 }
15081058
1509 var file_ent_fmt_buf: [10]FileEntFmt = undefined;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 if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad();1061 if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad();
1512 for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| {1062 for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| {
1513 ent_fmt.* = .{1063 ent_fmt.* = .{
1514 .content_type_code = try fbr.takeLeb128(u16),1064 .content_type_code = try fr.takeLeb128(u16),
1515 .form_code = try fbr.takeLeb128(u16),1065 .form_code = try fr.takeLeb128(u16),
1516 };1066 };
1517 }1067 }
15181068
1519 const file_names_count = try fbr.takeLeb128(usize);1069 const file_names_count = try fr.takeLeb128(usize);
1520 try file_entries.ensureUnusedCapacity(gpa, file_names_count);1070 try file_entries.ensureUnusedCapacity(gpa, file_names_count);
15211071
1522 for (try file_entries.addManyAsSlice(gpa, file_names_count)) |*e| {1072 for (try file_entries.addManyAsSlice(gpa, file_names_count)) |*e| {
1523 e.* = .{ .path = &.{} };1073 e.* = .{ .path = &.{} };
1524 for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| {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 switch (ent_fmt.content_type_code) {1076 switch (ent_fmt.content_type_code) {
1527 DW.LNCT.path => e.path = try form_value.getString(d.*),1077 DW.LNCT.path => e.path = try form_value.getString(d.*),
1528 DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),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,17 +1092,17 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
1542 var line_table: CompileUnit.SrcLocCache.LineTable = .{};1092 var line_table: CompileUnit.SrcLocCache.LineTable = .{};
1543 errdefer line_table.deinit(gpa);1093 errdefer line_table.deinit(gpa);
15441094
1545 fbr.seek = @intCast(prog_start_offset);1095 fr.seek = @intCast(prog_start_offset);
15461096
1547 const next_unit_pos = line_info_offset + next_offset;1097 const next_unit_pos = line_info_offset + next_offset;
15481098
1549 while (fbr.seek < next_unit_pos) {1099 while (fr.seek < next_unit_pos) {
1550 const opcode = try fbr.takeByte();1100 const opcode = try fr.takeByte();
15511101
1552 if (opcode == DW.LNS.extended_op) {1102 if (opcode == DW.LNS.extended_op) {
1553 const op_size = try fbr.takeLeb128(u64);1103 const op_size = try fr.takeLeb128(u64);
1554 if (op_size < 1) return bad();1104 if (op_size < 1) return bad();
1555 const sub_op = try fbr.takeByte();1105 const sub_op = try fr.takeByte();
1556 switch (sub_op) {1106 switch (sub_op) {
1557 DW.LNE.end_sequence => {1107 DW.LNE.end_sequence => {
1558 // The row being added here is an "end" address, meaning1108 // The row being added here is an "end" address, meaning
...@@ -1571,14 +1121,13 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !...@@ -1571,14 +1121,13 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
1571 prog.reset();1121 prog.reset();
1572 },1122 },
1573 DW.LNE.set_address => {1123 DW.LNE.set_address => {
1574 const addr = try fbr.takeInt(usize, endian);1124 prog.address = try readAddress(&fr, endian, addr_size_bytes);
1575 prog.address = addr;
1576 },1125 },
1577 DW.LNE.define_file => {1126 DW.LNE.define_file => {
1578 const path = try fbr.takeSentinel(0);1127 const path = try fr.takeSentinel(0);
1579 const dir_index = try fbr.takeLeb128(u32);1128 const dir_index = try fr.takeLeb128(u32);
1580 const mtime = try fbr.takeLeb128(u64);1129 const mtime = try fr.takeLeb128(u64);
1581 const size = try fbr.takeLeb128(u64);1130 const size = try fr.takeLeb128(u64);
1582 try file_entries.append(gpa, .{1131 try file_entries.append(gpa, .{
1583 .path = path,1132 .path = path,
1584 .dir_index = dir_index,1133 .dir_index = dir_index,
...@@ -1586,7 +1135,7 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !...@@ -1586,7 +1135,7 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
1586 .size = size,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 } else if (opcode >= opcode_base) {1140 } else if (opcode >= opcode_base) {
1592 // special opcodes1141 // special opcodes
...@@ -1604,19 +1153,19 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !...@@ -1604,19 +1153,19 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
1604 prog.basic_block = false;1153 prog.basic_block = false;
1605 },1154 },
1606 DW.LNS.advance_pc => {1155 DW.LNS.advance_pc => {
1607 const arg = try fbr.takeLeb128(usize);1156 const arg = try fr.takeLeb128(u64);
1608 prog.address += arg * minimum_instruction_length;1157 prog.address += arg * minimum_instruction_length;
1609 },1158 },
1610 DW.LNS.advance_line => {1159 DW.LNS.advance_line => {
1611 const arg = try fbr.takeLeb128(i64);1160 const arg = try fr.takeLeb128(i64);
1612 prog.line += arg;1161 prog.line += arg;
1613 },1162 },
1614 DW.LNS.set_file => {1163 DW.LNS.set_file => {
1615 const arg = try fbr.takeLeb128(usize);1164 const arg = try fr.takeLeb128(usize);
1616 prog.file = arg;1165 prog.file = arg;
1617 },1166 },
1618 DW.LNS.set_column => {1167 DW.LNS.set_column => {
1619 const arg = try fbr.takeLeb128(u64);1168 const arg = try fr.takeLeb128(u64);
1620 prog.column = arg;1169 prog.column = arg;
1621 },1170 },
1622 DW.LNS.negate_stmt => {1171 DW.LNS.negate_stmt => {
...@@ -1630,13 +1179,13 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !...@@ -1630,13 +1179,13 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
1630 prog.address += inc_addr;1179 prog.address += inc_addr;
1631 },1180 },
1632 DW.LNS.fixed_advance_pc => {1181 DW.LNS.fixed_advance_pc => {
1633 const arg = try fbr.takeInt(u16, endian);1182 const arg = try fr.takeInt(u16, endian);
1634 prog.address += arg;1183 prog.address += arg;
1635 },1184 },
1636 DW.LNS.set_prologue_end => {},1185 DW.LNS.set_prologue_end => {},
1637 else => {1186 else => {
1638 if (opcode - 1 >= standard_opcode_lengths.len) return bad();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,18 +1210,19 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
1661 };1210 };
1662}1211}
16631212
1664pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, cu: *CompileUnit) ScanError!void {1213pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, endian: Endian, cu: *CompileUnit) ScanError!void {
1665 if (cu.src_loc_cache != null) return;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}
16681217
1669pub fn getLineNumberInfo(1218pub fn getLineNumberInfo(
1670 d: *Dwarf,1219 d: *Dwarf,
1671 gpa: Allocator,1220 gpa: Allocator,
1221 endian: Endian,
1672 compile_unit: *CompileUnit,1222 compile_unit: *CompileUnit,
1673 target_address: u64,1223 target_address: u64,
1674) !std.debug.SourceLocation {1224) !std.debug.SourceLocation {
1675 try populateSrcLocCache(d, gpa, compile_unit);1225 try d.populateSrcLocCache(gpa, endian, compile_unit);
1676 const slc = &compile_unit.src_loc_cache.?;1226 const slc = &compile_unit.src_loc_cache.?;
1677 const entry = try slc.findSource(target_address);1227 const entry = try slc.findSource(target_address);
1678 const file_index = entry.file - @intFromBool(slc.version < 5);1228 const file_index = entry.file - @intFromBool(slc.version < 5);
...@@ -1696,7 +1246,7 @@ fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 {...@@ -1696,7 +1246,7 @@ fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 {
1696 return getStringGeneric(di.section(.debug_line_str), offset);1246 return getStringGeneric(di.section(.debug_line_str), offset);
1697}1247}
16981248
1699fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {1249fn readDebugAddr(di: Dwarf, endian: Endian, compile_unit: *const CompileUnit, index: u64) !u64 {
1700 const debug_addr = di.section(.debug_addr) orelse return bad();1250 const debug_addr = di.section(.debug_addr) orelse return bad();
17011251
1702 // addr_base points to the first item after the header, however we1252 // 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,139 +1255,40 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {
1705 // The header is 8 or 12 bytes depending on is_64.1255 // The header is 8 or 12 bytes depending on is_64.
1706 if (compile_unit.addr_base < 8) return bad();1256 if (compile_unit.addr_base < 8) return bad();
17071257
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 if (version != 5) return bad();1259 if (version != 5) return bad();
17101260
1711 const addr_size = debug_addr[compile_unit.addr_base - 2];1261 const addr_size = debug_addr[compile_unit.addr_base - 2];
1712 const seg_size = debug_addr[compile_unit.addr_base - 1];1262 const seg_size = debug_addr[compile_unit.addr_base - 1];
17131263
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 if (byte_offset + addr_size > debug_addr.len) return bad();1265 if (byte_offset + addr_size > debug_addr.len) return bad();
1716 return switch (addr_size) {1266 return switch (addr_size) {
1717 1 => debug_addr[byte_offset],1267 1 => debug_addr[@intCast(byte_offset)],
1718 2 => mem.readInt(u16, debug_addr[byte_offset..][0..2], di.endian),1268 2 => mem.readInt(u16, debug_addr[@intCast(byte_offset)..][0..2], endian),
1719 4 => mem.readInt(u32, debug_addr[byte_offset..][0..4], di.endian),1269 4 => mem.readInt(u32, debug_addr[@intCast(byte_offset)..][0..4], endian),
1720 8 => mem.readInt(u64, debug_addr[byte_offset..][0..8], di.endian),1270 8 => mem.readInt(u64, debug_addr[@intCast(byte_offset)..][0..8], endian),
1721 else => bad(),1271 else => bad(),
1722 };1272 };
1723}1273}
17241274
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`.
1731pub 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.
1777pub 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
1825fn parseFormValue(1275fn parseFormValue(
1826 r: *Reader,1276 r: *Reader,
1827 form_id: u64,1277 form_id: u64,
1828 format: Format,1278 format: Format,
1829 endian: Endian,1279 endian: Endian,
1280 addr_size_bytes: u8,
1830 implicit_const: ?i64,1281 implicit_const: ?i64,
1831) ScanError!FormValue {1282) ScanError!FormValue {
1832 return switch (form_id) {1283 return switch (form_id) {
1833 // DWARF5.pdf page 213: the size of this value is encoded in the1284 // DWARF5.pdf page 213: the size of this value is encoded in the
1834 // compilation unit header as address size.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 FORM.addrx1 => .{ .addrx = try r.takeByte() },1287 FORM.addrx1 => .{ .addrx = try r.takeByte() },
1837 FORM.addrx2 => .{ .addrx = try r.takeInt(u16, endian) },1288 FORM.addrx2 => .{ .addrx = try r.takeInt(u16, endian) },
1838 FORM.addrx3 => .{ .addrx = try r.takeInt(u24, endian) },1289 FORM.addrx3 => .{ .addrx = try r.takeInt(u24, endian) },
1839 FORM.addrx4 => .{ .addrx = try r.takeInt(u32, endian) },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) },
18411292
1842 FORM.block1 => .{ .block = try r.take(try r.takeByte()) },1293 FORM.block1 => .{ .block = try r.take(try r.takeByte()) },
1843 FORM.block2 => .{ .block = try r.take(try r.takeInt(u16, endian)) },1294 FORM.block2 => .{ .block = try r.take(try r.takeInt(u16, endian)) },
...@@ -1854,7 +1305,7 @@ fn parseFormValue(...@@ -1854,7 +1305,7 @@ fn parseFormValue(
1854 FORM.exprloc => .{ .exprloc = try r.take(try r.takeLeb128(usize)) },1305 FORM.exprloc => .{ .exprloc = try r.take(try r.takeLeb128(usize)) },
1855 FORM.flag => .{ .flag = (try r.takeByte()) != 0 },1306 FORM.flag => .{ .flag = (try r.takeByte()) != 0 },
1856 FORM.flag_present => .{ .flag = true },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) },
18581309
1859 FORM.ref1 => .{ .ref = try r.takeByte() },1310 FORM.ref1 => .{ .ref = try r.takeByte() },
1860 FORM.ref2 => .{ .ref = try r.takeInt(u16, endian) },1311 FORM.ref2 => .{ .ref = try r.takeInt(u16, endian) },
...@@ -1862,18 +1313,18 @@ fn parseFormValue(...@@ -1862,18 +1313,18 @@ fn parseFormValue(
1862 FORM.ref8 => .{ .ref = try r.takeInt(u64, endian) },1313 FORM.ref8 => .{ .ref = try r.takeInt(u64, endian) },
1863 FORM.ref_udata => .{ .ref = try r.takeLeb128(u64) },1314 FORM.ref_udata => .{ .ref = try r.takeLeb128(u64) },
18641315
1865 FORM.ref_addr => .{ .ref_addr = try readAddress(r, format, endian) },1316 FORM.ref_addr => .{ .ref_addr = try readFormatSizedInt(r, format, endian) },
1866 FORM.ref_sig8 => .{ .ref = try r.takeInt(u64, endian) },1317 FORM.ref_sig8 => .{ .ref = try r.takeInt(u64, endian) },
18671318
1868 FORM.string => .{ .string = try r.takeSentinel(0) },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 FORM.strx1 => .{ .strx = try r.takeByte() },1321 FORM.strx1 => .{ .strx = try r.takeByte() },
1871 FORM.strx2 => .{ .strx = try r.takeInt(u16, endian) },1322 FORM.strx2 => .{ .strx = try r.takeInt(u16, endian) },
1872 FORM.strx3 => .{ .strx = try r.takeInt(u24, endian) },1323 FORM.strx3 => .{ .strx = try r.takeInt(u24, endian) },
1873 FORM.strx4 => .{ .strx = try r.takeInt(u32, endian) },1324 FORM.strx4 => .{ .strx = try r.takeInt(u32, endian) },
1874 FORM.strx => .{ .strx = try r.takeLeb128(usize) },1325 FORM.strx => .{ .strx = try r.takeLeb128(usize) },
1875 FORM.line_strp => .{ .line_strp = try readAddress(r, format, endian) },1326 FORM.line_strp => .{ .line_strp = try readFormatSizedInt(r, format, endian) },
1876 FORM.indirect => parseFormValue(r, try r.takeLeb128(u64), format, endian, implicit_const),1327 FORM.indirect => parseFormValue(r, try r.takeLeb128(u64), format, endian, addr_size_bytes, implicit_const),
1877 FORM.implicit_const => .{ .sdata = implicit_const orelse return bad() },1328 FORM.implicit_const => .{ .sdata = implicit_const orelse return bad() },
1878 FORM.loclistx => .{ .loclistx = try r.takeLeb128(u64) },1329 FORM.loclistx => .{ .loclistx = try r.takeLeb128(u64) },
1879 FORM.rnglistx => .{ .rnglistx = try r.takeLeb128(u64) },1330 FORM.rnglistx => .{ .rnglistx = try r.takeLeb128(u64) },
...@@ -1946,7 +1397,7 @@ const UnitHeader = struct {...@@ -1946,7 +1397,7 @@ const UnitHeader = struct {
1946 unit_length: u64,1397 unit_length: u64,
1947};1398};
19481399
1949fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader {1400pub fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader {
1950 return switch (try r.takeInt(u32, endian)) {1401 return switch (try r.takeInt(u32, endian)) {
1951 0...0xfffffff0 - 1 => |unit_length| .{1402 0...0xfffffff0 - 1 => |unit_length| .{
1952 .format = .@"32",1403 .format = .@"32",
...@@ -1963,7 +1414,7 @@ fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader {...@@ -1963,7 +1414,7 @@ fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader {
1963}1414}
19641415
1965/// Returns the DWARF register number for an x86_64 register number found in compact unwind info1416/// Returns the DWARF register number for an x86_64 register number found in compact unwind info
1966pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {1417pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u16 {
1967 return switch (unwind_reg_number) {1418 return switch (unwind_reg_number) {
1968 1 => 3, // RBX1419 1 => 3, // RBX
1969 2 => 12, // R121420 2 => 12, // R12
...@@ -1971,7 +1422,61 @@ pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {...@@ -1971,7 +1422,61 @@ pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {
1971 4 => 14, // R141422 4 => 14, // R14
1972 5 => 15, // R151423 5 => 15, // R15
1973 6 => 6, // RBP1424 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.
1430pub 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
1440pub 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
1450pub 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.
1464pub 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}
19771482
...@@ -1982,11 +1487,11 @@ pub fn bad() error{InvalidDebugInfo} {...@@ -1982,11 +1487,11 @@ pub fn bad() error{InvalidDebugInfo} {
1982 return error.InvalidDebugInfo;1487 return error.InvalidDebugInfo;
1983}1488}
19841489
1985fn invalidDebugInfoDetected() void {1490pub fn invalidDebugInfoDetected() void {
1986 if (debug_debug_mode) @panic("bad dwarf");1491 if (debug_debug_mode) @panic("bad dwarf");
1987}1492}
19881493
1989fn missing() error{MissingDebugInfo} {1494pub fn missing() error{MissingDebugInfo} {
1990 if (debug_debug_mode) @panic("missing dwarf");1495 if (debug_debug_mode) @panic("missing dwarf");
1991 return error.MissingDebugInfo;1496 return error.MissingDebugInfo;
1992}1497}
...@@ -2000,460 +1505,41 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {...@@ -2000,460 +1505,41 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
2000 return str[casted_offset..last :0];1505 return str[casted_offset..last :0];
2001}1506}
20021507
2003const EhPointerContext = struct {1508pub fn getSymbol(di: *Dwarf, gpa: Allocator, endian: Endian, address: u64) !std.debug.Symbol {
2004 // The address of the pointer field itself1509 const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) {
2005 pc_rel_base: u64,1510 error.MissingDebugInfo, error.InvalidDebugInfo => return .unknown,
20061511 else => return err,
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
2019fn 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,
2060 };1512 };
20611513 return .{
2062 if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) {1514 .name = di.getSymbolName(address),
2063 if (@sizeOf(usize) != addr_size_bytes) {1515 .compile_unit_name = compile_unit.die.getAttrString(di, endian, std.dwarf.AT.name, di.section(.debug_str), compile_unit) catch |err| switch (err) {
2064 // See the documentation for `follow_indirect`1516 error.MissingDebugInfo, error.InvalidDebugInfo => null,
2065 return error.NonNativeIndirection;1517 },
2066 }1518 .source_location = di.getLineNumberInfo(gpa, endian, compile_unit, address) catch |err| switch (err) {
20671519 error.MissingDebugInfo, error.InvalidDebugInfo => null,
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
2078fn 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
2086pub 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(),
2390 else => return err,1520 else => return err,
2391 };1521 },
2392 defer elf_file.close();1522 };
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
2422pub 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
2440pub 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];
2444}1523}
24451524
2446fn 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.
1531fn readFormatSizedInt(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
2447 return switch (format) {1532 return switch (format) {
2448 .@"32" => try r.takeInt(u32, endian),1533 .@"32" => try r.takeInt(u32, endian),
2449 .@"64" => try r.takeInt(u64, endian),1534 .@"64" => try r.takeInt(u64, endian),
2450 };1535 };
2451}1536}
24521537
2453fn nativeFormat() std.dwarf.Format {1538fn readAddress(r: *Reader, endian: Endian, addr_size_bytes: u8) !u64 {
2454 return switch (@sizeOf(usize)) {1539 return switch (addr_size_bytes) {
2455 4 => .@"32",1540 2 => try r.takeInt(u16, endian),
2456 8 => .@"64",1541 4 => try r.takeInt(u32, endian),
2457 else => @compileError("unsupported @sizeOf(usize)"),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.
9cpu_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.
12pc: usize,
13
14cfi_vm: Dwarf.Unwind.VirtualMachine,
15expr_vm: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
16
17pub 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
49pub 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
65pub fn deinit(unwinder: *SelfUnwinder, gpa: Allocator) void {
66 unwinder.cfi_vm.deinit(gpa);
67 unwinder.expr_vm.deinit(gpa);
68 unwinder.* = undefined;
69}
70
71pub 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`.
86pub 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`.
135pub 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
168fn 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
301pub 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.
314fn 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
321const ip_reg_num = Dwarf.ipRegNum(builtin.target.cpu.arch).?;
322const fp_reg_num = Dwarf.fpRegNum(builtin.target.cpu.arch);
323const sp_reg_num = Dwarf.spRegNum(builtin.target.cpu.arch);
324
325const std = @import("std");
326const Allocator = std.mem.Allocator;
327const Dwarf = std.debug.Dwarf;
328const assert = std.debug.assert;
329const stripInstructionPtrAuthCode = std.debug.stripInstructionPtrAuthCode;
330
331const builtin = @import("builtin");
332const native_endian = builtin.target.cpu.arch.endian();
333
334const 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
23pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");
24
25frame_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`.
49lookup: ?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`.
62cie_list: std.MultiArrayList(struct {
63 offset: u64,
64 cie: CommonInformationEntry,
65}),
66
67const 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
74pub 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.
81pub 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.
100pub 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
112pub 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.
126pub 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.
244const 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
310pub 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
421pub 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.
472pub 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
547fn 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`.
574pub 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`.
605pub 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
626const 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`.
636fn 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`.
661fn 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.
684fn maxSlice(ptr: [*]const u8) []const u8 {
685 const len = std.math.maxInt(usize) - @intFromPtr(ptr);
686 return ptr[0..len];
687}
688
689const Allocator = std.mem.Allocator;
690const assert = std.debug.assert;
691const bad = Dwarf.bad;
692const cast = std.math.cast;
693const DW = std.dwarf;
694const Dwarf = std.debug.Dwarf;
695const EH = DW.EH;
696const Endian = std.builtin.Endian;
697const Format = DW.Format;
698const maxInt = std.math.maxInt;
699const missing = Dwarf.missing;
700const Reader = std.Io.Reader;
701const std = @import("std");
702const 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
4pub 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
23pub 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.
33pub 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
41pub const Column = struct {
42 register: u8,
43 rule: RegisterRule,
44};
45
46const ColumnRange = struct {
47 start: usize,
48 len: u8,
49};
50
51columns: std.ArrayList(Column) = .empty,
52stack: std.ArrayList(struct {
53 cfa: CfaRule,
54 columns: ColumnRange,
55}) = .empty,
56current_row: Row = .{},
57
58/// The result of executing the CIE's initial_instructions
59cie_row: ?Row = null,
60
61pub fn deinit(self: *VirtualMachine, gpa: Allocator) void {
62 self.stack.deinit(gpa);
63 self.columns.deinit(gpa);
64 self.* = undefined;
65}
66
67pub 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
75pub 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.
81fn 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
102pub 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.
131pub 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.
177fn 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
277fn 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
284const 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
322pub 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
454const std = @import("../../../std.zig");
455const assert = std.debug.assert;
456const Allocator = std.mem.Allocator;
457const Unwind = std.debug.Dwarf.Unwind;
458
459const VirtualMachine = @This();
lib/std/debug/Dwarf/abi.zig deleted-351
...@@ -1,351 +0,0 @@
1const builtin = @import("builtin");
2
3const std = @import("../../std.zig");
4const mem = std.mem;
5const posix = std.posix;
6const 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.
12pub 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.
32pub 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
42pub 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
54pub 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
64pub const RegisterContext = struct {
65 eh_frame: bool,
66 is_macho: bool,
67};
68
69pub 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.
86pub 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.
343pub 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 @@
1const builtin = @import("builtin");
2const std = @import("../../std.zig");
3const mem = std.mem;
4const debug = std.debug;
5const leb = std.leb;
6const DW = std.dwarf;
7const abi = std.debug.Dwarf.abi;
8const assert = std.debug.assert;
9const native_endian = builtin.cpu.arch.endian();
10
11/// TODO merge with std.dwarf.CFA
12const 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
54fn readBlock(reader: *std.Io.Reader) ![]const u8 {
55 const block_len = try reader.takeLeb128(usize);
56 return reader.take(block_len);
57}
58
59pub 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,12 +5,17 @@ const native_endian = native_arch.endian();
5const std = @import("std");5const std = @import("std");
6const leb = std.leb;6const leb = std.leb;
7const OP = std.dwarf.OP;7const OP = std.dwarf.OP;
8const abi = std.debug.Dwarf.abi;
9const mem = std.mem;8const mem = std.mem;
10const assert = std.debug.assert;9const assert = std.debug.assert;
11const testing = std.testing;10const testing = std.testing;
12const Writer = std.Io.Writer;11const Writer = std.Io.Writer;
1312
13const regNative = std.debug.Dwarf.SelfUnwinder.regNative;
14
15const ip_reg_num = std.debug.Dwarf.ipRegNum(native_arch).?;
16const fp_reg_num = std.debug.Dwarf.fpRegNum(native_arch);
17const sp_reg_num = std.debug.Dwarf.spRegNum(native_arch);
18
14/// Expressions can be evaluated in different contexts, each requiring its own set of inputs.19/// Expressions can be evaluated in different contexts, each requiring its own set of inputs.
15/// Callers should specify all the fields relevant to their context. If a field is required20/// Callers should specify all the fields relevant to their context. If a field is required
16/// by the expression and it isn't in the context, error.IncompleteExpressionContext is returned.21/// by the expression and it isn't in the context, error.IncompleteExpressionContext is returned.
...@@ -23,9 +28,7 @@ pub const Context = struct {...@@ -23,9 +28,7 @@ pub const Context = struct {
23 object_address: ?*const anyopaque = null,28 object_address: ?*const anyopaque = null,
24 /// .debug_addr section29 /// .debug_addr section
25 debug_addr: ?[]const u8 = null,30 debug_addr: ?[]const u8 = null,
26 /// Thread context31 cpu_context: ?*std.debug.cpu_context.Native = null,
27 thread_context: ?*std.debug.ThreadContext = null,
28 reg_context: ?abi.RegisterContext = null,
29 /// Call frame address, if in a CFI context32 /// Call frame address, if in a CFI context
30 cfa: ?usize = null,33 cfa: ?usize = null,
31 /// This expression is a sub-expression from an OP.entry_value instruction34 /// This expression is a sub-expression from an OP.entry_value instruction
...@@ -62,7 +65,9 @@ pub const Error = error{...@@ -62,7 +65,9 @@ pub const Error = error{
62 InvalidTypeLength,65 InvalidTypeLength,
6366
64 TruncatedIntegralType,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 };
6671
67/// A stack machine that can decode and run DWARF expressions.72/// A stack machine that can decode and run DWARF expressions.
68/// Expressions can be decoded for non-native address size and endianness,73/// Expressions can be decoded for non-native address size and endianness,
...@@ -369,29 +374,20 @@ pub fn StackMachine(comptime options: Options) type {...@@ -369,29 +374,20 @@ pub fn StackMachine(comptime options: Options) type {
369 OP.breg0...OP.breg31,374 OP.breg0...OP.breg31,
370 OP.bregx,375 OP.bregx,
371 => {376 => {
372 if (context.thread_context == null) return error.IncompleteExpressionContext;377 const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext;
373378
374 const base_register = operand.?.base_register;379 const br = operand.?.base_register;
375 var value: i64 = @intCast(mem.readInt(usize, (try abi.regBytes(380 const value: i64 = @intCast((try regNative(cpu_context, br.base_register)).*);
376 context.thread_context.?,381 try self.stack.append(allocator, .{ .generic = @intCast(value + br.offset) });
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) });
382 },382 },
383 OP.regval_type => {383 OP.regval_type => {
384 const register_type = operand.?.register_type;384 const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext;
385 const value = mem.readInt(usize, (try abi.regBytes(385 const rt = operand.?.register_type;
386 context.thread_context.?,
387 register_type.register,
388 context.reg_context,
389 ))[0..@sizeOf(usize)], native_endian);
390 try self.stack.append(allocator, .{386 try self.stack.append(allocator, .{
391 .regval_type = .{387 .regval_type = .{
392 .type_offset = register_type.type_offset,388 .type_offset = rt.type_offset,
393 .type_size = @sizeOf(addr_type),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,14 +730,14 @@ pub fn StackMachine(comptime options: Options) type {
734730
735 // TODO: The spec states that this sub-expression needs to observe the state (ie. registers)731 // TODO: The spec states that this sub-expression needs to observe the state (ie. registers)
736 // as it was upon entering the current subprogram. If this isn't being called at the732 // 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.
738734
739 if (isOpcodeRegisterLocation(block[0])) {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;
741737
742 var block_stream: std.Io.Reader = .fixed(block);738 var block_stream: std.Io.Reader = .fixed(block);
743 const register = (try readOperand(&block_stream, block[0], context)).?.register;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 try self.stack.append(allocator, .{ .generic = value });741 try self.stack.append(allocator, .{ .generic = value });
746 } else {742 } else {
747 var stack_machine: Self = .{};743 var stack_machine: Self = .{};
...@@ -1149,55 +1145,39 @@ test "basics" {...@@ -1149,55 +1145,39 @@ test "basics" {
1149 }1145 }
11501146
1151 // Register values1147 // Register values
1152 if (@sizeOf(std.debug.ThreadContext) != 0) {1148 if (std.debug.cpu_context.Native != noreturn) {
1153 stack_machine.reset();1149 stack_machine.reset();
1154 program.clearRetainingCapacity();1150 program.clearRetainingCapacity();
11551151
1156 const reg_context = abi.RegisterContext{1152 var cpu_context: std.debug.cpu_context.Native = undefined;
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);
1162 const context = Context{1153 const context = Context{
1163 .thread_context = &thread_context,1154 .cpu_context = &cpu_context,
1164 .reg_context = reg_context,
1165 };1155 };
11661156
1167 // Only test register operations on arch / os that have them implemented1157 const reg_bytes = try cpu_context.dwarfRegisterBytes(0);
1168 if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| {1158
11691159 // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it
1170 // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it1160
11711161 mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
1172 mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);1162 (try regNative(&cpu_context, fp_reg_num)).* = 1;
1173 (try abi.regValueNative(&thread_context, abi.fpRegNum(native_arch, reg_context), reg_context)).* = 1;1163 (try regNative(&cpu_context, sp_reg_num)).* = 2;
1174 (try abi.regValueNative(&thread_context, abi.spRegNum(native_arch, reg_context), reg_context)).* = 2;1164 (try regNative(&cpu_context, ip_reg_num)).* = 3;
1175 (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch).?, reg_context)).* = 3;1165
11761166 try b.writeBreg(writer, fp_reg_num, @as(usize, 100));
1177 try b.writeBreg(writer, abi.fpRegNum(native_arch, reg_context), @as(usize, 100));1167 try b.writeBreg(writer, sp_reg_num, @as(usize, 200));
1178 try b.writeBreg(writer, abi.spRegNum(native_arch, reg_context), @as(usize, 200));1168 try b.writeBregx(writer, ip_reg_num, @as(usize, 300));
1179 try b.writeBregx(writer, abi.ipRegNum(native_arch).?, @as(usize, 300));1169 try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400));
1180 try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400));1170
11811171 _ = try stack_machine.run(program.written(), allocator, context, 0);
1182 _ = try stack_machine.run(program.written(), allocator, context, 0);1172
11831173 const regval_type = stack_machine.stack.pop().?.regval_type;
1184 const regval_type = stack_machine.stack.pop().?.regval_type;1174 try testing.expectEqual(@as(usize, 400), regval_type.type_offset);
1185 try testing.expectEqual(@as(usize, 400), regval_type.type_offset);1175 try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size);
1186 try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size);1176 try testing.expectEqual(@as(usize, 0xee), regval_type.value);
1187 try testing.expectEqual(@as(usize, 0xee), regval_type.value);1177
11881178 try testing.expectEqual(@as(usize, 303), stack_machine.stack.pop().?.generic);
1189 try testing.expectEqual(@as(usize, 303), stack_machine.stack.pop().?.generic);1179 try testing.expectEqual(@as(usize, 202), stack_machine.stack.pop().?.generic);
1190 try testing.expectEqual(@as(usize, 202), stack_machine.stack.pop().?.generic);1180 try testing.expectEqual(@as(usize, 101), 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 }
1201 }1181 }
12021182
1203 // Stack operations1183 // Stack operations
...@@ -1585,38 +1565,21 @@ test "basics" {...@@ -1585,38 +1565,21 @@ test "basics" {
1585 }1565 }
15861566
1587 // Register location description1567 // Register location description
1588 const reg_context = abi.RegisterContext{1568 var cpu_context: std.debug.cpu_context.Native = undefined;
1589 .eh_frame = true,1569 context = .{ .cpu_context = &cpu_context };
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 };
15981570
1599 if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| {1571 const reg_bytes = try cpu_context.dwarfRegisterBytes(0);
1600 mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);1572 mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
16011573
1602 var sub_program: std.Io.Writer.Allocating = .init(allocator);1574 var sub_program: std.Io.Writer.Allocating = .init(allocator);
1603 defer sub_program.deinit();1575 defer sub_program.deinit();
1604 const sub_writer = &sub_program.writer;1576 const sub_writer = &sub_program.writer;
1605 try b.writeReg(sub_writer, 0);1577 try b.writeReg(sub_writer, 0);
16061578
1607 stack_machine.reset();1579 stack_machine.reset();
1608 program.clearRetainingCapacity();1580 program.clearRetainingCapacity();
1609 try b.writeEntryValue(writer, sub_program.written());1581 try b.writeEntryValue(writer, sub_program.written());
1610 _ = try stack_machine.run(program.written(), allocator, context, null);1582 _ = try stack_machine.run(program.written(), allocator, context, null);
1611 try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.pop().?.generic);1583 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 }
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
4is_64: bool,
5endian: 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.
9dwarf: ?Dwarf,
10
11/// If non-`null`, describes the `.eh_frame` section, which can be used with `Dwarf.Unwind`.
12eh_frame: ?UnwindSection,
13/// If non-`null`, describes the `.debug_frame` section, which can be used with `Dwarf.Unwind`.
14debug_frame: ?UnwindSection,
15
16/// If non-`null`, this is the contents of the `.strtab` section.
17strtab: ?[]const u8,
18/// If non-`null`, describes the `.symtab` section.
19symtab: ?SymtabSection,
20
21/// Binary search table lazily populated by `searchSymtab`.
22symbol_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`.
26mapped_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`.
30mapped_debug_file: ?[]align(std.heap.page_size_min) const u8,
31
32arena: std.heap.ArenaAllocator.State,
33
34pub const UnwindSection = struct {
35 vaddr: u64,
36 bytes: []const u8,
37};
38pub const SymtabSection = struct {
39 entry_size: u64,
40 bytes: []const u8,
41};
42
43pub 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
83pub 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
95pub 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
114pub 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
236pub 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
291fn 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`.
333const 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
359fn 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
401const 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};
407fn 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
531const std = @import("std");
532const Endian = std.builtin.Endian;
533const Dwarf = std.debug.Dwarf;
534const ElfFile = @This();
535const Allocator = std.mem.Allocator;
536const elf = std.elf;
lib/std/debug/Info.zig+19-11
...@@ -9,7 +9,7 @@...@@ -9,7 +9,7 @@
9const std = @import("../std.zig");9const std = @import("../std.zig");
10const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
11const Path = std.Build.Cache.Path;11const Path = std.Build.Cache.Path;
12const Dwarf = std.debug.Dwarf;12const ElfFile = std.debug.ElfFile;
13const assert = std.debug.assert;13const assert = std.debug.assert;
14const Coverage = std.debug.Coverage;14const Coverage = std.debug.Coverage;
15const SourceLocation = std.debug.Coverage.SourceLocation;15const SourceLocation = std.debug.Coverage.SourceLocation;
...@@ -17,27 +17,35 @@ const SourceLocation = std.debug.Coverage.SourceLocation;...@@ -17,27 +17,35 @@ const SourceLocation = std.debug.Coverage.SourceLocation;
17const Info = @This();17const Info = @This();
1818
19/// Sorted by key, ascending.19/// Sorted by key, ascending.
20address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule),20address_map: std.AutoArrayHashMapUnmanaged(u64, ElfFile),
21/// Externally managed, outlives this `Info` instance.21/// Externally managed, outlives this `Info` instance.
22coverage: *Coverage,22coverage: *Coverage,
2323
24pub const LoadError = Dwarf.ElfModule.LoadError;24pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || std.debug.Dwarf.ScanError || error{MissingDebugInfo};
2525
26pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info {26pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info {
27 var sections: Dwarf.SectionArray = Dwarf.null_section_array;27 var file = try path.root_dir.handle.openFile(path.sub_path, .{});
28 var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, &sections, null);28 defer file.close();
29 try elf_module.dwarf.populateRanges(gpa);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 var info: Info = .{37 var info: Info = .{
31 .address_map = .{},38 .address_map = .{},
32 .coverage = coverage,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 return info;43 return info;
36}44}
3745
38pub fn deinit(info: *Info, gpa: Allocator) void {46pub fn deinit(info: *Info, gpa: Allocator) void {
39 for (info.address_map.values()) |*elf_module| {47 for (info.address_map.values()) |*elf_file| {
40 elf_module.dwarf.deinit(gpa);48 elf_file.dwarf.?.deinit(gpa);
41 }49 }
42 info.address_map.deinit(gpa);50 info.address_map.deinit(gpa);
43 info.* = undefined;51 info.* = undefined;
...@@ -57,6 +65,6 @@ pub fn resolveAddresses(...@@ -57,6 +65,6 @@ pub fn resolveAddresses(
57) ResolveAddressesError!void {65) ResolveAddressesError!void {
58 assert(sorted_pc_addrs.len == output.len);66 assert(sorted_pc_addrs.len == output.len);
59 if (info.address_map.entries.len != 1) @panic("TODO");67 if (info.address_map.entries.len != 1) @panic("TODO");
60 const elf_module = &info.address_map.values()[0];68 const elf_file = &info.address_map.values()[0];
61 return info.coverage.resolveAddressesDwarf(gpa, sorted_pc_addrs, output, &elf_module.dwarf);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,6 +171,7 @@ pub fn parseInfoStream(self: *Pdb) !void {
171 const string_table_index = str_tab_index: {171 const string_table_index = str_tab_index: {
172 const name_bytes_len = try reader.takeInt(u32, .little);172 const name_bytes_len = try reader.takeInt(u32, .little);
173 const name_bytes = try reader.readAlloc(gpa, name_bytes_len);173 const name_bytes = try reader.readAlloc(gpa, name_bytes_len);
174 defer gpa.free(name_bytes);
174175
175 const HashTableHeader = extern struct {176 const HashTableHeader = extern struct {
176 size: u32,177 size: u32,
...@@ -412,8 +413,7 @@ const Msf = struct {...@@ -412,8 +413,7 @@ const Msf = struct {
412 return error.InvalidDebugInfo;413 return error.InvalidDebugInfo;
413 if (superblock.free_block_map_block != 1 and superblock.free_block_map_block != 2)414 if (superblock.free_block_map_block != 1 and superblock.free_block_map_block != 2)
414 return error.InvalidDebugInfo;415 return error.InvalidDebugInfo;
415 const file_len = try file_reader.getSize();416 if (superblock.num_blocks * superblock.block_size != try file_reader.getSize())
416 if (superblock.num_blocks * superblock.block_size != file_len)
417 return error.InvalidDebugInfo;417 return error.InvalidDebugInfo;
418 switch (superblock.block_size) {418 switch (superblock.block_size) {
419 // llvm only supports 4096 but we can handle any of these values419 // llvm only supports 4096 but we can handle any of these values
...@@ -427,6 +427,7 @@ const Msf = struct {...@@ -427,6 +427,7 @@ const Msf = struct {
427427
428 try file_reader.seekTo(superblock.block_size * superblock.block_map_addr);428 try file_reader.seekTo(superblock.block_size * superblock.block_map_addr);
429 const dir_blocks = try gpa.alloc(u32, dir_block_count);429 const dir_blocks = try gpa.alloc(u32, dir_block_count);
430 errdefer gpa.free(dir_blocks);
430 for (dir_blocks) |*b| {431 for (dir_blocks) |*b| {
431 b.* = try file_reader.interface.takeInt(u32, .little);432 b.* = try file_reader.interface.takeInt(u32, .little);
432 }433 }
...@@ -450,25 +451,25 @@ const Msf = struct {...@@ -450,25 +451,25 @@ const Msf = struct {
450 const streams = try gpa.alloc(MsfStream, stream_count);451 const streams = try gpa.alloc(MsfStream, stream_count);
451 errdefer gpa.free(streams);452 errdefer gpa.free(streams);
452453
453 for (streams, 0..) |*stream, i| {454 for (streams, stream_sizes) |*stream, size| {
454 const size = stream_sizes[i];
455 if (size == 0) {455 if (size == 0) {
456 stream.* = .empty;456 stream.* = .empty;
457 } else {457 continue;
458 const blocks = try gpa.alloc(u32, size);458 }
459 errdefer gpa.free(blocks);459 const blocks = try gpa.alloc(u32, size);
460 for (blocks) |*block| {460 errdefer gpa.free(blocks);
461 const block_id = try directory.interface.takeInt(u32, .little);461 for (blocks) |*block| {
462 const n = (block_id % superblock.block_size);462 const block_id = try directory.interface.takeInt(u32, .little);
463 // 0 is for pdb.SuperBlock, 1 and 2 for FPMs.463 // Index 0 is reserved for the superblock.
464 if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.block_size > file_len)464 // In theory, every page which is `n * block_size + 1` or `n * block_size + 2`
465 return error.InvalidBlockIndex;465 // is also reserved, for one of the FPMs. However, LLVM has been observed to map
466 block.* = block_id;466 // these into actual streams, so allow it for compatibility.
467 }467 if (block_id == 0 or block_id >= superblock.num_blocks) return error.InvalidBlockIndex;
468 const buffer = try gpa.alloc(u8, 64);468 block.* = block_id;
469 errdefer gpa.free(buffer);
470 stream.* = .init(superblock.block_size, file_reader, blocks, buffer);
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 }
473474
474 const end = directory.logicalPos();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
4const builtin = @import("builtin");
5const native_os = builtin.os.tag;
6const native_endian = native_arch.endian();
7const native_arch = builtin.cpu.arch;
8
9const std = @import("../std.zig");
10const mem = std.mem;
11const Allocator = std.mem.Allocator;
12const windows = std.os.windows;
13const macho = std.macho;
14const fs = std.fs;
15const coff = std.coff;
16const pdb = std.pdb;
17const assert = std.debug.assert;
18const posix = std.posix;
19const elf = std.elf;
20const Dwarf = std.debug.Dwarf;
21const Pdb = std.debug.Pdb;
22const File = std.fs.File;
23const math = std.math;
24const testing = std.testing;
25const StackIterator = std.debug.StackIterator;
26const regBytes = Dwarf.abi.regBytes;
27const regValueNative = Dwarf.abi.regValueNative;
28
29const SelfInfo = @This();
30
31const root = @import("root");
32
33allocator: Allocator,
34address_map: std.AutoHashMap(usize, *Module),
35modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void,
36
37pub const OpenError = error{
38 MissingDebugInfo,
39 UnsupportedOperatingSystem,
40} || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).@"fn".return_type.?).error_union.error_set;
41
42pub 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
62pub 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
106pub 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
123pub 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.
140pub 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
154fn 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
222fn 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
260fn 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
350fn 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
359fn 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
397fn 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
491fn lookupModuleHaiku(self: *SelfInfo, address: usize) !*Module {
492 _ = self;
493 _ = address;
494 @panic("TODO implement lookup module for Haiku");
495}
496
497fn lookupModuleWasm(self: *SelfInfo, address: usize) !*Module {
498 _ = self;
499 _ = address;
500 @panic("TODO implement lookup module for Wasm");
501}
502
503pub 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.
829pub 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.
853fn 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
977fn 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.
1060pub 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
1089const 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.
1108fn 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
1127fn 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
1150test 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.
1175pub 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
1492pub 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.
1539pub 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.
1565pub 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
1781fn fpRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
1782 return Dwarf.abi.fpRegNum(native_arch, reg_context);
1783}
1784
1785fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
1786 return Dwarf.abi.spRegNum(native_arch, reg_context);
1787}
1788
1789const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
1790
1791/// Tells whether unwinding for the host is implemented.
1792pub const supports_unwinding = supportsUnwinding(&builtin.target);
1793
1794comptime {
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*.
1803pub 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
1827fn 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.
1849pub 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.
2213fn 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.
2233fn 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 @@
1mutex: std.Thread.Mutex,
2/// Accessed through `Module.Adapter`.
3modules: std.ArrayHashMapUnmanaged(Module, void, Module.Context, false),
4ofiles: std.StringArrayHashMapUnmanaged(?OFile),
5
6pub const init: SelfInfo = .{
7 .mutex = .{},
8 .modules = .empty,
9 .ofiles = .empty,
10};
11pub 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
33pub 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}
101pub 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
107pub const can_unwind: bool = true;
108pub 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.
112pub 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}
136fn 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.
442fn 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
462const 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
783const 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
808const 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};
858test {
859 _ = MachoSymbol;
860}
861
862/// Uses `mmap` to map the file at `path` into memory.
863fn 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
889fn 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
979const std = @import("std");
980const Allocator = std.mem.Allocator;
981const Dwarf = std.debug.Dwarf;
982const Error = std.debug.SelfInfoError;
983const assert = std.debug.assert;
984const posix = std.posix;
985const macho = std.macho;
986const mem = std.mem;
987const testing = std.testing;
988const dwarfRegNative = std.debug.Dwarf.SelfUnwinder.regNative;
989
990const builtin = @import("builtin");
991const native_endian = builtin.target.cpu.arch.endian();
992
993const SelfInfo = @This();
lib/std/debug/SelfInfo/Elf.zig created+427
...@@ -0,0 +1,427 @@
1rwlock: std.Thread.RwLock,
2
3modules: std.ArrayList(Module),
4ranges: std.ArrayList(Module.Range),
5
6unwind_cache: if (can_unwind) ?[]Dwarf.SelfUnwinder.CacheEntry else ?noreturn,
7
8pub const init: SelfInfo = .{
9 .rwlock = .{},
10 .modules = .empty,
11 .ranges = .empty,
12 .unwind_cache = null,
13};
14pub 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
31pub 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}
76pub 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
83pub 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};
100comptime {
101 if (can_unwind) {
102 std.debug.assert(Dwarf.supportsUnwinding(&builtin.target));
103 }
104}
105pub const UnwindContext = Dwarf.SelfUnwinder;
106pub 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
157const 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
299fn 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}
358const 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
418const std = @import("std");
419const Allocator = std.mem.Allocator;
420const Dwarf = std.debug.Dwarf;
421const Error = std.debug.SelfInfoError;
422const assert = std.debug.assert;
423
424const builtin = @import("builtin");
425const native_endian = builtin.target.cpu.arch.endian();
426
427const SelfInfo = @This();
lib/std/debug/SelfInfo/Windows.zig created+559
...@@ -0,0 +1,559 @@
1mutex: std.Thread.Mutex,
2modules: std.ArrayListUnmanaged(Module),
3module_name_arena: std.heap.ArenaAllocator.State,
4
5pub const init: SelfInfo = .{
6 .mutex = .{},
7 .modules = .empty,
8 .module_name_arena = .{},
9};
10pub 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
23pub 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}
30pub 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
37pub 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};
64pub 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};
146pub 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
183const 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`.
496fn 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
546const std = @import("std");
547const Allocator = std.mem.Allocator;
548const Dwarf = std.debug.Dwarf;
549const Pdb = std.debug.Pdb;
550const Error = std.debug.SelfInfoError;
551const assert = std.debug.assert;
552const coff = std.coff;
553const fs = std.fs;
554const windows = std.os.windows;
555
556const builtin = @import("builtin");
557const native_endian = builtin.target.cpu.arch.endian();
558
559const 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`.
4pub const Native = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "CpuContext"))
5 root.debug.CpuContext
6else 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
14pub const DwarfRegisterError = error{
15 InvalidRegister,
16 UnsupportedRegister,
17};
18
19pub 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
180pub 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
227pub 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
286pub 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
353pub 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.
408pub 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
470const 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
1024const std = @import("../std.zig");
1025const root = @import("root");
1026const builtin = @import("builtin");
1027const native_arch = @import("builtin").target.cpu.arch;
1028const native_os = @import("builtin").target.os.tag;
lib/std/dwarf/EH.zig+30-23
...@@ -1,27 +1,34 @@...@@ -1,27 +1,34 @@
1pub const PE = struct {1pub const PE = packed struct(u8) {
2 pub const absptr = 0x00;2 type: Type,
3 rel: Rel,
4 /// Undocumented GCC extension
5 indirect: bool = false,
36
4 pub const size_mask = 0x7;7 /// This is a special encoding which does not correspond to named `type`/`rel` values.
5 pub const sign_mask = 0x8;8 pub const omit: PE = @bitCast(@as(u8, 0xFF));
6 pub const type_mask = size_mask | sign_mask;
79
8 pub const uleb128 = 0x01;10 pub const Type = enum(u4) {
9 pub const udata2 = 0x02;11 absptr = 0x0,
10 pub const udata4 = 0x03;12 uleb128 = 0x1,
11 pub const udata8 = 0x04;13 udata2 = 0x2,
12 pub const sleb128 = 0x09;14 udata4 = 0x3,
13 pub const sdata2 = 0x0A;15 udata8 = 0x4,
14 pub const sdata4 = 0x0B;16 sleb128 = 0x9,
15 pub const sdata8 = 0x0C;17 sdata2 = 0xA,
18 sdata4 = 0xB,
19 sdata8 = 0xC,
20 _,
21 };
1622
17 pub const rel_mask = 0x70;23 /// The specification considers this a `u4`, but the GCC `indirect` field extension conflicts
18 pub const pcrel = 0x10;24 /// with that, so we consider it a `u3` instead.
19 pub const textrel = 0x20;25 pub const Rel = enum(u3) {
20 pub const datarel = 0x30;26 abs = 0x0,
21 pub const funcrel = 0x40;27 pcrel = 0x1,
22 pub const aligned = 0x50;28 textrel = 0x2,
2329 datarel = 0x3,
24 pub const indirect = 0x80;30 funcrel = 0x4,
2531 aligned = 0x5,
26 pub const omit = 0xff;32 _,
33 };
27};34};
lib/std/dynamic_library.zig+1-2
...@@ -95,8 +95,7 @@ pub fn get_DYNAMIC() ?[*]const elf.Dyn {...@@ -95,8 +95,7 @@ pub fn get_DYNAMIC() ?[*]const elf.Dyn {
95pub fn linkmap_iterator(phdrs: []const elf.Phdr) error{InvalidExe}!LinkMap.Iterator {95pub fn linkmap_iterator(phdrs: []const elf.Phdr) error{InvalidExe}!LinkMap.Iterator {
96 _ = phdrs;96 _ = phdrs;
97 const _DYNAMIC = get_DYNAMIC() orelse {97 const _DYNAMIC = get_DYNAMIC() orelse {
98 // No PT_DYNAMIC means this is either a statically-linked program or a98 // No PT_DYNAMIC means this is a statically-linked non-PIE program.
99 // badly corrupted dynamically-linked one.
100 return .{ .current = null };99 return .{ .current = null };
101 };100 };
102101
lib/std/elf.zig+2-1
...@@ -744,7 +744,8 @@ pub const SectionHeaderBufferIterator = struct {...@@ -744,7 +744,8 @@ pub const SectionHeaderBufferIterator = struct {
744744
745 const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);745 const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);
746 const offset = it.elf_header.shoff + size * it.index;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)..]);
748749
749 return takeShdr(&reader, it.elf_header);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,7 +183,7 @@ pub fn realloc(uncasted_memory: []u8, new_len: usize, may_move: bool) ?[*]u8 {
183183
184 if (posix.MREMAP != void) {184 if (posix.MREMAP != void) {
185 // TODO: if the next_mmap_addr_hint is within the remapped range, update it185 // 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 return new_memory.ptr;187 return new_memory.ptr;
188 }188 }
189189
lib/std/heap/debug_allocator.zig+13-38
...@@ -505,23 +505,14 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -505,23 +505,14 @@ pub fn DebugAllocator(comptime config: Config) type {
505 return if (leaks) .leak else .ok;505 return if (leaks) .leak else .ok;
506 }506 }
507507
508 fn collectStackTrace(first_trace_addr: usize, addresses: *[stack_n]usize) void {508 fn collectStackTrace(first_trace_addr: usize, addr_buf: *[stack_n]usize) void {
509 if (stack_n == 0) return;509 const st = std.debug.captureCurrentStackTrace(.{ .first_address = first_trace_addr }, addr_buf);
510 @memset(addresses, 0);510 @memset(addr_buf[@min(st.index, addr_buf.len)..], 0);
511 var stack_trace: StackTrace = .{
512 .instruction_addresses = addresses,
513 .index = 0,
514 };
515 std.debug.captureStackTrace(first_trace_addr, &stack_trace);
516 }511 }
517512
518 fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {513 fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {
519 var addresses: [stack_n]usize = @splat(0);514 var addr_buf: [stack_n]usize = undefined;
520 var second_free_stack_trace: StackTrace = .{515 const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
521 .instruction_addresses = &addresses,
522 .index = 0,
523 };
524 std.debug.captureStackTrace(ret_addr, &second_free_stack_trace);
525 log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{516 log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{
526 alloc_stack_trace, free_stack_trace, second_free_stack_trace,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,12 +553,8 @@ pub fn DebugAllocator(comptime config: Config) type {
562 }553 }
563554
564 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {555 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
565 var addresses: [stack_n]usize = [1]usize{0} ** stack_n;556 var addr_buf: [stack_n]usize = undefined;
566 var free_stack_trace: StackTrace = .{557 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
567 .instruction_addresses = &addresses,
568 .index = 0,
569 };
570 std.debug.captureStackTrace(ret_addr, &free_stack_trace);
571 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{558 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
572 entry.value_ptr.bytes.len,559 entry.value_ptr.bytes.len,
573 old_mem.len,560 old_mem.len,
...@@ -672,12 +659,8 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -672,12 +659,8 @@ pub fn DebugAllocator(comptime config: Config) type {
672 }659 }
673660
674 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {661 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
675 var addresses: [stack_n]usize = [1]usize{0} ** stack_n;662 var addr_buf: [stack_n]usize = undefined;
676 var free_stack_trace = StackTrace{663 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
677 .instruction_addresses = &addresses,
678 .index = 0,
679 };
680 std.debug.captureStackTrace(ret_addr, &free_stack_trace);
681 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{664 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
682 entry.value_ptr.bytes.len,665 entry.value_ptr.bytes.len,
683 old_mem.len,666 old_mem.len,
...@@ -900,12 +883,8 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -900,12 +883,8 @@ pub fn DebugAllocator(comptime config: Config) type {
900 if (requested_size == 0) @panic("Invalid free");883 if (requested_size == 0) @panic("Invalid free");
901 const slot_alignment = bucket.log2PtrAligns(slot_count)[slot_index];884 const slot_alignment = bucket.log2PtrAligns(slot_count)[slot_index];
902 if (old_memory.len != requested_size or alignment != slot_alignment) {885 if (old_memory.len != requested_size or alignment != slot_alignment) {
903 var addresses: [stack_n]usize = [1]usize{0} ** stack_n;886 var addr_buf: [stack_n]usize = undefined;
904 var free_stack_trace: StackTrace = .{887 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
905 .instruction_addresses = &addresses,
906 .index = 0,
907 };
908 std.debug.captureStackTrace(return_address, &free_stack_trace);
909 if (old_memory.len != requested_size) {888 if (old_memory.len != requested_size) {
910 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{889 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
911 requested_size,890 requested_size,
...@@ -999,12 +978,8 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -999,12 +978,8 @@ pub fn DebugAllocator(comptime config: Config) type {
999 if (requested_size == 0) @panic("Invalid free");978 if (requested_size == 0) @panic("Invalid free");
1000 const slot_alignment = bucket.log2PtrAligns(slot_count)[slot_index];979 const slot_alignment = bucket.log2PtrAligns(slot_count)[slot_index];
1001 if (memory.len != requested_size or alignment != slot_alignment) {980 if (memory.len != requested_size or alignment != slot_alignment) {
1002 var addresses: [stack_n]usize = [1]usize{0} ** stack_n;981 var addr_buf: [stack_n]usize = undefined;
1003 var free_stack_trace: StackTrace = .{982 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
1004 .instruction_addresses = &addresses,
1005 .index = 0,
1006 };
1007 std.debug.captureStackTrace(return_address, &free_stack_trace);
1008 if (memory.len != requested_size) {983 if (memory.len != requested_size) {
1009 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{984 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
1010 requested_size,985 requested_size,
lib/std/macho.zig+66-53
...@@ -839,62 +839,75 @@ pub const nlist = extern struct {...@@ -839,62 +839,75 @@ pub const nlist = extern struct {
839839
840pub const nlist_64 = extern struct {840pub const nlist_64 = extern struct {
841 n_strx: u32,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 n_sect: u8,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 n_value: u64,907 n_value: u64,
846908
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 pub fn tentative(sym: nlist_64) bool {909 pub fn tentative(sym: nlist_64) bool {
896 if (!sym.undf()) return false;910 return sym.n_type.bits.type == .undf and sym.n_value != 0;
897 return sym.n_value != 0;
898 }911 }
899};912};
900913
...@@ -2046,7 +2059,7 @@ pub const unwind_info_compressed_second_level_page_header = extern struct {...@@ -2046,7 +2059,7 @@ pub const unwind_info_compressed_second_level_page_header = extern struct {
2046 // encodings array2059 // encodings array
2047};2060};
20482061
2049pub const UnwindInfoCompressedEntry = packed struct {2062pub const UnwindInfoCompressedEntry = packed struct(u32) {
2050 funcOffset: u24,2063 funcOffset: u24,
2051 encodingIndex: u8,2064 encodingIndex: u8,
2052};2065};
lib/std/os/freebsd.zig+73
...@@ -3,6 +3,7 @@ const fd_t = std.c.fd_t;...@@ -3,6 +3,7 @@ const fd_t = std.c.fd_t;
3const off_t = std.c.off_t;3const off_t = std.c.off_t;
4const unexpectedErrno = std.posix.unexpectedErrno;4const unexpectedErrno = std.posix.unexpectedErrno;
5const errno = std.posix.errno;5const errno = std.posix.errno;
6const builtin = @import("builtin");
67
7pub const CopyFileRangeError = std.posix.UnexpectedError || error{8pub const CopyFileRangeError = std.posix.UnexpectedError || error{
8 /// If infd is not open for reading or outfd is not open for writing, or9 /// 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,3 +48,75 @@ pub fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64,
47 else => |err| return unexpectedErrno(err),48 else => |err| return unexpectedErrno(err),
48 }49 }
49}50}
51
52pub 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,7 +49,6 @@ const arch_bits = switch (native_arch) {
49 .s390x => @import("linux/s390x.zig"),49 .s390x => @import("linux/s390x.zig"),
50 else => struct {50 else => struct {
51 pub const ucontext_t = void;51 pub const ucontext_t = void;
52 pub const getcontext = {};
53 },52 },
54};53};
5554
...@@ -112,7 +111,6 @@ pub const timeval = arch_bits.timeval;...@@ -112,7 +111,6 @@ pub const timeval = arch_bits.timeval;
112pub const timezone = arch_bits.timezone;111pub const timezone = arch_bits.timezone;
113pub const ucontext_t = arch_bits.ucontext_t;112pub const ucontext_t = arch_bits.ucontext_t;
114pub const user_desc = arch_bits.user_desc;113pub const user_desc = arch_bits.user_desc;
115pub const getcontext = arch_bits.getcontext;
116114
117pub const tls = @import("linux/tls.zig");115pub const tls = @import("linux/tls.zig");
118pub const BPF = @import("linux/bpf.zig");116pub const BPF = @import("linux/bpf.zig");
lib/std/os/linux/aarch64.zig-3
...@@ -260,7 +260,4 @@ pub const ucontext_t = extern struct {...@@ -260,7 +260,4 @@ pub const ucontext_t = extern struct {
260 mcontext: mcontext_t,260 mcontext: mcontext_t,
261};261};
262262
263/// TODO
264pub const getcontext = {};
265
266pub const Elf_Symndx = u32;263pub const Elf_Symndx = u32;
lib/std/os/linux/arm.zig-3
...@@ -310,7 +310,4 @@ pub const ucontext_t = extern struct {...@@ -310,7 +310,4 @@ pub const ucontext_t = extern struct {
310 regspace: [64]u64,310 regspace: [64]u64,
311};311};
312312
313/// TODO
314pub const getcontext = {};
315
316pub const Elf_Symndx = u32;313pub const Elf_Symndx = u32;
lib/std/os/linux/hexagon.zig-3
...@@ -237,6 +237,3 @@ pub const VDSO = void;...@@ -237,6 +237,3 @@ pub const VDSO = void;
237237
238/// TODO238/// TODO
239pub const ucontext_t = void;239pub const ucontext_t = void;
240
241/// TODO
242pub const getcontext = {};
lib/std/os/linux/loongarch64.zig-3
...@@ -250,6 +250,3 @@ pub const ucontext_t = extern struct {...@@ -250,6 +250,3 @@ pub const ucontext_t = extern struct {
250};250};
251251
252pub const Elf_Symndx = u32;252pub const Elf_Symndx = u32;
253
254/// TODO
255pub const getcontext = {};
lib/std/os/linux/m68k.zig-3
...@@ -258,6 +258,3 @@ pub const VDSO = void;...@@ -258,6 +258,3 @@ pub const VDSO = void;
258258
259/// TODO259/// TODO
260pub const ucontext_t = void;260pub const ucontext_t = void;
261
262/// TODO
263pub const getcontext = {};
lib/std/os/linux/mips.zig-3
...@@ -349,6 +349,3 @@ pub const Elf_Symndx = u32;...@@ -349,6 +349,3 @@ pub const Elf_Symndx = u32;
349349
350/// TODO350/// TODO
351pub const ucontext_t = void;351pub const ucontext_t = void;
352
353/// TODO
354pub const getcontext = {};
lib/std/os/linux/mips64.zig-3
...@@ -328,6 +328,3 @@ pub const Elf_Symndx = u32;...@@ -328,6 +328,3 @@ pub const Elf_Symndx = u32;
328328
329/// TODO329/// TODO
330pub const ucontext_t = void;330pub const ucontext_t = void;
331
332/// TODO
333pub const getcontext = {};
lib/std/os/linux/powerpc.zig-3
...@@ -381,6 +381,3 @@ pub const ucontext_t = extern struct {...@@ -381,6 +381,3 @@ pub const ucontext_t = extern struct {
381};381};
382382
383pub const Elf_Symndx = u32;383pub const Elf_Symndx = u32;
384
385/// TODO
386pub const getcontext = {};
lib/std/os/linux/powerpc64.zig-3
...@@ -376,6 +376,3 @@ pub const ucontext_t = extern struct {...@@ -376,6 +376,3 @@ pub const ucontext_t = extern struct {
376};376};
377377
378pub const Elf_Symndx = u32;378pub const Elf_Symndx = u32;
379
380/// TODO
381pub const getcontext = {};
lib/std/os/linux/riscv32.zig-3
...@@ -255,6 +255,3 @@ pub const ucontext_t = extern struct {...@@ -255,6 +255,3 @@ pub const ucontext_t = extern struct {
255 sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask255 sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
256 mcontext: mcontext_t,256 mcontext: mcontext_t,
257};257};
258
259/// TODO
260pub const getcontext = {};
lib/std/os/linux/riscv64.zig-3
...@@ -255,6 +255,3 @@ pub const ucontext_t = extern struct {...@@ -255,6 +255,3 @@ pub const ucontext_t = extern struct {
255 sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask255 sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
256 mcontext: mcontext_t,256 mcontext: mcontext_t,
257};257};
258
259/// TODO
260pub const getcontext = {};
lib/std/os/linux/s390x.zig-3
...@@ -273,6 +273,3 @@ pub const mcontext_t = extern struct {...@@ -273,6 +273,3 @@ pub const mcontext_t = extern struct {
273 __regs2: [18]u32,273 __regs2: [18]u32,
274 __regs3: [16]f64,274 __regs3: [16]f64,
275};275};
276
277/// TODO
278pub const getcontext = {};
lib/std/os/linux/sparc64.zig-3
...@@ -426,6 +426,3 @@ pub const ucontext_t = extern struct {...@@ -426,6 +426,3 @@ pub const ucontext_t = extern struct {
426 stack: stack_t,426 stack: stack_t,
427 sigset: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask427 sigset: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
428};428};
429
430/// TODO
431pub const getcontext = {};
lib/std/os/linux/x86.zig+15-25
...@@ -80,28 +80,32 @@ pub fn syscall6(...@@ -80,28 +80,32 @@ pub fn syscall6(
80 arg5: usize,80 arg5: usize,
81 arg6: usize,81 arg6: usize,
82) usize {82) usize {
83 // arg5/arg6 are passed via memory as we're out of registers if ebp is used as frame pointer, or83 // arg6 can't be passed to asm in a register because ebp might be reserved as the frame pointer
84 // if we're compiling with PIC. We push arg5/arg6 on the stack before changing ebp/esp as the84 // and there are no more GPRs available; so we'll need a memory operand for it. Adding that
85 // compiler may reference arg5/arg6 as an offset relative to ebp/esp.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 return asm volatile (90 return asm volatile (
87 \\ push %[arg5]91 \\ push %[args56]
88 \\ push %[arg6]
89 \\ push %%edi
90 \\ push %%ebp92 \\ push %%ebp
91 \\ mov 12(%%esp), %%edi93 \\ mov 4(%%esp), %%ebp
92 \\ mov 8(%%esp), %%ebp94 \\ 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 \\ int $0x8099 \\ int $0x80
94 \\ pop %%ebp100 \\ pop %%ebp
95 \\ pop %%edi101 \\ pop %%edi
96 \\ add $8, %%esp
97 : [ret] "={eax}" (-> usize),102 : [ret] "={eax}" (-> usize),
98 : [number] "{eax}" (@intFromEnum(number)),103 : [number] "{eax}" (@intFromEnum(number)),
99 [arg1] "{ebx}" (arg1),104 [arg1] "{ebx}" (arg1),
100 [arg2] "{ecx}" (arg2),105 [arg2] "{ecx}" (arg2),
101 [arg3] "{edx}" (arg3),106 [arg3] "{edx}" (arg3),
102 [arg4] "{esi}" (arg4),107 [arg4] "{esi}" (arg4),
103 [arg5] "rm" (arg5),108 [args56] "rm" (&args56),
104 [arg6] "rm" (arg6),
105 : .{ .memory = true });109 : .{ .memory = true });
106}110}
107111
...@@ -432,17 +436,3 @@ pub fn getContextInternal() callconv(.naked) usize {...@@ -432,17 +436,3 @@ pub fn getContextInternal() callconv(.naked) usize {
432 [sigset_size] "i" (linux.NSIG / 8),436 [sigset_size] "i" (linux.NSIG / 8),
433 : .{ .cc = true, .memory = true, .eax = true, .ecx = true, .edx = true });437 : .{ .cc = true, .memory = true, .eax = true, .ecx = true, .edx = true });
434}438}
435
436pub 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,98 +352,3 @@ pub const ucontext_t = extern struct {
352 sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a glibc-compatible (1024-bit) sigmask.352 sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a glibc-compatible (1024-bit) sigmask.
353 fpregs_mem: [64]usize, // Not part of kernel ABI, only part of glibc ucontext_t353 fpregs_mem: [64]usize, // Not part of kernel ABI, only part of glibc ucontext_t
354};354};
355
356fn gpRegisterOffset(comptime reg_index: comptime_int) usize {
357 return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index;
358}
359
360fn 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
437pub 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,7 +2849,7 @@ pub fn unexpectedError(err: Win32Error) UnexpectedError {
2849 std.debug.print("error.Unexpected: GetLastError({d}): {f}\n", .{2849 std.debug.print("error.Unexpected: GetLastError({d}): {f}\n", .{
2850 err, std.unicode.fmtUtf16Le(buf_wstr[0..len]),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 return error.Unexpected;2854 return error.Unexpected;
2855}2855}
...@@ -2863,7 +2863,7 @@ pub fn unexpectedWSAError(err: ws2_32.WinsockError) UnexpectedError {...@@ -2863,7 +2863,7 @@ pub fn unexpectedWSAError(err: ws2_32.WinsockError) UnexpectedError {
2863pub fn unexpectedStatus(status: NTSTATUS) UnexpectedError {2863pub fn unexpectedStatus(status: NTSTATUS) UnexpectedError {
2864 if (std.posix.unexpected_error_tracing) {2864 if (std.posix.unexpected_error_tracing) {
2865 std.debug.print("error.Unexpected NTSTATUS=0x{x}\n", .{@intFromEnum(status)});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 return error.Unexpected;2868 return error.Unexpected;
2869}2869}
lib/std/posix.zig+2-5
...@@ -47,7 +47,6 @@ else switch (native_os) {...@@ -47,7 +47,6 @@ else switch (native_os) {
47 .linux => linux,47 .linux => linux,
48 .plan9 => std.os.plan9,48 .plan9 => std.os.plan9,
49 else => struct {49 else => struct {
50 pub const ucontext_t = void;
51 pub const pid_t = void;50 pub const pid_t = void;
52 pub const pollfd = void;51 pub const pollfd = void;
53 pub const fd_t = void;52 pub const fd_t = void;
...@@ -141,7 +140,6 @@ pub const in_pktinfo = system.in_pktinfo;...@@ -141,7 +140,6 @@ pub const in_pktinfo = system.in_pktinfo;
141pub const in6_pktinfo = system.in6_pktinfo;140pub const in6_pktinfo = system.in6_pktinfo;
142pub const ino_t = system.ino_t;141pub const ino_t = system.ino_t;
143pub const linger = system.linger;142pub const linger = system.linger;
144pub const mcontext_t = system.mcontext_t;
145pub const mode_t = system.mode_t;143pub const mode_t = system.mode_t;
146pub const msghdr = system.msghdr;144pub const msghdr = system.msghdr;
147pub const msghdr_const = system.msghdr_const;145pub const msghdr_const = system.msghdr_const;
...@@ -170,7 +168,6 @@ pub const timespec = system.timespec;...@@ -170,7 +168,6 @@ pub const timespec = system.timespec;
170pub const timestamp_t = system.timestamp_t;168pub const timestamp_t = system.timestamp_t;
171pub const timeval = system.timeval;169pub const timeval = system.timeval;
172pub const timezone = system.timezone;170pub const timezone = system.timezone;
173pub const ucontext_t = system.ucontext_t;
174pub const uid_t = system.uid_t;171pub const uid_t = system.uid_t;
175pub const user_desc = system.user_desc;172pub const user_desc = system.user_desc;
176pub const utsname = system.utsname;173pub const utsname = system.utsname;
...@@ -692,7 +689,7 @@ pub fn abort() noreturn {...@@ -692,7 +689,7 @@ pub fn abort() noreturn {
692 // even when linking libc on Windows we use our own abort implementation.689 // even when linking libc on Windows we use our own abort implementation.
693 // See https://github.com/ziglang/zig/issues/2071 for more details.690 // See https://github.com/ziglang/zig/issues/2071 for more details.
694 if (native_os == .windows) {691 if (native_os == .windows) {
695 if (builtin.mode == .Debug) {692 if (builtin.mode == .Debug and windows.peb().BeingDebugged != 0) {
696 @breakpoint();693 @breakpoint();
697 }694 }
698 windows.kernel32.ExitProcess(3);695 windows.kernel32.ExitProcess(3);
...@@ -7590,7 +7587,7 @@ pub const UnexpectedError = error{...@@ -7590,7 +7587,7 @@ pub const UnexpectedError = error{
7590pub fn unexpectedErrno(err: E) UnexpectedError {7587pub fn unexpectedErrno(err: E) UnexpectedError {
7591 if (unexpected_error_tracing) {7588 if (unexpected_error_tracing) {
7592 std.debug.print("unexpected errno: {d}\n", .{@intFromEnum(err)});7589 std.debug.print("unexpected errno: {d}\n", .{@intFromEnum(err)});
7593 std.debug.dumpCurrentStackTrace(null);7590 std.debug.dumpCurrentStackTrace(.{});
7594 }7591 }
7595 return error.Unexpected;7592 return error.Unexpected;
7596}7593}
lib/std/start.zig+5-2
...@@ -635,8 +635,11 @@ pub inline fn callMain() u8 {...@@ -635,8 +635,11 @@ pub inline fn callMain() u8 {
635 else => {},635 else => {},
636 }636 }
637 std.log.err("{s}", .{@errorName(err)});637 std.log.err("{s}", .{@errorName(err)});
638 if (@errorReturnTrace()) |trace| {638 switch (native_os) {
639 std.debug.dumpStackTrace(trace.*);639 .freestanding, .other => {},
640 else => if (@errorReturnTrace()) |trace| {
641 std.debug.dumpStackTrace(trace);
642 },
640 }643 }
641 return 1;644 return 1;
642 };645 };
lib/std/std.zig+16
...@@ -171,6 +171,22 @@ pub const Options = struct {...@@ -171,6 +171,22 @@ pub const Options = struct {
171 http_enable_ssl_key_log_file: bool = @import("builtin").mode == .Debug,171 http_enable_ssl_key_log_file: bool = @import("builtin").mode == .Debug,
172172
173 side_channels_mitigations: crypto.SideChannelsMitigations = crypto.default_side_channels_mitigations,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};
175191
176// This forces the start.zig file to be imported, and the comptime logic inside that192// 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,12 +64,8 @@ fn alloc(
64 const self: *FailingAllocator = @ptrCast(@alignCast(ctx));64 const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
65 if (self.alloc_index == self.fail_index) {65 if (self.alloc_index == self.fail_index) {
66 if (!self.has_induced_failure) {66 if (!self.has_induced_failure) {
67 @memset(&self.stack_addresses, 0);67 const st = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &self.stack_addresses);
68 var stack_trace = std.builtin.StackTrace{68 @memset(self.stack_addresses[@min(st.index, self.stack_addresses.len)..], 0);
69 .instruction_addresses = &self.stack_addresses,
70 .index = 0,
71 };
72 std.debug.captureStackTrace(return_address, &stack_trace);
73 self.has_induced_failure = true;69 self.has_induced_failure = true;
74 }70 }
75 return null;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,8 +1510,16 @@ static inline zig_u128 zig_shl_u128(zig_u128 lhs, uint8_t rhs) {
1510}1510}
15111511
1512static inline zig_i128 zig_shr_i128(zig_i128 lhs, uint8_t rhs) {1512static 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 zig_i128 sign_mask = lhs < zig_make_i128(0, 0) ? -zig_make_i128(0, 1) : zig_make_i128(0, 0);1520 zig_i128 sign_mask = lhs < zig_make_i128(0, 0) ? -zig_make_i128(0, 1) : zig_make_i128(0, 0);
1514 return ((lhs ^ sign_mask) >> rhs) ^ sign_mask;1521 return ((lhs ^ sign_mask) >> rhs) ^ sign_mask;
1522#endif
1515}1523}
15161524
1517static inline zig_i128 zig_shl_i128(zig_i128 lhs, uint8_t rhs) {1525static inline zig_i128 zig_shl_i128(zig_i128 lhs, uint8_t rhs) {
src/Sema.zig+3-3
...@@ -1130,8 +1130,8 @@ fn analyzeBodyInner(...@@ -1130,8 +1130,8 @@ fn analyzeBodyInner(
1130 const tags = sema.code.instructions.items(.tag);1130 const tags = sema.code.instructions.items(.tag);
1131 const datas = sema.code.instructions.items(.data);1131 const datas = sema.code.instructions.items(.data);
11321132
1133 var crash_info = crash_report.prepAnalyzeBody(sema, block, body);1133 var crash_info: crash_report.AnalyzeBody = undefined;
1134 crash_info.push();1134 crash_info.push(sema, block, body);
1135 defer crash_info.pop();1135 defer crash_info.pop();
11361136
1137 // We use a while (true) loop here to avoid a redundant way of breaking out of1137 // 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,7 +2632,7 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg
2632 std.debug.print("compile error during Sema:\n", .{});2632 std.debug.print("compile error during Sema:\n", .{});
2633 var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory");2633 var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory");
2634 error_bundle.renderToStdErr(.{ .ttyconf = .no_color });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 }
26372637
2638 if (block) |start_block| {2638 if (block) |start_block| {
src/Zcu/PerThread.zig+4
...@@ -28,6 +28,7 @@ const Value = @import("../Value.zig");...@@ -28,6 +28,7 @@ const Value = @import("../Value.zig");
28const Zcu = @import("../Zcu.zig");28const Zcu = @import("../Zcu.zig");
29const Compilation = @import("../Compilation.zig");29const Compilation = @import("../Compilation.zig");
30const codegen = @import("../codegen.zig");30const codegen = @import("../codegen.zig");
31const crash_report = @import("../crash_report.zig");
31const Zir = std.zig.Zir;32const Zir = std.zig.Zir;
32const Zoir = std.zig.Zoir;33const Zoir = std.zig.Zoir;
33const ZonGen = std.zig.ZonGen;34const ZonGen = std.zig.ZonGen;
...@@ -4390,6 +4391,9 @@ pub fn addDependency(pt: Zcu.PerThread, unit: AnalUnit, dependee: InternPool.Dep...@@ -4390,6 +4391,9 @@ pub fn addDependency(pt: Zcu.PerThread, unit: AnalUnit, dependee: InternPool.Dep
4390pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air, out: *@import("../link.zig").ZcuTask.LinkFunc.SharedMir) void {4391pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air, out: *@import("../link.zig").ZcuTask.LinkFunc.SharedMir) void {
4391 const zcu = pt.zcu;4392 const zcu = pt.zcu;
43924393
4394 crash_report.CodegenFunc.start(zcu, func_index);
4395 defer crash_report.CodegenFunc.stop(func_index);
4396
4393 var timer = zcu.comp.startTimer();4397 var timer = zcu.comp.startTimer();
43944398
4395 const success: bool = if (runCodegenInner(pt, func_index, air)) |mir| success: {4399 const success: bool = if (runCodegenInner(pt, func_index, air)) |mir| success: {
src/crash_report.zig+109-461
...@@ -1,34 +1,31 @@...@@ -1,34 +1,31 @@
1const std = @import("std");1/// We override the panic implementation to our own one, so we can print our own information before
2const builtin = @import("builtin");2/// calling the default panic handler. This declaration must be re-exposed from `@import("root")`.
3const build_options = @import("build_options");3pub const panic = if (dev.env == .bootstrap)
4const debug = std.debug;
5const print_zir = @import("print_zir.zig");
6const windows = std.os.windows;
7const posix = std.posix;
8const native_os = builtin.os.tag;
9
10const Zcu = @import("Zcu.zig");
11const Sema = @import("Sema.zig");
12const InternPool = @import("InternPool.zig");
13const Zir = std.zig.Zir;
14const Decl = Zcu.Decl;
15const 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.
20pub const panic = if (build_options.enable_debug_extensions)
21 std.debug.FullPanic(compilerPanic)
22else if (dev.env == .bootstrap)
23 std.debug.simple_panic4 std.debug.simple_panic
24else5else
25 std.debug.FullPanic(std.debug.defaultPanic);6 std.debug.FullPanic(panicImpl);
267
27/// Install signal handlers to identify crashes and report diagnostics.8/// We let std install its segfault handler, but we override the target-agnostic handler it calls,
28pub fn initialize() void {9/// so we can print our own information before calling the default segfault logic. This declaration
29 if (build_options.enable_debug_extensions and debug.have_segfault_handling_support) {10/// must be re-exposed from `@import("root")`.
30 attachSegfaultHandler();11pub const debug = struct {
31 }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.
18pub var zig_argv0: []const u8 = "zig";
19
20fn 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}
25fn 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}
3330
34pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct {31pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct {
...@@ -38,63 +35,98 @@ 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 body: []const Zir.Inst.Index,35 body: []const Zir.Inst.Index,
39 body_index: usize,36 body_index: usize,
4037
41 pub fn push(self: *@This()) void {38 threadlocal var current: ?*AnalyzeBody = null;
42 const head = &zir_state;
43 debug.assert(self.parent == null);
44 self.parent = head.*;
45 head.* = self;
46 }
4739
48 pub fn pop(self: *@This()) void {40 pub fn setBodyIndex(ab: *AnalyzeBody, index: usize) void {
49 const head = &zir_state;41 ab.body_index = index;
50 const old = head.*.?;
51 debug.assert(old == self);
52 head.* = old.parent;
53 }42 }
5443
55 pub fn setBodyIndex(self: *@This(), index: usize) void {44 pub fn push(ab: *AnalyzeBody, sema: *Sema, block: *Sema.Block, body: []const Zir.Inst.Index) void {
56 self.body_index = index;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} else struct {58} else struct {
59 pub inline fn push(_: @This()) void {}59 const current: ?noreturn = null;
60 pub inline fn pop(_: @This()) void {}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 pub inline fn setBodyIndex(_: @This(), _: usize) void {}63 pub inline fn setBodyIndex(_: @This(), _: usize) void {}
62};64};
6365
64threadlocal var zir_state: ?*AnalyzeBody = if (build_options.enable_debug_extensions) null else @compileError("Cannot use zir_state without debug extensions.");66pub 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};
6584
66pub fn prepAnalyzeBody(sema: *Sema, block: *Sema.Block, body: []const Zir.Inst.Index) AnalyzeBody {85fn dumpCrashContext() Io.Writer.Error!void {
67 return if (build_options.enable_debug_extensions) .{86 const S = struct {
68 .parent = null,87 /// In the case of recursive panics or segfaults, don't print the context for a second time.
69 .sema = sema,88 threadlocal var already_dumped = false;
70 .block = block,89 /// TODO: make this unnecessary. It exists because `print_zir` currently needs an allocator,
71 .body = body,90 /// but that shouldn't be necessary---it's already only used in one place.
72 .body_index = 0,91 threadlocal var crash_heap: [64 * 1024]u8 = undefined;
73 } else .{};92 };
74}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();
75100
76fn dumpStatusReport() !void {101 try stderr.writeAll("Compiler crash context:\n");
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();
81102
82 var stderr_fw = std.fs.File.stderr().writer(&.{});103 if (CodegenFunc.current) |*cg| {
83 const stderr = &stderr_fw.interface;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}
113fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void {
84 const block: *Sema.Block = anal.block;114 const block: *Sema.Block = anal.block;
85 const zcu = anal.sema.pt.zcu;115 const zcu = anal.sema.pt.zcu;
116 const comp = zcu.comp;
117
118 var fba: std.heap.FixedBufferAllocator = .init(crash_heap);
86119
87 const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu) orelse {120 const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu) orelse {
88 const file = zcu.fileByIndex(block.src_base_inst.resolveFile(&zcu.intern_pool));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 return;123 return;
91 };124 };
92125
93 try stderr.writeAll("Analyzing ");126 try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(comp)});
94 try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(zcu.comp)});
95127
96 print_zir.renderInstructionContext(128 print_zir.renderInstructionContext(
97 allocator,129 fba.allocator(),
98 anal.body,130 anal.body,
99 anal.body_index,131 anal.body_index,
100 file,132 file,
...@@ -107,16 +139,16 @@ fn dumpStatusReport() !void {...@@ -107,16 +139,16 @@ fn dumpStatusReport() !void {
107 };139 };
108 try stderr.print(140 try stderr.print(
109 \\ For full context, use the command141 \\ 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) });
114146
115 var parent = anal.parent;147 var parent = anal.parent;
116 while (parent) |curr| {148 while (parent) |curr| {
117 fba.reset();149 fba.reset();
118 const cur_block_file = zcu.fileByIndex(curr.block.src_base_inst.resolveFile(&zcu.intern_pool));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 _, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu) orelse {152 _, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu) orelse {
121 try stderr.writeAll(" > [lost instruction; this should not happen]\n");153 try stderr.writeAll(" > [lost instruction; this should not happen]\n");
122 parent = curr.parent;154 parent = curr.parent;
...@@ -124,7 +156,7 @@ fn dumpStatusReport() !void {...@@ -124,7 +156,7 @@ fn dumpStatusReport() !void {
124 };156 };
125 try stderr.writeAll(" > ");157 try stderr.writeAll(" > ");
126 print_zir.renderSingleInstruction(158 print_zir.renderSingleInstruction(
127 allocator,159 fba.allocator(),
128 curr.body[curr.body_index],160 curr.body[curr.body_index],
129 cur_block_file,161 cur_block_file,
130 cur_block_src_base_node,162 cur_block_src_base_node,
...@@ -142,398 +174,14 @@ fn dumpStatusReport() !void {...@@ -142,398 +174,14 @@ fn dumpStatusReport() !void {
142 try stderr.writeByte('\n');174 try stderr.writeByte('\n');
143}175}
144176
145var crash_heap: [16 * 4096]u8 = undefined;177const std = @import("std");
146178const Io = std.Io;
147pub fn compilerPanic(msg: []const u8, maybe_ret_addr: ?usize) noreturn {179const Zir = std.zig.Zir;
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
156pub 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
172fn 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
205const WindowsSegfaultMessage = union(enum) {
206 literal: []const u8,
207 segfault: void,
208 illegal_instruction: void,
209};
210
211fn 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
221fn 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
258const 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
282const 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;
526180
527 var stderr_fw = std.fs.File.stderr().writer(&.{});181const Sema = @import("Sema.zig");
528 const stderr = &stderr_fw.interface;182const Zcu = @import("Zcu.zig");
529 stderr.writeAll("\nPanicked while dumping inner panic stack: ") catch {};183const InternPool = @import("InternPool.zig");
530 stderr.writeAll(msg) catch {};184const dev = @import("dev.zig");
531 stderr.writeByte('\n') catch {};185const print_zir = @import("print_zir.zig");
532186
533 // If we succeed, restore all the way to dumping the stack.187const build_options = @import("build_options");
534 state.recover_verbosity = .message_and_stack;
535 },
536 .silent => {},
537 }
538 }
539};
src/link/Dwarf.zig+2-2
...@@ -4064,6 +4064,7 @@ fn updateLazyValue(...@@ -4064,6 +4064,7 @@ fn updateLazyValue(
4064 },4064 },
4065 .error_union => |error_union| {4065 .error_union => |error_union| {
4066 try wip_nav.abbrevCode(.aggregate_comptime_value);4066 try wip_nav.abbrevCode(.aggregate_comptime_value);
4067 try wip_nav.refType(.fromInterned(error_union.ty));
4067 var err_buf: [4]u8 = undefined;4068 var err_buf: [4]u8 = undefined;
4068 const err_bytes = err_buf[0 .. std.math.divCeil(u17, zcu.errorSetBits(), 8) catch unreachable];4069 const err_bytes = err_buf[0 .. std.math.divCeil(u17, zcu.errorSetBits(), 8) catch unreachable];
4069 dwarf.writeInt(err_bytes, switch (error_union.val) {4070 dwarf.writeInt(err_bytes, switch (error_union.val) {
...@@ -4101,7 +4102,6 @@ fn updateLazyValue(...@@ -4101,7 +4102,6 @@ fn updateLazyValue(
4101 try diw.writeUleb128(err_bytes.len);4102 try diw.writeUleb128(err_bytes.len);
4102 try diw.writeAll(err_bytes);4103 try diw.writeAll(err_bytes);
4103 }4104 }
4104 try wip_nav.refType(.fromInterned(error_union.ty));
4105 try diw.writeUleb128(@intFromEnum(AbbrevCode.null));4105 try diw.writeUleb128(@intFromEnum(AbbrevCode.null));
4106 },4106 },
4107 .enum_literal => |enum_literal| {4107 .enum_literal => |enum_literal| {
...@@ -4852,7 +4852,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Erro...@@ -4852,7 +4852,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Erro
4852 hw.writeSleb128(dwarf.debug_frame.header.data_alignment_factor) catch unreachable;4852 hw.writeSleb128(dwarf.debug_frame.header.data_alignment_factor) catch unreachable;
4853 hw.writeUleb128(dwarf.debug_frame.header.return_address_register) catch unreachable;4853 hw.writeUleb128(dwarf.debug_frame.header.return_address_register) catch unreachable;
4854 hw.writeUleb128(1) catch unreachable;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 hw.writeByte(DW.CFA.def_cfa_sf) catch unreachable;4856 hw.writeByte(DW.CFA.def_cfa_sf) catch unreachable;
4857 hw.writeUleb128(Register.rsp.dwarfNum()) catch unreachable;4857 hw.writeUleb128(Register.rsp.dwarfNum()) catch unreachable;
4858 hw.writeSleb128(-1) catch unreachable;4858 hw.writeSleb128(-1) catch unreachable;
src/link/Elf.zig+22-17
...@@ -1884,6 +1884,16 @@ fn initSyntheticSections(self: *Elf) !void {...@@ -1884,6 +1884,16 @@ fn initSyntheticSections(self: *Elf) !void {
1884 const ptr_size = self.ptrWidthBytes();1884 const ptr_size = self.ptrWidthBytes();
1885 const shared_objects = self.shared_objects.values();1885 const shared_objects = self.shared_objects.values();
18861886
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 const needs_eh_frame = blk: {1897 const needs_eh_frame = blk: {
1888 if (self.zigObjectPtr()) |zo|1898 if (self.zigObjectPtr()) |zo|
1889 if (zo.eh_frame_index != null) break :blk true;1899 if (zo.eh_frame_index != null) break :blk true;
...@@ -1891,6 +1901,7 @@ fn initSyntheticSections(self: *Elf) !void {...@@ -1891,6 +1901,7 @@ fn initSyntheticSections(self: *Elf) !void {
1891 if (self.file(index).?.object.cies.items.len > 0) break true;1901 if (self.file(index).?.object.cies.items.len > 0) break true;
1892 } else false;1902 } else false;
1893 };1903 };
1904
1894 if (needs_eh_frame) {1905 if (needs_eh_frame) {
1895 if (self.section_indexes.eh_frame == null) {1906 if (self.section_indexes.eh_frame == null) {
1896 self.section_indexes.eh_frame = self.sectionByName(".eh_frame") orelse try self.addSection(.{1907 self.section_indexes.eh_frame = self.sectionByName(".eh_frame") orelse try self.addSection(.{
...@@ -1922,13 +1933,17 @@ fn initSyntheticSections(self: *Elf) !void {...@@ -1922,13 +1933,17 @@ fn initSyntheticSections(self: *Elf) !void {
1922 });1933 });
1923 }1934 }
19241935
1925 if (self.section_indexes.got_plt == null) {1936 if (have_dynamic_linker) {
1926 self.section_indexes.got_plt = try self.addSection(.{1937 if (self.section_indexes.got_plt == null) {
1927 .name = try self.insertShString(".got.plt"),1938 self.section_indexes.got_plt = try self.addSection(.{
1928 .type = elf.SHT_PROGBITS,1939 .name = try self.insertShString(".got.plt"),
1929 .flags = elf.SHF_ALLOC | elf.SHF_WRITE,1940 .type = elf.SHT_PROGBITS,
1930 .addralign = @alignOf(u64),1941 .flags = elf.SHF_ALLOC | elf.SHF_WRITE,
1931 });1942 .addralign = @alignOf(u64),
1943 });
1944 }
1945 } else {
1946 assert(self.plt.symbols.items.len == 0);
1932 }1947 }
19331948
1934 const needs_rela_dyn = blk: {1949 const needs_rela_dyn = blk: {
...@@ -1989,16 +2004,6 @@ fn initSyntheticSections(self: *Elf) !void {...@@ -1989,16 +2004,6 @@ fn initSyntheticSections(self: *Elf) !void {
1989 });2004 });
1990 }2005 }
19912006
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 if (needs_interp and self.section_indexes.interp == null) {2007 if (needs_interp and self.section_indexes.interp == null) {
2003 self.section_indexes.interp = try self.addSection(.{2008 self.section_indexes.interp = try self.addSection(.{
2004 .name = try self.insertShString(".interp"),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,7 +234,14 @@ pub fn calcEhFrameSize(elf_file: *Elf) !usize {
234 return offset;234 return offset;
235}235}
236236
237fn 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
237pub fn calcEhFrameHdrSize(elf_file: *Elf) usize {243pub fn calcEhFrameHdrSize(elf_file: *Elf) usize {
244 if (!haveEhFrameHdrSearchTable(elf_file)) return 8;
238 var count: usize = 0;245 var count: usize = 0;
239 for (elf_file.objects.items) |index| {246 for (elf_file.objects.items) |index| {
240 for (elf_file.file(index).?.object.fdes.items) |fde| {247 for (elf_file.file(index).?.object.fdes.items) |fde| {
...@@ -242,7 +249,7 @@ pub fn calcEhFrameHdrSize(elf_file: *Elf) usize {...@@ -242,7 +249,7 @@ pub fn calcEhFrameHdrSize(elf_file: *Elf) usize {
242 count += 1;249 count += 1;
243 }250 }
244 }251 }
245 return eh_frame_hdr_header_size + count * 8;252 return 12 + count * 8;
246}253}
247254
248pub fn calcEhFrameRelocs(elf_file: *Elf) usize {255pub fn calcEhFrameRelocs(elf_file: *Elf) usize {
...@@ -455,76 +462,77 @@ pub fn writeEhFrameRelocs(elf_file: *Elf, relocs: *std.array_list.Managed(elf.El...@@ -455,76 +462,77 @@ pub fn writeEhFrameRelocs(elf_file: *Elf, relocs: *std.array_list.Managed(elf.El
455}462}
456463
457pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void {464pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void {
458 const comp = elf_file.base.comp;465 const endian = elf_file.getTarget().cpu.arch.endian();
459 const gpa = comp.gpa;466 const have_table = haveEhFrameHdrSearchTable(elf_file);
460467
461 try writer.writeByte(1); // version468 try writer.writeByte(1); // version
462 try writer.writeByte(DW_EH_PE.pcrel | DW_EH_PE.sdata4);469 try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .sdata4, .rel = .pcrel }))); // eh_frame_ptr_enc
463 try writer.writeByte(DW_EH_PE.udata4);470 if (have_table) {
464 try writer.writeByte(DW_EH_PE.datarel | DW_EH_PE.sdata4);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 }
465477
466 const shdrs = elf_file.sections.items(.shdr);478 const shdrs = elf_file.sections.items(.shdr);
467 const eh_frame_shdr = shdrs[elf_file.section_indexes.eh_frame.?];479 const eh_frame_shdr = shdrs[elf_file.section_indexes.eh_frame.?];
468 const eh_frame_hdr_shdr = shdrs[elf_file.section_indexes.eh_frame_hdr.?];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)));481 // eh_frame_ptr
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 };
475 try writer.writeInt(482 try writer.writeInt(
476 u32,483 u32,
477 @as(u32, @bitCast(@as(484 @as(u32, @bitCast(@as(
478 i32,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 .little,488 .little,
482 );489 );
483 try writer.writeInt(u32, num_fdes, .little);
484490
485 const Entry = extern struct {491 if (!have_table) return;
486 init_addr: u32,
487 fde_addr: u32,
488492
489 pub fn lessThan(ctx: void, lhs: @This(), rhs: @This()) bool {493 const gpa = elf_file.base.comp.gpa;
490 _ = ctx;494
491 return lhs.init_addr < rhs.init_addr;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 };
494503 // The number of entries was already computed by `calcEhFrameHdrSize`.
495 var entries = std.array_list.Managed(Entry).init(gpa);504 const num_fdes: u32 = @intCast(@divExact(eh_frame_hdr_shdr.sh_size - 12, 8));
496 defer entries.deinit();505 try writer.writeInt(u32, num_fdes, endian);
497 try entries.ensureTotalCapacityPrecise(num_fdes);506
498507 var entries: std.ArrayList(Entry) = try .initCapacity(gpa, num_fdes);
499 for (elf_file.objects.items) |index| {508 defer entries.deinit(gpa);
500 const object = elf_file.file(index).?.object;509 for (elf_file.objects.items) |file_index| {
510 const object = elf_file.file(file_index).?.object;
501 for (object.fdes.items) |fde| {511 for (object.fdes.items) |fde| {
502 if (!fde.alive) continue;512 if (!fde.alive) continue;
503
504 const relocs = fde.relocs(object);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 const rel = relocs[0];515 const rel = relocs[0];
507 const ref = object.resolveSymbol(rel.r_sym(), elf_file);516 const ref = object.resolveSymbol(rel.r_sym(), elf_file);
508 const sym = elf_file.symbol(ref).?;517 const sym = elf_file.symbol(ref).?;
509 const P = @as(i64, @intCast(fde.address(elf_file)));518 const fde_addr_abs: i64 = @intCast(fde.address(elf_file));
510 const S = @as(i64, @intCast(sym.address(.{}, elf_file)));519 const fde_addr_rel: i64 = fde_addr_abs - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr));
511 const A = rel.r_addend;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 entries.appendAssumeCapacity(.{522 entries.appendAssumeCapacity(.{
513 .init_addr = @bitCast(@as(i32, @truncate(S + A - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))),523 .first_pc_rel = @truncate(first_pc_rel),
514 .fde_addr = @as(524 .fde_addr_rel = @truncate(fde_addr_rel),
515 u32,
516 @bitCast(@as(i32, @truncate(P - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))),
517 ),
518 });525 });
519 }526 }
520 }527 }
521528 assert(entries.items.len == num_fdes);
522 std.mem.sort(Entry, entries.items, {}, Entry.lessThan);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}
525535
526const eh_frame_hdr_header_size: usize = 12;
527
528const x86_64 = struct {536const x86_64 = struct {
529 fn resolveReloc(rec: anytype, elf_file: *Elf, rel: elf.Elf64_Rela, source: i64, target: i64, data: []u8) !void {537 fn resolveReloc(rec: anytype, elf_file: *Elf, rel: elf.Elf64_Rela, source: i64, target: i64, data: []u8) !void {
530 const r_type: elf.R_X86_64 = @enumFromInt(rel.r_type());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,3 +595,5 @@ const DW_EH_PE = std.dwarf.EH.PE;
587const Elf = @import("../Elf.zig");595const Elf = @import("../Elf.zig");
588const Object = @import("Object.zig");596const Object = @import("Object.zig");
589const Symbol = @import("Symbol.zig");597const Symbol = @import("Symbol.zig");
598
599const builtin = @import("builtin");
src/link/MachO.zig+3-3
...@@ -4149,9 +4149,9 @@ pub const SymtabCtx = struct {...@@ -4149,9 +4149,9 @@ pub const SymtabCtx = struct {
41494149
4150pub const null_sym = macho.nlist_64{4150pub const null_sym = macho.nlist_64{
4151 .n_strx = 0,4151 .n_strx = 0,
4152 .n_type = 0,4152 .n_type = @bitCast(@as(u8, 0)),
4153 .n_sect = 0,4153 .n_sect = 0,
4154 .n_desc = 0,4154 .n_desc = @bitCast(@as(u16, 0)),
4155 .n_value = 0,4155 .n_value = 0,
4156};4156};
41574157
...@@ -5070,7 +5070,7 @@ pub fn getKernError(err: std.c.kern_return_t) KernE {...@@ -5070,7 +5070,7 @@ pub fn getKernError(err: std.c.kern_return_t) KernE {
5070pub fn unexpectedKernError(err: KernE) std.posix.UnexpectedError {5070pub fn unexpectedKernError(err: KernE) std.posix.UnexpectedError {
5071 if (std.posix.unexpected_error_tracing) {5071 if (std.posix.unexpected_error_tracing) {
5072 std.debug.print("unexpected error: {d}\n", .{@intFromEnum(err)});5072 std.debug.print("unexpected error: {d}\n", .{@intFromEnum(err)});
5073 std.debug.dumpCurrentStackTrace(null);5073 std.debug.dumpCurrentStackTrace(.{});
5074 }5074 }
5075 return error.Unexpected;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,9 +69,9 @@ pub fn initSymbols(self: *InternalObject, macho_file: *MachO) !void {
69 const nlist = obj.symtab.addOneAssumeCapacity();69 const nlist = obj.symtab.addOneAssumeCapacity();
70 nlist.* = .{70 nlist.* = .{
71 .n_strx = name.pos,71 .n_strx = name.pos,
72 .n_type = args.type,72 .n_type = @bitCast(args.type),
73 .n_sect = 0,73 .n_sect = 0,
74 .n_desc = args.desc,74 .n_desc = @bitCast(args.desc),
75 .n_value = 0,75 .n_value = 0,
76 };76 };
77 symbol.nlist_idx = nlist_idx;77 symbol.nlist_idx = nlist_idx;
...@@ -143,7 +143,7 @@ pub fn resolveSymbols(self: *InternalObject, macho_file: *MachO) !void {...@@ -143,7 +143,7 @@ pub fn resolveSymbols(self: *InternalObject, macho_file: *MachO) !void {
143 }143 }
144 global.* = gop.index;144 global.* = gop.index;
145145
146 if (nlist.undf()) continue;146 if (nlist.n_type.bits.type == .undf) continue;
147 if (gop.ref.getFile(macho_file) == null) {147 if (gop.ref.getFile(macho_file) == null) {
148 gop.ref.* = .{ .index = @intCast(i), .file = self.index };148 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
149 continue;149 continue;
...@@ -171,7 +171,7 @@ pub fn resolveBoundarySymbols(self: *InternalObject, macho_file: *MachO) !void {...@@ -171,7 +171,7 @@ pub fn resolveBoundarySymbols(self: *InternalObject, macho_file: *MachO) !void {
171 const object = macho_file.getFile(index).?.object;171 const object = macho_file.getFile(index).?.object;
172 for (object.symbols.items, 0..) |sym, i| {172 for (object.symbols.items, 0..) |sym, i| {
173 const nlist = object.symtab.items(.nlist)[i];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 const ref = object.getSymbolRef(@intCast(i), macho_file);175 const ref = object.getSymbolRef(@intCast(i), macho_file);
176 if (ref.getFile(macho_file) != null) continue;176 if (ref.getFile(macho_file) != null) continue;
177 const name = sym.getName(macho_file);177 const name = sym.getName(macho_file);
...@@ -206,9 +206,9 @@ pub fn resolveBoundarySymbols(self: *InternalObject, macho_file: *MachO) !void {...@@ -206,9 +206,9 @@ pub fn resolveBoundarySymbols(self: *InternalObject, macho_file: *MachO) !void {
206 const nlist = self.symtab.addOneAssumeCapacity();206 const nlist = self.symtab.addOneAssumeCapacity();
207 nlist.* = .{207 nlist.* = .{
208 .n_strx = name_str.pos,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 .n_sect = 0,210 .n_sect = 0,
211 .n_desc = 0,211 .n_desc = @bitCast(@as(u16, 0)),
212 .n_value = 0,212 .n_value = 0,
213 };213 };
214 sym.nlist_idx = nlist_idx;214 sym.nlist_idx = nlist_idx;
...@@ -226,7 +226,7 @@ pub fn markLive(self: *InternalObject, macho_file: *MachO) void {...@@ -226,7 +226,7 @@ pub fn markLive(self: *InternalObject, macho_file: *MachO) void {
226226
227 for (0..self.symbols.items.len) |i| {227 for (0..self.symbols.items.len) |i| {
228 const nlist = self.symtab.items[i];228 const nlist = self.symtab.items[i];
229 if (!nlist.ext()) continue;229 if (!nlist.n_type.bits.ext) continue;
230230
231 const ref = self.getSymbolRef(@intCast(i), macho_file);231 const ref = self.getSymbolRef(@intCast(i), macho_file);
232 const file = ref.getFile(macho_file) orelse continue;232 const file = ref.getFile(macho_file) orelse continue;
...@@ -273,9 +273,9 @@ fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_fil...@@ -273,9 +273,9 @@ fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_fil
273 const nlist = try self.symtab.addOne(gpa);273 const nlist = try self.symtab.addOne(gpa);
274 nlist.* = .{274 nlist.* = .{
275 .n_strx = name_str.pos,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 .n_sect = @intCast(n_sect + 1),277 .n_sect = @intCast(n_sect + 1),
278 .n_desc = 0,278 .n_desc = @bitCast(@as(u16, 0)),
279 .n_value = 0,279 .n_value = 0,
280 };280 };
281 sym.nlist_idx = nlist_idx;281 sym.nlist_idx = nlist_idx;
...@@ -326,9 +326,9 @@ fn addObjcSelrefsSection(self: *InternalObject, methname_sym_index: Symbol.Index...@@ -326,9 +326,9 @@ fn addObjcSelrefsSection(self: *InternalObject, methname_sym_index: Symbol.Index
326 const nlist = try self.symtab.addOne(gpa);326 const nlist = try self.symtab.addOne(gpa);
327 nlist.* = .{327 nlist.* = .{
328 .n_strx = 0,328 .n_strx = 0,
329 .n_type = macho.N_SECT,329 .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } },
330 .n_sect = @intCast(n_sect + 1),330 .n_sect = @intCast(n_sect + 1),
331 .n_desc = 0,331 .n_desc = @bitCast(@as(u16, 0)),
332 .n_value = 0,332 .n_value = 0,
333 };333 };
334 sym.nlist_idx = nlist_idx;334 sym.nlist_idx = nlist_idx;
...@@ -352,8 +352,8 @@ pub fn resolveObjcMsgSendSymbols(self: *InternalObject, macho_file: *MachO) !voi...@@ -352,8 +352,8 @@ pub fn resolveObjcMsgSendSymbols(self: *InternalObject, macho_file: *MachO) !voi
352352
353 for (object.symbols.items, 0..) |sym, i| {353 for (object.symbols.items, 0..) |sym, i| {
354 const nlist = object.symtab.items(.nlist)[i];354 const nlist = object.symtab.items(.nlist)[i];
355 if (!nlist.ext()) continue;355 if (!nlist.n_type.bits.ext) continue;
356 if (!nlist.undf()) continue;356 if (nlist.n_type.bits.type != .undf) continue;
357357
358 const ref = object.getSymbolRef(@intCast(i), macho_file);358 const ref = object.getSymbolRef(@intCast(i), macho_file);
359 if (ref.getFile(macho_file) != null) continue;359 if (ref.getFile(macho_file) != null) continue;
...@@ -381,9 +381,9 @@ pub fn resolveObjcMsgSendSymbols(self: *InternalObject, macho_file: *MachO) !voi...@@ -381,9 +381,9 @@ pub fn resolveObjcMsgSendSymbols(self: *InternalObject, macho_file: *MachO) !voi
381 const nlist = try self.symtab.addOne(gpa);381 const nlist = try self.symtab.addOne(gpa);
382 nlist.* = .{382 nlist.* = .{
383 .n_strx = name_str.pos,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 .n_sect = 0,385 .n_sect = 0,
386 .n_desc = 0,386 .n_desc = @bitCast(@as(u16, 0)),
387 .n_value = 0,387 .n_value = 0,
388 };388 };
389 sym.nlist_idx = nlist_idx;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,13 +179,13 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
179 idx: usize,179 idx: usize,
180180
181 fn rank(ctx: *const Object, nl: macho.nlist_64) u8 {181 fn rank(ctx: *const Object, nl: macho.nlist_64) u8 {
182 if (!nl.ext()) {182 if (!nl.n_type.bits.ext) {
183 const name = ctx.getNStrx(nl.n_strx);183 const name = ctx.getNStrx(nl.n_strx);
184 if (name.len == 0) return 5;184 if (name.len == 0) return 5;
185 if (name[0] == 'l' or name[0] == 'L') return 4;185 if (name[0] == 'l' or name[0] == 'L') return 4;
186 return 3;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 }
190190
191 fn lessThan(ctx: *const Object, lhs: @This(), rhs: @This()) bool {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,7 +202,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
202 var nlists = try std.array_list.Managed(NlistIdx).initCapacity(gpa, self.symtab.items(.nlist).len);202 var nlists = try std.array_list.Managed(NlistIdx).initCapacity(gpa, self.symtab.items(.nlist).len);
203 defer nlists.deinit();203 defer nlists.deinit();
204 for (self.symtab.items(.nlist), 0..) |nlist, i| {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 nlists.appendAssumeCapacity(.{ .nlist = nlist, .idx = i });206 nlists.appendAssumeCapacity(.{ .nlist = nlist, .idx = i });
207 }207 }
208 mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan);208 mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan);
...@@ -488,9 +488,9 @@ fn initCstringLiterals(self: *Object, allocator: Allocator, file: File.Handle, m...@@ -488,9 +488,9 @@ fn initCstringLiterals(self: *Object, allocator: Allocator, file: File.Handle, m
488 self.symtab.set(nlist_index, .{488 self.symtab.set(nlist_index, .{
489 .nlist = .{489 .nlist = .{
490 .n_strx = name_str.pos,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 .n_sect = @intCast(atom.n_sect + 1),492 .n_sect = @intCast(atom.n_sect + 1),
493 .n_desc = 0,493 .n_desc = @bitCast(@as(u16, 0)),
494 .n_value = atom.getInputAddress(macho_file),494 .n_value = atom.getInputAddress(macho_file),
495 },495 },
496 .size = atom.size,496 .size = atom.size,
...@@ -555,9 +555,9 @@ fn initFixedSizeLiterals(self: *Object, allocator: Allocator, macho_file: *MachO...@@ -555,9 +555,9 @@ fn initFixedSizeLiterals(self: *Object, allocator: Allocator, macho_file: *MachO
555 self.symtab.set(nlist_index, .{555 self.symtab.set(nlist_index, .{
556 .nlist = .{556 .nlist = .{
557 .n_strx = name_str.pos,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 .n_sect = @intCast(atom.n_sect + 1),559 .n_sect = @intCast(atom.n_sect + 1),
560 .n_desc = 0,560 .n_desc = @bitCast(@as(u16, 0)),
561 .n_value = atom.getInputAddress(macho_file),561 .n_value = atom.getInputAddress(macho_file),
562 },562 },
563 .size = atom.size,563 .size = atom.size,
...@@ -613,9 +613,9 @@ fn initPointerLiterals(self: *Object, allocator: Allocator, macho_file: *MachO)...@@ -613,9 +613,9 @@ fn initPointerLiterals(self: *Object, allocator: Allocator, macho_file: *MachO)
613 self.symtab.set(nlist_index, .{613 self.symtab.set(nlist_index, .{
614 .nlist = .{614 .nlist = .{
615 .n_strx = name_str.pos,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 .n_sect = @intCast(atom.n_sect + 1),617 .n_sect = @intCast(atom.n_sect + 1),
618 .n_desc = 0,618 .n_desc = @bitCast(@as(u16, 0)),
619 .n_value = atom.getInputAddress(macho_file),619 .n_value = atom.getInputAddress(macho_file),
620 },620 },
621 .size = atom.size,621 .size = atom.size,
...@@ -805,7 +805,7 @@ fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void {...@@ -805,7 +805,7 @@ fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void {
805 const tracy = trace(@src());805 const tracy = trace(@src());
806 defer tracy.end();806 defer tracy.end();
807 for (self.symtab.items(.nlist), self.symtab.items(.atom)) |nlist, *atom| {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 const sect = self.sections.items(.header)[nlist.n_sect - 1];809 const sect = self.sections.items(.header)[nlist.n_sect - 1];
810 const subs = self.sections.items(.subsections)[nlist.n_sect - 1].items;810 const subs = self.sections.items(.subsections)[nlist.n_sect - 1].items;
811 if (nlist.n_value == sect.addr) {811 if (nlist.n_value == sect.addr) {
...@@ -852,30 +852,30 @@ fn initSymbols(self: *Object, allocator: Allocator, macho_file: *MachO) !void {...@@ -852,30 +852,30 @@ fn initSymbols(self: *Object, allocator: Allocator, macho_file: *MachO) !void {
852 symbol.extra = self.addSymbolExtraAssumeCapacity(.{});852 symbol.extra = self.addSymbolExtraAssumeCapacity(.{});
853853
854 if (self.getAtom(atom_index)) |atom| {854 if (self.getAtom(atom_index)) |atom| {
855 assert(!nlist.abs());855 assert(nlist.n_type.bits.type != .abs);
856 symbol.value -= atom.getInputAddress(macho_file);856 symbol.value -= atom.getInputAddress(macho_file);
857 symbol.atom_ref = .{ .index = atom_index, .file = self.index };857 symbol.atom_ref = .{ .index = atom_index, .file = self.index };
858 }858 }
859859
860 symbol.flags.weak = nlist.weakDef();860 symbol.flags.weak = nlist.n_desc.weak_def_or_ref_to_weak;
861 symbol.flags.abs = nlist.abs();861 symbol.flags.abs = nlist.n_type.bits.type == .abs;
862 symbol.flags.tentative = nlist.tentative();862 symbol.flags.tentative = nlist.tentative();
863 symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip();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 & macho.REFERENCED_DYNAMICALLY != 0;864 symbol.flags.dyn_ref = nlist.n_desc.referenced_dynamically;
865 symbol.flags.interposable = false;865 symbol.flags.interposable = false;
866 // TODO866 // 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();
868868
869 if (nlist.sect() and869 if (nlist.n_type.bits.type == .sect and
870 self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)870 self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
871 {871 {
872 symbol.flags.tlv = true;872 symbol.flags.tlv = true;
873 }873 }
874874
875 if (nlist.ext()) {875 if (nlist.n_type.bits.ext) {
876 if (nlist.undf()) {876 if (nlist.n_type.bits.type == .undf) {
877 symbol.flags.weak_ref = nlist.weakRef();877 symbol.flags.weak_ref = nlist.n_desc.weak_ref;
878 } else if (nlist.pext() or (nlist.weakDef() and nlist.weakRef()) or self.hidden) {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 symbol.visibility = .hidden;879 symbol.visibility = .hidden;
880 } else {880 } else {
881 symbol.visibility = .global;881 symbol.visibility = .global;
...@@ -902,10 +902,10 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f...@@ -902,10 +902,10 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f
902 };902 };
903903
904 const start: u32 = for (self.symtab.items(.nlist), 0..) |nlist, i| {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 } else @intCast(self.symtab.items(.nlist).len);906 } else @intCast(self.symtab.items(.nlist).len);
907 const end: u32 = for (self.symtab.items(.nlist)[start..], start..) |nlist, i| {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 } else @intCast(self.symtab.items(.nlist).len);909 } else @intCast(self.symtab.items(.nlist).len);
910910
911 if (start == end) return;911 if (start == end) return;
...@@ -919,7 +919,7 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f...@@ -919,7 +919,7 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f
919 var addr_lookup = std.StringHashMap(u64).init(allocator);919 var addr_lookup = std.StringHashMap(u64).init(allocator);
920 defer addr_lookup.deinit();920 defer addr_lookup.deinit();
921 for (syms) |sym| {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 try addr_lookup.putNoClobber(self.getNStrx(sym.n_strx), sym.n_value);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,41 +927,43 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f
927 var i: u32 = start;927 var i: u32 = start;
928 while (i < end) : (i += 1) {928 while (i < end) : (i += 1) {
929 const open = syms[i];929 const open = syms[i];
930 if (open.n_type != macho.N_SO) {930 if (open.n_type.stab != .so) {
931 try macho_file.reportParseError2(self.index, "unexpected symbol stab type 0x{x} as the first entry", .{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 return error.MalformedObject;934 return error.MalformedObject;
935 }935 }
936936
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) {}
938938
939 var sf: StabFile = .{ .comp_dir = i };939 var sf: StabFile = .{ .comp_dir = i };
940 // TODO validate940 // TODO validate
941 i += 3;941 i += 3;
942942
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 const nlist = syms[i];944 const nlist = syms[i];
945 var stab: StabFile.Stab = .{};945 var stab: StabFile.Stab = .{};
946 switch (nlist.n_type) {946 switch (nlist.n_type.stab) {
947 macho.N_BNSYM => {947 .bnsym => {
948 stab.is_func = true;948 stab.is_func = true;
949 stab.index = sym_lookup.find(nlist.n_value);949 stab.index = sym_lookup.find(nlist.n_value);
950 // TODO validate950 // TODO validate
951 i += 3;951 i += 3;
952 },952 },
953 macho.N_GSYM => {953 .gsym => {
954 stab.is_func = false;954 stab.is_func = false;
955 stab.index = sym_lookup.find(addr_lookup.get(self.getNStrx(nlist.n_strx)).?);955 stab.index = sym_lookup.find(addr_lookup.get(self.getNStrx(nlist.n_strx)).?);
956 },956 },
957 macho.N_STSYM => {957 .stsym => {
958 stab.is_func = false;958 stab.is_func = false;
959 stab.index = sym_lookup.find(nlist.n_value);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 else => {965 else => {
962 try macho_file.reportParseError2(self.index, "unhandled symbol stab type 0x{x}", .{966 try macho_file.reportParseError2(self.index, "unhandled symbol stab type '{t}'", .{nlist.n_type.stab});
963 nlist.n_type,
964 });
965 return error.MalformedObject;967 return error.MalformedObject;
966 },968 },
967 }969 }
...@@ -1132,7 +1134,7 @@ fn initUnwindRecords(self: *Object, allocator: Allocator, sect_id: u8, file: Fil...@@ -1132,7 +1134,7 @@ fn initUnwindRecords(self: *Object, allocator: Allocator, sect_id: u8, file: Fil
1132 fn find(fs: @This(), addr: u64) ?Symbol.Index {1134 fn find(fs: @This(), addr: u64) ?Symbol.Index {
1133 for (0..fs.ctx.symbols.items.len) |i| {1135 for (0..fs.ctx.symbols.items.len) |i| {
1134 const nlist = fs.ctx.symtab.items(.nlist)[i];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 return null;1139 return null;
1138 }1140 }
...@@ -1241,8 +1243,8 @@ fn parseUnwindRecords(self: *Object, allocator: Allocator, cpu_arch: std.Target....@@ -1241,8 +1243,8 @@ fn parseUnwindRecords(self: *Object, allocator: Allocator, cpu_arch: std.Target.
12411243
1242 const slice = self.symtab.slice();1244 const slice = self.symtab.slice();
1243 for (slice.items(.nlist), slice.items(.atom), slice.items(.size)) |nlist, atom, size| {1245 for (slice.items(.nlist), slice.items(.atom), slice.items(.size)) |nlist, atom, size| {
1244 if (nlist.stab()) continue;1246 if (nlist.n_type.bits.is_stab != 0) continue;
1245 if (!nlist.sect()) continue;1247 if (nlist.n_type.bits.type != .sect) continue;
1246 const sect = self.sections.items(.header)[nlist.n_sect - 1];1248 const sect = self.sections.items(.header)[nlist.n_sect - 1];
1247 if (sect.isCode() and sect.size > 0) {1249 if (sect.isCode() and sect.size > 0) {
1248 try superposition.ensureUnusedCapacity(1);1250 try superposition.ensureUnusedCapacity(1);
...@@ -1458,8 +1460,8 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void {...@@ -1458,8 +1460,8 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void {
1458 const gpa = macho_file.base.comp.gpa;1460 const gpa = macho_file.base.comp.gpa;
14591461
1460 for (self.symtab.items(.nlist), self.symtab.items(.atom), self.globals.items, 0..) |nlist, atom_index, *global, i| {1462 for (self.symtab.items(.nlist), self.symtab.items(.atom), self.globals.items, 0..) |nlist, atom_index, *global, i| {
1461 if (!nlist.ext()) continue;1463 if (!nlist.n_type.bits.ext) continue;
1462 if (nlist.sect()) {1464 if (nlist.n_type.bits.type == .sect) {
1463 const atom = self.getAtom(atom_index).?;1465 const atom = self.getAtom(atom_index).?;
1464 if (!atom.isAlive()) continue;1466 if (!atom.isAlive()) continue;
1465 }1467 }
...@@ -1473,7 +1475,7 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void {...@@ -1473,7 +1475,7 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void {
1473 }1475 }
1474 global.* = gop.index;1476 global.* = gop.index;
14751477
1476 if (nlist.undf() and !nlist.tentative()) continue;1478 if (nlist.n_type.bits.type == .undf and !nlist.tentative()) continue;
1477 if (gop.ref.getFile(macho_file) == null) {1479 if (gop.ref.getFile(macho_file) == null) {
1478 gop.ref.* = .{ .index = @intCast(i), .file = self.index };1480 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
1479 continue;1481 continue;
...@@ -1481,7 +1483,7 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void {...@@ -1481,7 +1483,7 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void {
14811483
1482 if (self.asFile().getSymbolRank(.{1484 if (self.asFile().getSymbolRank(.{
1483 .archive = !self.alive,1485 .archive = !self.alive,
1484 .weak = nlist.weakDef(),1486 .weak = nlist.n_desc.weak_def_or_ref_to_weak,
1485 .tentative = nlist.tentative(),1487 .tentative = nlist.tentative(),
1486 }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) {1488 }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) {
1487 gop.ref.* = .{ .index = @intCast(i), .file = self.index };1489 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
...@@ -1495,12 +1497,12 @@ pub fn markLive(self: *Object, macho_file: *MachO) void {...@@ -1495,12 +1497,12 @@ pub fn markLive(self: *Object, macho_file: *MachO) void {
14951497
1496 for (0..self.symbols.items.len) |i| {1498 for (0..self.symbols.items.len) |i| {
1497 const nlist = self.symtab.items(.nlist)[i];1499 const nlist = self.symtab.items(.nlist)[i];
1498 if (!nlist.ext()) continue;1500 if (!nlist.n_type.bits.ext) continue;
14991501
1500 const ref = self.getSymbolRef(@intCast(i), macho_file);1502 const ref = self.getSymbolRef(@intCast(i), macho_file);
1501 const file = ref.getFile(macho_file) orelse continue;1503 const file = ref.getFile(macho_file) orelse continue;
1502 const sym = ref.getSymbol(macho_file).?;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 if (should_keep and file == .object and !file.object.alive) {1506 if (should_keep and file == .object and !file.object.alive) {
1505 file.object.alive = true;1507 file.object.alive = true;
1506 file.object.markLive(macho_file);1508 file.object.markLive(macho_file);
...@@ -1565,7 +1567,7 @@ pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void {...@@ -1565,7 +1567,7 @@ pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void {
1565 const name = try std.fmt.allocPrintSentinel(gpa, "__DATA$__common${s}", .{sym.getName(macho_file)}, 0);1567 const name = try std.fmt.allocPrintSentinel(gpa, "__DATA$__common${s}", .{sym.getName(macho_file)}, 0);
1566 defer gpa.free(name);1568 defer gpa.free(name);
15671569
1568 const alignment = (nlist.n_desc >> 8) & 0x0f;1570 const alignment = (@as(u16, @bitCast(nlist.n_desc)) >> 8) & 0x0f;
1569 const n_sect = try self.addSection(gpa, "__DATA", "__common");1571 const n_sect = try self.addSection(gpa, "__DATA", "__common");
1570 const atom_index = try self.addAtom(gpa, .{1572 const atom_index = try self.addAtom(gpa, .{
1571 .name = try self.addString(gpa, name),1573 .name = try self.addString(gpa, name),
...@@ -1589,9 +1591,9 @@ pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void {...@@ -1589,9 +1591,9 @@ pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void {
1589 sym.visibility = .global;1591 sym.visibility = .global;
15901592
1591 nlist.n_value = 0;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 nlist.n_sect = 0;1595 nlist.n_sect = 0;
1594 nlist.n_desc = 0;1596 nlist.n_desc = @bitCast(@as(u16, 0));
1595 nlist_atom.* = atom_index;1597 nlist_atom.* = atom_index;
1596 }1598 }
1597}1599}
...@@ -1685,7 +1687,7 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {...@@ -1685,7 +1687,7 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {
1685pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, macho_file: *MachO) error{OutOfMemory}!void {1687pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, macho_file: *MachO) error{OutOfMemory}!void {
1686 const gpa = macho_file.base.comp.gpa;1688 const gpa = macho_file.base.comp.gpa;
1687 for (self.symtab.items(.nlist)) |nlist| {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 const off = try ar_symtab.strtab.insert(gpa, self.getNStrx(nlist.n_strx));1691 const off = try ar_symtab.strtab.insert(gpa, self.getNStrx(nlist.n_strx));
1690 try ar_symtab.entries.append(gpa, .{ .off = off, .file = self.index });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,30 +2030,30 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
2028 ) void {2030 ) void {
2029 context.symtab.items[index] = .{2031 context.symtab.items[index] = .{
2030 .n_strx = 0,2032 .n_strx = 0,
2031 .n_type = macho.N_BNSYM,2033 .n_type = .{ .stab = .bnsym },
2032 .n_sect = n_sect,2034 .n_sect = n_sect,
2033 .n_desc = 0,2035 .n_desc = @bitCast(@as(u16, 0)),
2034 .n_value = n_value,2036 .n_value = n_value,
2035 };2037 };
2036 context.symtab.items[index + 1] = .{2038 context.symtab.items[index + 1] = .{
2037 .n_strx = n_strx,2039 .n_strx = n_strx,
2038 .n_type = macho.N_FUN,2040 .n_type = .{ .stab = .fun },
2039 .n_sect = n_sect,2041 .n_sect = n_sect,
2040 .n_desc = 0,2042 .n_desc = @bitCast(@as(u16, 0)),
2041 .n_value = n_value,2043 .n_value = n_value,
2042 };2044 };
2043 context.symtab.items[index + 2] = .{2045 context.symtab.items[index + 2] = .{
2044 .n_strx = 0,2046 .n_strx = 0,
2045 .n_type = macho.N_FUN,2047 .n_type = .{ .stab = .fun },
2046 .n_sect = 0,2048 .n_sect = 0,
2047 .n_desc = 0,2049 .n_desc = @bitCast(@as(u16, 0)),
2048 .n_value = size,2050 .n_value = size,
2049 };2051 };
2050 context.symtab.items[index + 3] = .{2052 context.symtab.items[index + 3] = .{
2051 .n_strx = 0,2053 .n_strx = 0,
2052 .n_type = macho.N_ENSYM,2054 .n_type = .{ .stab = .ensym },
2053 .n_sect = n_sect,2055 .n_sect = n_sect,
2054 .n_desc = 0,2056 .n_desc = @bitCast(@as(u16, 0)),
2055 .n_value = size,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,9 +2070,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
2068 // N_SO comp_dir2070 // N_SO comp_dir
2069 ctx.symtab.items[index] = .{2071 ctx.symtab.items[index] = .{
2070 .n_strx = n_strx,2072 .n_strx = n_strx,
2071 .n_type = macho.N_SO,2073 .n_type = .{ .stab = .so },
2072 .n_sect = 0,2074 .n_sect = 0,
2073 .n_desc = 0,2075 .n_desc = @bitCast(@as(u16, 0)),
2074 .n_value = 0,2076 .n_value = 0,
2075 };2077 };
2076 index += 1;2078 index += 1;
...@@ -2081,9 +2083,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v...@@ -2081,9 +2083,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
2081 // N_SO tu_name2083 // N_SO tu_name
2082 macho_file.symtab.items[index] = .{2084 macho_file.symtab.items[index] = .{
2083 .n_strx = n_strx,2085 .n_strx = n_strx,
2084 .n_type = macho.N_SO,2086 .n_type = .{ .stab = .so },
2085 .n_sect = 0,2087 .n_sect = 0,
2086 .n_desc = 0,2088 .n_desc = @bitCast(@as(u16, 0)),
2087 .n_value = 0,2089 .n_value = 0,
2088 };2090 };
2089 index += 1;2091 index += 1;
...@@ -2094,9 +2096,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v...@@ -2094,9 +2096,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
2094 // N_OSO path2096 // N_OSO path
2095 ctx.symtab.items[index] = .{2097 ctx.symtab.items[index] = .{
2096 .n_strx = n_strx,2098 .n_strx = n_strx,
2097 .n_type = macho.N_OSO,2099 .n_type = .{ .stab = .oso },
2098 .n_sect = 0,2100 .n_sect = 0,
2099 .n_desc = 1,2101 .n_desc = @bitCast(@as(u16, 1)),
2100 .n_value = self.mtime,2102 .n_value = self.mtime,
2101 };2103 };
2102 index += 1;2104 index += 1;
...@@ -2159,18 +2161,18 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v...@@ -2159,18 +2161,18 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
2159 } else if (sym.visibility == .global) {2161 } else if (sym.visibility == .global) {
2160 ctx.symtab.items[index] = .{2162 ctx.symtab.items[index] = .{
2161 .n_strx = sym_n_strx,2163 .n_strx = sym_n_strx,
2162 .n_type = macho.N_GSYM,2164 .n_type = .{ .stab = .gsym },
2163 .n_sect = sym_n_sect,2165 .n_sect = sym_n_sect,
2164 .n_desc = 0,2166 .n_desc = @bitCast(@as(u16, 0)),
2165 .n_value = 0,2167 .n_value = 0,
2166 };2168 };
2167 index += 1;2169 index += 1;
2168 } else {2170 } else {
2169 ctx.symtab.items[index] = .{2171 ctx.symtab.items[index] = .{
2170 .n_strx = sym_n_strx,2172 .n_strx = sym_n_strx,
2171 .n_type = macho.N_STSYM,2173 .n_type = .{ .stab = .stsym },
2172 .n_sect = sym_n_sect,2174 .n_sect = sym_n_sect,
2173 .n_desc = 0,2175 .n_desc = @bitCast(@as(u16, 0)),
2174 .n_value = sym_n_value,2176 .n_value = sym_n_value,
2175 };2177 };
2176 index += 1;2178 index += 1;
...@@ -2181,9 +2183,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v...@@ -2181,9 +2183,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
2181 // N_SO2183 // N_SO
2182 ctx.symtab.items[index] = .{2184 ctx.symtab.items[index] = .{
2183 .n_strx = 0,2185 .n_strx = 0,
2184 .n_type = macho.N_SO,2186 .n_type = .{ .stab = .so },
2185 .n_sect = 0,2187 .n_sect = 0,
2186 .n_desc = 0,2188 .n_desc = @bitCast(@as(u16, 0)),
2187 .n_value = 0,2189 .n_value = 0,
2188 };2190 };
2189 } else {2191 } else {
...@@ -2198,9 +2200,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v...@@ -2198,9 +2200,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
2198 // N_SO comp_dir2200 // N_SO comp_dir
2199 ctx.symtab.items[index] = .{2201 ctx.symtab.items[index] = .{
2200 .n_strx = n_strx,2202 .n_strx = n_strx,
2201 .n_type = macho.N_SO,2203 .n_type = .{ .stab = .so },
2202 .n_sect = 0,2204 .n_sect = 0,
2203 .n_desc = 0,2205 .n_desc = @bitCast(@as(u16, 0)),
2204 .n_value = 0,2206 .n_value = 0,
2205 };2207 };
2206 index += 1;2208 index += 1;
...@@ -2211,9 +2213,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v...@@ -2211,9 +2213,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
2211 // N_SO tu_name2213 // N_SO tu_name
2212 ctx.symtab.items[index] = .{2214 ctx.symtab.items[index] = .{
2213 .n_strx = n_strx,2215 .n_strx = n_strx,
2214 .n_type = macho.N_SO,2216 .n_type = .{ .stab = .so },
2215 .n_sect = 0,2217 .n_sect = 0,
2216 .n_desc = 0,2218 .n_desc = @bitCast(@as(u16, 0)),
2217 .n_value = 0,2219 .n_value = 0,
2218 };2220 };
2219 index += 1;2221 index += 1;
...@@ -2224,9 +2226,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v...@@ -2224,9 +2226,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
2224 // N_OSO path2226 // N_OSO path
2225 ctx.symtab.items[index] = .{2227 ctx.symtab.items[index] = .{
2226 .n_strx = n_strx,2228 .n_strx = n_strx,
2227 .n_type = macho.N_OSO,2229 .n_type = .{ .stab = .so },
2228 .n_sect = 0,2230 .n_sect = 0,
2229 .n_desc = 1,2231 .n_desc = @bitCast(@as(u16, 1)),
2230 .n_value = sf.getOsoModTime(self),2232 .n_value = sf.getOsoModTime(self),
2231 };2233 };
2232 index += 1;2234 index += 1;
...@@ -2254,18 +2256,18 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v...@@ -2254,18 +2256,18 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
2254 } else if (sym.visibility == .global) {2256 } else if (sym.visibility == .global) {
2255 ctx.symtab.items[index] = .{2257 ctx.symtab.items[index] = .{
2256 .n_strx = sym_n_strx,2258 .n_strx = sym_n_strx,
2257 .n_type = macho.N_GSYM,2259 .n_type = .{ .stab = .gsym },
2258 .n_sect = sym_n_sect,2260 .n_sect = sym_n_sect,
2259 .n_desc = 0,2261 .n_desc = @bitCast(@as(u16, 0)),
2260 .n_value = 0,2262 .n_value = 0,
2261 };2263 };
2262 index += 1;2264 index += 1;
2263 } else {2265 } else {
2264 ctx.symtab.items[index] = .{2266 ctx.symtab.items[index] = .{
2265 .n_strx = sym_n_strx,2267 .n_strx = sym_n_strx,
2266 .n_type = macho.N_STSYM,2268 .n_type = .{ .stab = .stsym },
2267 .n_sect = sym_n_sect,2269 .n_sect = sym_n_sect,
2268 .n_desc = 0,2270 .n_desc = @bitCast(@as(u16, 0)),
2269 .n_value = sym_n_value,2271 .n_value = sym_n_value,
2270 };2272 };
2271 index += 1;2273 index += 1;
...@@ -2276,9 +2278,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v...@@ -2276,9 +2278,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
2276 // N_SO2278 // N_SO
2277 ctx.symtab.items[index] = .{2279 ctx.symtab.items[index] = .{
2278 .n_strx = 0,2280 .n_strx = 0,
2279 .n_type = macho.N_SO,2281 .n_type = .{ .stab = .so },
2280 .n_sect = 0,2282 .n_sect = 0,
2281 .n_desc = 0,2283 .n_desc = @bitCast(@as(u16, 0)),
2282 .n_value = 0,2284 .n_value = 0,
2283 };2285 };
2284 index += 1;2286 index += 1;
src/link/MachO/Symbol.zig+28-14
...@@ -36,7 +36,7 @@ pub fn isLocal(symbol: Symbol) bool {...@@ -36,7 +36,7 @@ pub fn isLocal(symbol: Symbol) bool {
36pub fn isSymbolStab(symbol: Symbol, macho_file: *MachO) bool {36pub fn isSymbolStab(symbol: Symbol, macho_file: *MachO) bool {
37 const file = symbol.getFile(macho_file) orelse return false;37 const file = symbol.getFile(macho_file) orelse return false;
38 return switch (file) {38 return switch (file) {
39 .object => symbol.getNlist(macho_file).stab(),39 .object => symbol.getNlist(macho_file).n_type.bits.is_stab != 0,
40 else => false,40 else => false,
41 };41 };
42}42}
...@@ -233,35 +233,49 @@ pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void {...@@ -233,35 +233,49 @@ pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void {
233233
234pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void {234pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void {
235 if (symbol.isLocal()) {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 out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.getOutputSectionIndex(macho_file) + 1);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 out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);244 out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);
240245
241 switch (symbol.visibility) {246 switch (symbol.visibility) {
242 .hidden => out.n_type |= macho.N_PEXT,247 .hidden => out.n_type.bits.pext = true,
243 else => {},248 else => {},
244 }249 }
245 } else if (symbol.flags.@"export") {250 } else if (symbol.flags.@"export") {
246 assert(symbol.visibility == .global);251 assert(symbol.visibility == .global);
247 out.n_type = macho.N_EXT;252 out.n_type = .{ .bits = .{
248 out.n_type |= if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;253 .ext = true,
254 .type = if (symbol.flags.abs) .abs else .sect,
255 .pext = false,
256 .is_stab = 0,
257 } };
249 out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.getOutputSectionIndex(macho_file) + 1);258 out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.getOutputSectionIndex(macho_file) + 1);
250 out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);259 out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);
251 out.n_desc = 0;260 out.n_desc = @bitCast(@as(u16, 0));
252261
253 if (symbol.flags.weak) {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 if (symbol.flags.dyn_ref) {265 if (symbol.flags.dyn_ref) {
257 out.n_desc |= macho.REFERENCED_DYNAMICALLY;266 out.n_desc.referenced_dynamically = true;
258 }267 }
259 } else {268 } else {
260 assert(symbol.visibility == .global);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 out.n_sect = 0;276 out.n_sect = 0;
263 out.n_value = 0;277 out.n_value = 0;
264 out.n_desc = 0;278 out.n_desc = @bitCast(@as(u16, 0));
265279
266 // TODO:280 // TODO:
267 // const ord: u16 = if (macho_file.options.namespace == .flat)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,14 +288,14 @@ pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) vo
274 ord288 ord
275 else289 else
276 macho.BIND_SPECIAL_DYLIB_SELF;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);
278292
279 if (symbol.flags.weak) {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 }
282296
283 if (symbol.weakRef(macho_file)) {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,10 +56,10 @@ pub fn writeSymtab(thunk: Thunk, macho_file: *MachO, ctx: anytype) void {
56 n_strx += @intCast("__thunk".len);56 n_strx += @intCast("__thunk".len);
57 ctx.strtab.items[n_strx] = 0;57 ctx.strtab.items[n_strx] = 0;
58 n_strx += 1;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 out_sym.n_sect = @intCast(thunk.out_n_sect + 1);60 out_sym.n_sect = @intCast(thunk.out_n_sect + 1);
61 out_sym.n_value = @intCast(thunk.getTargetAddress(ref, macho_file));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}
6565
src/link/MachO/ZigObject.zig+16-16
...@@ -123,9 +123,9 @@ fn newSymbol(self: *ZigObject, allocator: Allocator, name: MachO.String, args: s...@@ -123,9 +123,9 @@ fn newSymbol(self: *ZigObject, allocator: Allocator, name: MachO.String, args: s
123 self.symtab.set(nlist_idx, .{123 self.symtab.set(nlist_idx, .{
124 .nlist = .{124 .nlist = .{
125 .n_strx = name.pos,125 .n_strx = name.pos,
126 .n_type = args.type,126 .n_type = @bitCast(args.type),
127 .n_sect = 0,127 .n_sect = 0,
128 .n_desc = args.desc,128 .n_desc = @bitCast(args.desc),
129 .n_value = 0,129 .n_value = 0,
130 },130 },
131 .size = 0,131 .size = 0,
...@@ -206,8 +206,8 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void {...@@ -206,8 +206,8 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void {
206 const gpa = macho_file.base.comp.gpa;206 const gpa = macho_file.base.comp.gpa;
207207
208 for (self.symtab.items(.nlist), self.symtab.items(.atom), self.globals.items, 0..) |nlist, atom_index, *global, i| {208 for (self.symtab.items(.nlist), self.symtab.items(.atom), self.globals.items, 0..) |nlist, atom_index, *global, i| {
209 if (!nlist.ext()) continue;209 if (!nlist.n_type.bits.ext) continue;
210 if (nlist.sect()) {210 if (nlist.n_type.bits.type == .sect) {
211 const atom = self.getAtom(atom_index).?;211 const atom = self.getAtom(atom_index).?;
212 if (!atom.isAlive()) continue;212 if (!atom.isAlive()) continue;
213 }213 }
...@@ -221,7 +221,7 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void {...@@ -221,7 +221,7 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void {
221 }221 }
222 global.* = gop.index;222 global.* = gop.index;
223223
224 if (nlist.undf() and !nlist.tentative()) continue;224 if (nlist.n_type.bits.type == .undf and !nlist.tentative()) continue;
225 if (gop.ref.getFile(macho_file) == null) {225 if (gop.ref.getFile(macho_file) == null) {
226 gop.ref.* = .{ .index = @intCast(i), .file = self.index };226 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
227 continue;227 continue;
...@@ -229,7 +229,7 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void {...@@ -229,7 +229,7 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void {
229229
230 if (self.asFile().getSymbolRank(.{230 if (self.asFile().getSymbolRank(.{
231 .archive = false,231 .archive = false,
232 .weak = nlist.weakDef(),232 .weak = nlist.n_desc.weak_def_or_ref_to_weak,
233 .tentative = nlist.tentative(),233 .tentative = nlist.tentative(),
234 }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) {234 }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) {
235 gop.ref.* = .{ .index = @intCast(i), .file = self.index };235 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
...@@ -243,12 +243,12 @@ pub fn markLive(self: *ZigObject, macho_file: *MachO) void {...@@ -243,12 +243,12 @@ pub fn markLive(self: *ZigObject, macho_file: *MachO) void {
243243
244 for (0..self.symbols.items.len) |i| {244 for (0..self.symbols.items.len) |i| {
245 const nlist = self.symtab.items(.nlist)[i];245 const nlist = self.symtab.items(.nlist)[i];
246 if (!nlist.ext()) continue;246 if (!nlist.n_type.bits.ext) continue;
247247
248 const ref = self.getSymbolRef(@intCast(i), macho_file);248 const ref = self.getSymbolRef(@intCast(i), macho_file);
249 const file = ref.getFile(macho_file) orelse continue;249 const file = ref.getFile(macho_file) orelse continue;
250 const sym = ref.getSymbol(macho_file).?;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 if (should_keep and file == .object and !file.object.alive) {252 if (should_keep and file == .object and !file.object.alive) {
253 file.object.alive = true;253 file.object.alive = true;
254 file.object.markLive(macho_file);254 file.object.markLive(macho_file);
...@@ -331,8 +331,8 @@ pub fn claimUnresolved(self: *ZigObject, macho_file: *MachO) void {...@@ -331,8 +331,8 @@ pub fn claimUnresolved(self: *ZigObject, macho_file: *MachO) void {
331331
332 for (self.symbols.items, 0..) |*sym, i| {332 for (self.symbols.items, 0..) |*sym, i| {
333 const nlist = self.symtab.items(.nlist)[i];333 const nlist = self.symtab.items(.nlist)[i];
334 if (!nlist.ext()) continue;334 if (!nlist.n_type.bits.ext) continue;
335 if (!nlist.undf()) continue;335 if (nlist.n_type.bits.type != .undf) continue;
336336
337 if (self.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;337 if (self.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;
338338
...@@ -974,7 +974,7 @@ fn updateNavCode(...@@ -974,7 +974,7 @@ fn updateNavCode(
974 atom.setAlive(true);974 atom.setAlive(true);
975 atom.name = sym.name;975 atom.name = sym.name;
976 nlist.n_strx = sym.name.pos;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 nlist.n_sect = sect_index + 1;978 nlist.n_sect = sect_index + 1;
979 self.symtab.items(.size)[sym.nlist_idx] = code.len;979 self.symtab.items(.size)[sym.nlist_idx] = code.len;
980980
...@@ -1115,7 +1115,7 @@ fn createTlvDescriptor(...@@ -1115,7 +1115,7 @@ fn createTlvDescriptor(
1115 atom.name = sym.name;1115 atom.name = sym.name;
1116 nlist.n_strx = sym.name.pos;1116 nlist.n_strx = sym.name.pos;
1117 nlist.n_sect = sect_index + 1;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 nlist.n_value = 0;1119 nlist.n_value = 0;
1120 self.symtab.items(.size)[sym.nlist_idx] = size;1120 self.symtab.items(.size)[sym.nlist_idx] = size;
11211121
...@@ -1322,7 +1322,7 @@ pub fn updateExports(...@@ -1322,7 +1322,7 @@ pub fn updateExports(
1322 const global_sym = &self.symbols.items[global_nlist_index];1322 const global_sym = &self.symbols.items[global_nlist_index];
1323 global_nlist.n_value = nlist.n_value;1323 global_nlist.n_value = nlist.n_value;
1324 global_nlist.n_sect = nlist.n_sect;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 self.symtab.items(.size)[global_nlist_index] = self.symtab.items(.size)[nlist_idx];1326 self.symtab.items(.size)[global_nlist_index] = self.symtab.items(.size)[nlist_idx];
1327 self.symtab.items(.atom)[global_nlist_index] = atom_index;1327 self.symtab.items(.atom)[global_nlist_index] = atom_index;
1328 global_sym.atom_ref = .{ .index = atom_index, .file = self.index };1328 global_sym.atom_ref = .{ .index = atom_index, .file = self.index };
...@@ -1330,7 +1330,7 @@ pub fn updateExports(...@@ -1330,7 +1330,7 @@ pub fn updateExports(
1330 switch (exp.opts.linkage) {1330 switch (exp.opts.linkage) {
1331 .internal => {1331 .internal => {
1332 // Symbol should be hidden, or in MachO lingo, private extern.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 global_sym.visibility = .hidden;1334 global_sym.visibility = .hidden;
1335 },1335 },
1336 .strong => {1336 .strong => {
...@@ -1339,7 +1339,7 @@ pub fn updateExports(...@@ -1339,7 +1339,7 @@ pub fn updateExports(
1339 .weak => {1339 .weak => {
1340 // Weak linkage is specified as part of n_desc field.1340 // Weak linkage is specified as part of n_desc field.
1341 // Symbol's n_type is like for a symbol with strong linkage.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 global_sym.visibility = .global;1343 global_sym.visibility = .global;
1344 global_sym.flags.weak = true;1344 global_sym.flags.weak = true;
1345 },1345 },
...@@ -1394,7 +1394,7 @@ fn updateLazySymbol(...@@ -1394,7 +1394,7 @@ fn updateLazySymbol(
13941394
1395 const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];1395 const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
1396 nlist.n_strx = name_str.pos;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 nlist.n_sect = output_section_index + 1;1398 nlist.n_sect = output_section_index + 1;
1399 self.symtab.items(.size)[sym.nlist_idx] = code.len;1399 self.symtab.items(.size)[sym.nlist_idx] = code.len;
14001400
src/link/MachO/eh_frame.zig+10-10
...@@ -26,24 +26,24 @@ pub const Cie = struct {...@@ -26,24 +26,24 @@ pub const Cie = struct {
2626
27 for (aug[1..]) |ch| switch (ch) {27 for (aug[1..]) |ch| switch (ch) {
28 'R' => {28 'R' => {
29 const enc = try reader.takeByte();29 const enc: DW.EH.PE = @bitCast(try reader.takeByte());
30 if (enc != DW_EH_PE.pcrel | DW_EH_PE.absptr) {30 if (enc != @as(DW.EH.PE, .{ .type = .absptr, .rel = .pcrel })) {
31 @panic("unexpected pointer encoding"); // TODO error31 @panic("unexpected pointer encoding"); // TODO error
32 }32 }
33 },33 },
34 'P' => {34 'P' => {
35 const enc = try reader.takeByte();35 const enc: DW.EH.PE = @bitCast(try reader.takeByte());
36 if (enc != DW_EH_PE.pcrel | DW_EH_PE.indirect | DW_EH_PE.sdata4) {36 if (enc != @as(DW.EH.PE, .{ .type = .sdata4, .rel = .pcrel, .indirect = true })) {
37 @panic("unexpected personality pointer encoding"); // TODO error37 @panic("unexpected personality pointer encoding"); // TODO error
38 }38 }
39 _ = try reader.takeInt(u32, .little); // personality pointer39 _ = try reader.takeInt(u32, .little); // personality pointer
40 },40 },
41 'L' => {41 'L' => {
42 const enc = try reader.takeByte();42 const enc: DW.EH.PE = @bitCast(try reader.takeByte());
43 switch (enc & DW_EH_PE.type_mask) {43 switch (enc.type) {
44 DW_EH_PE.sdata4 => cie.lsda_size = .p32,44 .sdata4 => cie.lsda_size = .p32,
45 DW_EH_PE.absptr => cie.lsda_size = .p64,45 .absptr => cie.lsda_size = .p64,
46 else => unreachable, // TODO error46 else => @panic("unexpected lsda encoding"), // TODO error
47 }47 }
48 },48 },
49 else => @panic("unexpected augmentation string"), // TODO error49 else => @panic("unexpected augmentation string"), // TODO error
...@@ -505,7 +505,7 @@ const Writer = std.Io.Writer;...@@ -505,7 +505,7 @@ const Writer = std.Io.Writer;
505505
506const Allocator = std.mem.Allocator;506const Allocator = std.mem.Allocator;
507const Atom = @import("Atom.zig");507const Atom = @import("Atom.zig");
508const DW_EH_PE = std.dwarf.EH.PE;508const DW = std.dwarf;
509const File = @import("file.zig").File;509const File = @import("file.zig").File;
510const MachO = @import("../MachO.zig");510const MachO = @import("../MachO.zig");
511const Object = @import("Object.zig");511const Object = @import("Object.zig");
src/link/MachO/file.zig+8-8
...@@ -192,21 +192,21 @@ pub const File = union(enum) {...@@ -192,21 +192,21 @@ pub const File = union(enum) {
192 assert(file == .object or file == .zig_object);192 assert(file == .object or file == .zig_object);
193193
194 for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| {194 for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| {
195 if (!nlist.ext()) continue;195 if (!nlist.n_type.bits.ext) continue;
196 if (!nlist.undf()) continue;196 if (nlist.n_type.bits.type != .undf) continue;
197197
198 if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;198 if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;
199199
200 const is_import = switch (macho_file.undefined_treatment) {200 const is_import = switch (macho_file.undefined_treatment) {
201 .@"error" => false,201 .@"error" => false,
202 .warn, .suppress => nlist.weakRef(),202 .warn, .suppress => nlist.n_desc.weak_ref,
203 .dynamic_lookup => true,203 .dynamic_lookup => true,
204 };204 };
205 if (is_import) {205 if (is_import) {
206 sym.value = 0;206 sym.value = 0;
207 sym.atom_ref = .{ .index = 0, .file = 0 };207 sym.atom_ref = .{ .index = 0, .file = 0 };
208 sym.flags.weak = false;208 sym.flags.weak = false;
209 sym.flags.weak_ref = nlist.weakRef();209 sym.flags.weak_ref = nlist.n_desc.weak_ref;
210 sym.flags.import = is_import;210 sym.flags.import = is_import;
211 sym.visibility = .global;211 sym.visibility = .global;
212212
...@@ -223,13 +223,13 @@ pub const File = union(enum) {...@@ -223,13 +223,13 @@ pub const File = union(enum) {
223 assert(file == .object or file == .zig_object);223 assert(file == .object or file == .zig_object);
224224
225 for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| {225 for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| {
226 if (!nlist.ext()) continue;226 if (!nlist.n_type.bits.ext) continue;
227 if (!nlist.undf()) continue;227 if (nlist.n_type.bits.type != .undf) continue;
228 if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;228 if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;
229229
230 sym.value = 0;230 sym.value = 0;
231 sym.atom_ref = .{ .index = 0, .file = 0 };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 sym.flags.import = true;233 sym.flags.import = true;
234 sym.visibility = .global;234 sym.visibility = .global;
235235
...@@ -247,7 +247,7 @@ pub const File = union(enum) {...@@ -247,7 +247,7 @@ pub const File = union(enum) {
247 for (file.getSymbols(), file.getNlists(), 0..) |sym, nlist, i| {247 for (file.getSymbols(), file.getNlists(), 0..) |sym, nlist, i| {
248 if (sym.visibility != .global) continue;248 if (sym.visibility != .global) continue;
249 if (sym.flags.weak) continue;249 if (sym.flags.weak) continue;
250 if (nlist.undf()) continue;250 if (nlist.n_type.bits.type == .undf) continue;
251 const ref = file.getSymbolRef(@intCast(i), macho_file);251 const ref = file.getSymbolRef(@intCast(i), macho_file);
252 const ref_file = ref.getFile(macho_file) orelse continue;252 const ref_file = ref.getFile(macho_file) orelse continue;
253 if (ref_file.getIndex() == file.getIndex()) continue;253 if (ref_file.getIndex() == file.getIndex()) continue;
src/main.zig+3-3
...@@ -43,7 +43,6 @@ const thread_stack_size = 60 << 20;...@@ -43,7 +43,6 @@ const thread_stack_size = 60 << 20;
43pub const std_options: std.Options = .{43pub const std_options: std.Options = .{
44 .wasiCwd = wasi_cwd,44 .wasiCwd = wasi_cwd,
45 .logFn = log,45 .logFn = log,
46 .enable_segfault_handler = false,
4746
48 .log_level = switch (builtin.mode) {47 .log_level = switch (builtin.mode) {
49 .Debug => .debug,48 .Debug => .debug,
...@@ -53,6 +52,7 @@ pub const std_options: std.Options = .{...@@ -53,6 +52,7 @@ pub const std_options: std.Options = .{
53};52};
5453
55pub const panic = crash_report.panic;54pub const panic = crash_report.panic;
55pub const debug = crash_report.debug;
5656
57var wasi_preopens: fs.wasi.Preopens = undefined;57var wasi_preopens: fs.wasi.Preopens = undefined;
58pub fn wasi_cwd() std.os.wasi.fd_t {58pub fn wasi_cwd() std.os.wasi.fd_t {
...@@ -165,8 +165,6 @@ var debug_allocator: std.heap.DebugAllocator(.{...@@ -165,8 +165,6 @@ var debug_allocator: std.heap.DebugAllocator(.{
165}) = .init;165}) = .init;
166166
167pub fn main() anyerror!void {167pub fn main() anyerror!void {
168 crash_report.initialize();
169
170 const gpa, const is_debug = gpa: {168 const gpa, const is_debug = gpa: {
171 if (build_options.debug_gpa) break :gpa .{ debug_allocator.allocator(), true };169 if (build_options.debug_gpa) break :gpa .{ debug_allocator.allocator(), true };
172 if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false };170 if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false };
...@@ -192,6 +190,8 @@ pub fn main() anyerror!void {...@@ -192,6 +190,8 @@ pub fn main() anyerror!void {
192190
193 const args = try process.argsAlloc(arena);191 const args = try process.argsAlloc(arena);
194192
193 if (args.len > 0) crash_report.zig_argv0 = args[0];
194
195 if (tracy.enable_allocation) {195 if (tracy.enable_allocation) {
196 var gpa_tracy = tracy.tracyAllocator(gpa);196 var gpa_tracy = tracy.tracyAllocator(gpa);
197 return mainArgs(gpa_tracy.allocator(), arena, args);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,7 +512,7 @@ pub fn defaultUnwindTables(target: *const std.Target, libunwind: bool, libtsan:
512 if (target.os.tag.isDarwin()) return .async;512 if (target.os.tag.isDarwin()) return .async;
513 if (libunwind) return .async;513 if (libunwind) return .async;
514 if (libtsan) return .async;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 return .none;516 return .none;
517}517}
518518
stage1/zig.h+8
...@@ -1510,8 +1510,16 @@ static inline zig_u128 zig_shl_u128(zig_u128 lhs, uint8_t rhs) {...@@ -1510,8 +1510,16 @@ static inline zig_u128 zig_shl_u128(zig_u128 lhs, uint8_t rhs) {
1510}1510}
15111511
1512static inline zig_i128 zig_shr_i128(zig_i128 lhs, uint8_t rhs) {1512static 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 zig_i128 sign_mask = lhs < zig_make_i128(0, 0) ? -zig_make_i128(0, 1) : zig_make_i128(0, 0);1520 zig_i128 sign_mask = lhs < zig_make_i128(0, 0) ? -zig_make_i128(0, 1) : zig_make_i128(0, 0);
1514 return ((lhs ^ sign_mask) >> rhs) ^ sign_mask;1521 return ((lhs ^ sign_mask) >> rhs) ^ sign_mask;
1522#endif
1515}1523}
15161524
1517static inline zig_i128 zig_shl_i128(zig_i128 lhs, uint8_t rhs) {1525static inline zig_i128 zig_shl_i128(zig_i128 lhs, uint8_t rhs) {
test/cases/disable_stack_tracing.zig created+28
...@@ -0,0 +1,28 @@
1pub const std_options: std.Options = .{
2 .allow_stack_tracing = false,
3};
4
5pub 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}
16fn 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
21const 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 @@
1pub 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 @@
1b: *std.Build,
2step: *Step,
3test_filters: []const []const u8,
4targets: []const std.Build.ResolvedTarget,
5optimize_modes: []const OptimizeMode,
6convert_exe: *std.Build.Step.Compile,
7
8pub 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
25pub 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
41fn 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
51fn 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
125const ErrorTrace = @This();
126const std = @import("std");
127const builtin = @import("builtin");
128const Step = std.Build.Step;
129const OptimizeMode = std.builtin.OptimizeMode;
130const mem = std.mem;
test/src/StackTrace.zig+159-56
...@@ -1,75 +1,171 @@...@@ -1,75 +1,171 @@
1b: *std.Build,1b: *std.Build,
2step: *Step,2step: *Step,
3test_index: usize,
4test_filters: []const []const u8,3test_filters: []const []const u8,
5optimize_modes: []const OptimizeMode,4targets: []const std.Build.ResolvedTarget,
6check_exe: *std.Build.Step.Compile,5convert_exe: *std.Build.Step.Compile,
76
8const Config = struct {7const Config = struct {
9 name: []const u8,8 name: []const u8,
10 source: []const u8,9 source: []const u8,
11 Debug: ?PerMode = null,10 /// Whether this test case expects to have unwind tables / frame pointers.
12 ReleaseSmall: ?PerMode = null,11 unwind: enum {
13 ReleaseSafe: ?PerMode = null,12 /// This case assumes that some unwind strategy, safe or unsafe, is available.
14 ReleaseFast: ?PerMode = null,13 any,
1514 /// This case assumes that no unwinding strategy is available.
16 const PerMode = struct {15 none,
17 expect: []const u8,16 /// This case assumes that a safe unwind strategy, like DWARF unwinding, is available.
18 exclude_arch: []const std.Target.Cpu.Arch = &.{},17 safe,
19 exclude_os: []const std.Target.Os.Tag = &.{},18 /// This case assumes that at most, unsafe FP unwinding is available.
20 error_tracing: ?bool = null,19 no_safe,
21 };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};
2328
24pub fn addCase(self: *StackTrace, config: Config) void {29pub fn addCase(self: *StackTrace, config: Config) void {
25 self.addCaseInner(config, true);30 for (self.targets) |*target| {
26 if (shouldTestNonLlvm(&self.b.graph.host.result)) {31 addCaseTarget(
27 self.addCaseInner(config, false);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}
3041fn addCaseTarget(
31fn addCaseInner(self: *StackTrace, config: Config, use_llvm: bool) void {42 self: *StackTrace,
32 if (config.Debug) |per_mode|43 config: Config,
33 self.addExpect(config.name, config.source, .Debug, use_llvm, per_mode);44 target: *const std.Build.ResolvedTarget,
3445 triple: ?[]const u8,
35 if (config.ReleaseSmall) |per_mode|46) void {
36 self.addExpect(config.name, config.source, .ReleaseSmall, use_llvm, per_mode);47 const both_backends = switch (target.result.cpu.arch) {
3748 .x86_64 => switch (target.result.ofmt) {
38 if (config.ReleaseFast) |per_mode|49 .elf => true,
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
45fn shouldTestNonLlvm(target: *const std.Target) bool {
46 return switch (target.cpu.arch) {
47 .x86_64 => switch (target.ofmt) {
48 .elf => !target.os.tag.isBSD(),
49 else => false,50 else => false,
50 },51 },
51 else => false,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}
54118
55fn addExpect(119fn addCaseInstance(
56 self: *StackTrace,120 self: *StackTrace,
121 target: *const std.Build.ResolvedTarget,
122 triple: ?[]const u8,
57 name: []const u8,123 name: []const u8,
58 source: []const u8,124 source: []const u8,
59 optimize_mode: OptimizeMode,
60 use_llvm: bool,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) void {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 const b = self.b;134 const b = self.b;
67 const annotated_case_name = b.fmt("check {s} ({s} {s})", .{135
68 name, @tagName(optimize_mode), if (use_llvm) "llvm" else "selfhosted",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| {164 if (self.test_filters.len > 0) {
71 if (mem.indexOf(u8, annotated_case_name, test_filter)) |_| break;165 for (self.test_filters) |test_filter| {
72 } else if (self.test_filters.len > 0) return;166 if (mem.indexOf(u8, annotated_case_name, test_filter)) |_| break;
167 } else return;
168 }
73169
74 const write_files = b.addWriteFiles();170 const write_files = b.addWriteFiles();
75 const source_zig = write_files.add("source.zig", source);171 const source_zig = write_files.add("source.zig", source);
...@@ -77,27 +173,34 @@ fn addExpect(...@@ -77,27 +173,34 @@ fn addExpect(
77 .name = "test",173 .name = "test",
78 .root_module = b.createModule(.{174 .root_module = b.createModule(.{
79 .root_source_file = source_zig,175 .root_source_file = source_zig,
80 .optimize = optimize_mode,176 .optimize = .Debug,
81 .target = b.graph.host,177 .target = target.*,
82 .error_tracing = mode_config.error_tracing,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 .use_llvm = use_llvm,184 .use_llvm = use_llvm,
85 });185 });
186 exe.pie = pie;
86 exe.bundle_ubsan_rt = false;187 exe.bundle_ubsan_rt = false;
87188
88 const run = b.addRunArtifact(exe);189 const run = b.addRunArtifact(exe);
89 run.removeEnvironmentVariable("CLICOLOR_FORCE");190 run.removeEnvironmentVariable("CLICOLOR_FORCE");
90 run.setEnvironmentVariable("NO_COLOR", "1");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 run.expectStdOutEqual("");197 run.expectStdOutEqual("");
93198
94 const check_run = b.addRunArtifact(self.check_exe);199 const check_run = b.addRunArtifact(self.convert_exe);
95 check_run.setName(annotated_case_name);200 check_run.setName(annotated_case_name);
96 check_run.addFileArg(run.captureStdErr(.{}));201 check_run.addFileArg(run.captureStdErr(.{}));
97 check_run.addArgs(&.{202 check_run.expectExitCode(0);
98 @tagName(optimize_mode),203 check_run.addCheck(.{ .expect_stdout_match = expect_stderr });
99 });
100 check_run.expectStdOutEqual(mode_config.expect);
101204
102 self.step.dependOn(&check_run.step);205 self.step.dependOn(&check_run.step);
103}206}
test/src/check-stack-trace.zig deleted-88
...@@ -1,88 +0,0 @@
1const builtin = @import("builtin");
2const std = @import("std");
3const mem = std.mem;
4const fs = std.fs;
5
6pub 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
27pub 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
107const std = @import("std");
test/stack_traces.zig+153-807
...@@ -1,878 +1,224 @@...@@ -1,878 +1,224 @@
1const std = @import("std");1pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {
2const os = std.os;
3const tests = @import("tests.zig");
4
5pub fn addCases(cases: *tests.StackTracesContext) void {
6 cases.addCase(.{2 cases.addCase(.{
7 .name = "return",3 .name = "simple panic",
8 .source =4 .source =
9 \\pub fn main() !void {5 \\pub fn main() void {
10 \\ return error.TheSkyIsFalling;6 \\ foo();
11 \\}7 \\}
12 ,8 \\fn foo() void {
13 .Debug = .{9 \\ @panic("oh no");
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;
55 \\}10 \\}
56 \\11 \\
57 \\pub fn main() !void {
58 \\ try foo();
59 \\}
60 ,12 ,
61 .Debug = .{13 .unwind = .any,
62 .expect =14 .expect_panic = true,
63 \\error: TheSkyIsFalling15 .expect =
64 \\source.zig:2:5: [address] in foo ([main_file])16 \\panic: oh no
65 \\ return error.TheSkyIsFalling;17 \\source.zig:5:5: [address] in foo
66 \\ ^18 \\ @panic("oh no");
67 \\source.zig:6:5: [address] in main ([main_file])19 \\ ^
68 \\ try foo();20 \\source.zig:2:8: [address] in main
69 \\ ^21 \\ foo();
70 \\22 \\ ^
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 \\}
108 \\23 \\
109 \\fn foo() !void {24 ,
110 \\ bar() catch {25 .expect_strip =
111 \\ return; // non-error result: success26 \\panic: oh no
112 \\ };27 \\???:?:?: [address] in source.foo
113 \\}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 });
15632
157 cases.addCase(.{33 cases.addCase(.{
158 .name = "continue in while loop",34 .name = "simple panic with no unwind strategy",
159 .source =35 .source =
160 \\fn foo() !void {36 \\pub fn main() void {
161 \\ return error.UhOh;37 \\ foo();
162 \\}38 \\}
163 \\39 \\fn foo() void {
164 \\pub fn main() !void {40 \\ @panic("oh no");
165 \\ var i: usize = 0;
166 \\ while (i < 3) : (i += 1) {
167 \\ foo() catch continue;
168 \\ }
169 \\ return error.UnrelatedError;
170 \\}41 \\}
42 \\
171 ,43 ,
172 .Debug = .{44 .unwind = .none,
173 .expect =45 .expect_panic = true,
174 \\error: UnrelatedError46 .expect = "panic: oh no",
175 \\source.zig:10:5: [address] in main ([main_file])47 .expect_strip = "panic: oh no",
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 },
207 });48 });
20849
209 cases.addCase(.{50 cases.addCase(.{
210 .name = "try return + handled catch/if-else",51 .name = "dump current trace",
211 .source =52 .source =
212 \\fn foo() !void {53 \\pub fn main() void {
213 \\ return error.TheSkyIsFalling;54 \\ foo(bar());
214 \\}55 \\}
215 \\56 \\fn bar() void {
216 \\pub fn main() !void {57 \\ qux(123);
217 \\ foo() catch {}; // should not affect error trace
218 \\ if (foo()) |_| {} else |_| {
219 \\ // should also not affect error trace
220 \\ }
221 \\ try foo();
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 = .{67 .unwind = .safe,
225 .expect =68 .expect_panic = false,
226 \\error: TheSkyIsFalling69 .expect =
227 \\source.zig:2:5: [address] in foo ([main_file])70 \\source.zig:9:36: [address] in qux
228 \\ return error.TheSkyIsFalling;71 \\ std.debug.dumpCurrentStackTrace(.{});
229 \\ ^72 \\ ^
230 \\source.zig:10:5: [address] in main ([main_file])73 \\source.zig:5:8: [address] in bar
231 \\ try foo();74 \\ qux(123);
232 \\ ^75 \\ ^
233 \\76 \\source.zig:2:12: [address] in main
234 ,77 \\ foo(bar());
235 },78 \\ ^
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; }
271 \\79 \\
272 \\pub fn main() !void {80 ,
273 \\ comptime var i: usize = 0;81 .expect_strip =
274 \\ b: inline while (i < 5) : (i += 1) {82 \\???:?:?: [address] in source.qux
275 \\ foo() catch {83 \\???:?:?: [address] in source.bar
276 \\ break :b; // non-error break, success84 \\???:?:?: [address] in source.main
277 \\ };
278 \\ }
279 \\ // foo() was successfully handled, should not appear in trace
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 });
32088
321 cases.addCase(.{89 cases.addCase(.{
322 .name = "catch and re-throw error",90 .name = "dump current trace with no unwind strategy",
323 .source =91 .source =
324 \\fn foo() !void {92 \\pub fn main() void {
325 \\ return error.TheSkyIsFalling;93 \\ foo(bar());
326 \\}94 \\}
327 \\95 \\fn bar() void {
328 \\pub fn main() !void {96 \\ qux(123);
329 \\ return foo() catch error.AndMyCarIsOutOfGas;
330 \\}97 \\}
331 ,98 \\fn foo(_: void) void {}
332 .Debug = .{99 \\fn qux(x: u32) void {
333 .expect =100 \\ std.debug.print("pre\n", .{});
334 \\error: AndMyCarIsOutOfGas101 \\ std.debug.dumpCurrentStackTrace(.{});
335 \\source.zig:2:5: [address] in foo ([main_file])102 \\ std.debug.print("post\n", .{});
336 \\ return error.TheSkyIsFalling;103 \\ _ = x;
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;
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 = .{108 .unwind = .no_safe,
392 .expect =109 .expect_panic = false,
393 \\error: SomethingUnrelatedWentWrong110 .expect = "pre\npost\n",
394 \\source.zig:11:5: [address] in main ([main_file])111 .expect_strip = "pre\npost\n",
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 },
426 });112 });
427113
428 cases.addCase(.{114 cases.addCase(.{
429 .name = "error stored in const has trace preserved for duration of block",115 .name = "dump captured trace",
430 .source =116 .source =
431 \\fn foo() !void { return error.TheSkyIsFalling; }117 \\pub fn main() void {
432 \\fn bar() !void { return error.InternalError; }118 \\ var stack_trace_buf: [8]usize = undefined;
433 \\fn baz() !void { return error.UnexpectedReality; }119 \\ dumpIt(&captureIt(&stack_trace_buf));
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;
454 \\}120 \\}
455 ,121 \\fn captureIt(buf: []usize) std.builtin.StackTrace {
456 .Debug = .{122 \\ return captureItInner(buf);
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;
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; }132 ,
516 \\fn foo() !void { return error.Foo; }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 {146 ,
519 \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());147 .expect_strip =
520 \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());148 \\???:?:?: [address] in source.captureItInner
521 \\ try expectError(error.Foo, foo());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 });
574154
575 cases.addCase(.{155 cases.addCase(.{
576 .name = "try return from within catch",156 .name = "dump captured trace with no unwind strategy",
577 .source =157 .source =
578 \\fn foo() !void {158 \\pub fn main() void {
579 \\ return error.TheSkyIsFalling;159 \\ var stack_trace_buf: [8]usize = undefined;
160 \\ dumpIt(&captureIt(&stack_trace_buf));
580 \\}161 \\}
581 \\162 \\fn captureIt(buf: []usize) std.builtin.StackTrace {
582 \\fn bar() !void {163 \\ return captureItInner(buf);
583 \\ return error.AndMyCarIsOutOfGas;
584 \\}164 \\}
585 \\165 \\fn dumpIt(st: *const std.builtin.StackTrace) void {
586 \\pub fn main() !void {166 \\ std.debug.dumpStackTrace(st);
587 \\ foo() catch { // error trace should include foo()
588 \\ try bar();
589 \\ };
590 \\}167 \\}
591 ,168 \\fn captureItInner(buf: []usize) std.builtin.StackTrace {
592 .Debug = .{169 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
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;
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 = .{174 .unwind = .no_safe,
658 .expect =175 .expect_panic = false,
659 \\error: AndMyCarIsOutOfGas176 .expect = "(empty stack trace)\n",
660 \\source.zig:2:5: [address] in foo ([main_file])177 .expect_strip = "(empty stack trace)\n",
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 },
703 });178 });
704179
705 cases.addCase(.{180 cases.addCase(.{
706 .name = "try try return return",181 .name = "dump captured trace on thread",
707 .source =182 .source =
708 \\fn foo() !void {183 \\pub fn main() !void {
709 \\ try bar();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 \\188 \\fn threadMain(stack_trace_buf: []usize) void {
712 \\fn bar() !void {189 \\ dumpIt(&captureIt(stack_trace_buf));
713 \\ return make_error();
714 \\}190 \\}
715 \\191 \\fn captureIt(buf: []usize) std.builtin.StackTrace {
716 \\fn make_error() !void {192 \\ return captureItInner(buf);
717 \\ return error.TheSkyIsFalling;
718 \\}193 \\}
719 \\194 \\fn dumpIt(st: *const std.builtin.StackTrace) void {
720 \\pub fn main() !void {195 \\ std.debug.dumpStackTrace(st);
721 \\ try foo();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 \\const std = @import("std");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 = .{203 .unwind = .safe,
795 // std.debug.sys_can_stack_trace204 .expect_panic = false,
796 .exclude_arch = &.{205 .expect =
797 .loongarch32,206 \\source.zig:16:46: [address] in captureItInner
798 .loongarch64,207 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
799 .mips,208 \\ ^
800 .mipsel,209 \\source.zig:10:26: [address] in captureIt
801 .mips64,210 \\ return captureItInner(buf);
802 .mips64el,211 \\ ^
803 .s390x,212 \\source.zig:7:22: [address] in threadMain
804 },213 \\ dumpIt(&captureIt(stack_trace_buf));
805 .exclude_os = &.{214 \\ ^
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 \\}
828 \\215 \\
829 \\noinline fn failure() error{ Fatal, NonFatal }!void {216 ,
830 \\ return error.NonFatal;217 .expect_strip =
831 \\}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,9 +1,10 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");
32
4/// This tests the path where DWARF information is embedded in a COFF binary3/// This tests the path where DWARF information is embedded in a COFF binary
5pub fn build(b: *std.Build) void {4pub 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 .aarch64,8 .aarch64,
8 .x86,9 .x86,
9 .x86_64,10 .x86_64,
...@@ -15,10 +16,10 @@ pub fn build(b: *std.Build) void {...@@ -15,10 +16,10 @@ pub fn build(b: *std.Build) void {
15 b.default_step = test_step;16 b.default_step = test_step;
1617
17 const optimize: std.builtin.OptimizeMode = .Debug;18 const optimize: std.builtin.OptimizeMode = .Debug;
18 const target = if (builtin.os.tag == .windows)19 const target = switch (host.result.os.tag) {
19 b.standardTargetOptions(.{})20 .windows => host,
20 else21 else => b.resolveTargetQuery(.{ .os_tag = .windows }),
21 b.resolveTargetQuery(.{ .os_tag = .windows });22 };
2223
23 const exe = b.addExecutable(.{24 const exe = b.addExecutable(.{
24 .name = "main",25 .name = "main",
test/standalone/coff_dwarf/main.zig+23-16
...@@ -1,27 +1,34 @@...@@ -1,27 +1,34 @@
1const std = @import("std");1const std = @import("std");
2const assert = std.debug.assert;2const fatal = std.process.fatal;
3const testing = std.testing;
43
5extern fn add(a: u32, b: u32, addr: *usize) u32;4extern fn add(a: u32, b: u32, addr: *usize) u32;
65
7pub fn main() !void {6pub fn main() void {
8 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;7 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;
9 defer assert(gpa.deinit() == .ok);8 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);
10 const allocator = gpa.allocator();9 const gpa = debug_alloc_inst.allocator();
1110
12 var debug_info = try std.debug.SelfInfo.open(allocator);11 var di: std.debug.SelfInfo = .init;
13 defer debug_info.deinit();12 defer di.deinit(gpa);
1413
15 var add_addr: usize = undefined;14 var add_addr: usize = undefined;
16 _ = add(1, 2, &add_addr);15 _ = add(1, 2, &add_addr);
1716
18 const module = try debug_info.getModuleForAddress(add_addr);17 const symbol = di.getSymbol(gpa, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err});
19 const symbol = try module.getSymbolAtAddress(allocator, add_addr);18 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
20 defer if (symbol.source_location) |sl| allocator.free(sl.file_name);
2119
22 try testing.expectEqualStrings("add", symbol.name);20 if (symbol.name == null) fatal("failed to resolve symbol name", .{});
23 try testing.expect(symbol.source_location != null);21 if (symbol.compile_unit_name == null) fatal("failed to resolve compile unit", .{});
24 try testing.expectEqualStrings("shared_lib.c", std.fs.path.basename(symbol.source_location.?.file_name));22 if (symbol.source_location == null) fatal("failed to resolve source location", .{});
25 try testing.expectEqual(@as(u64, 3), symbol.source_location.?.line);23
26 try testing.expectEqual(@as(u64, 0), symbol.source_location.?.column);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,27 +1,19 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const debug = std.debug;3const fatal = std.process.fatal;
4const testing = std.testing;
54
6noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void {5noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
7 expected[0] = @returnAddress();6 expected[0] = @returnAddress();
87 return std.debug.captureCurrentStackTrace(.{
9 var context: debug.ThreadContext = undefined;8 .first_address = @returnAddress(),
10 testing.expect(debug.getContext(&context)) catch @panic("failed to getContext");9 .allow_unsafe_unwind = true,
1110 }, addr_buf);
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 }
19}11}
2012
21noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {13noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
22 // Exercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored14 // Exercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored
23 if (builtin.target.ofmt != .c) {15 if (builtin.target.ofmt != .c) {
24 switch (builtin.cpu.arch) {16 switch (builtin.target.cpu.arch) {
25 .x86 => {17 .x86 => {
26 if (builtin.omit_frame_pointer) {18 if (builtin.omit_frame_pointer) {
27 asm volatile (19 asm volatile (
...@@ -67,10 +59,10 @@ noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {...@@ -67,10 +59,10 @@ noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {
67 }59 }
6860
69 expected[1] = @returnAddress();61 expected[1] = @returnAddress();
70 frame3(expected, unwound);62 return frame3(expected, addr_buf);
71}63}
7264
73noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void {65noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
74 expected[2] = @returnAddress();66 expected[2] = @returnAddress();
7567
76 // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding68 // 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,22 +70,32 @@ noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void {
78 var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined;70 var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined;
79 _ = std.mem.doNotOptimizeAway(&pad);71 _ = std.mem.doNotOptimizeAway(&pad);
8072
81 frame2(expected, unwound);73 return frame2(expected, addr_buf);
82}74}
8375
84noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void {76noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
85 expected[3] = @returnAddress();77 expected[3] = @returnAddress();
86 frame1(expected, unwound);78 return frame1(expected, addr_buf);
87}79}
8880
89pub fn main() !void {81pub fn main() void {
90 // Disabled until the DWARF unwinder bugs on .aarch64 are solved82 if (std.debug.cpu_context.Native == noreturn and builtin.omit_frame_pointer) {
91 if (builtin.omit_frame_pointer and comptime builtin.target.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) return;83 // Stack unwinding is impossible.
9284 return;
93 if (!std.debug.have_ucontext or !std.debug.have_getcontext) return;85 }
9486
95 var expected: [4]usize = undefined;87 var expected: [4]usize = undefined;
96 var unwound: [4]usize = undefined;88 var addr_buf: [4]usize = undefined;
97 frame0(&expected, &unwound);89 const trace = frame0(&expected, &addr_buf);
98 try testing.expectEqual(expected, unwound);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,26 +1,21 @@
1/// Test StackIterator on 'freestanding' target. Based on unwind.zig.1//! Test StackIterator on 'freestanding' target. Based on unwind.zig.
2
2const std = @import("std");3const std = @import("std");
3const builtin = @import("builtin");
4const debug = std.debug;
54
6noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void {5noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
7 expected[0] = @returnAddress();6 expected[0] = @returnAddress();
87 return std.debug.captureCurrentStackTrace(.{
9 var it = debug.StackIterator.init(@returnAddress(), @frameAddress());8 .first_address = @returnAddress(),
10 defer it.deinit();9 .allow_unsafe_unwind = true,
1110 }, addr_buf);
12 // Save StackIterator's frame addresses into `unwound`:
13 for (unwound) |*addr| {
14 if (it.next()) |return_address| addr.* = return_address;
15 }
16}11}
1712
18noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {13noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
19 expected[1] = @returnAddress();14 expected[1] = @returnAddress();
20 frame3(expected, unwound);15 return frame3(expected, addr_buf);
21}16}
2217
23noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void {18noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
24 expected[2] = @returnAddress();19 expected[2] = @returnAddress();
2520
26 // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding21 // 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,37 +23,44 @@ noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void {
28 var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined;23 var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined;
29 _ = std.mem.doNotOptimizeAway(&pad);24 _ = std.mem.doNotOptimizeAway(&pad);
3025
31 frame2(expected, unwound);26 return frame2(expected, addr_buf);
32}27}
3328
34noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void {29noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
35 expected[3] = @returnAddress();30 expected[3] = @returnAddress();
36 frame1(expected, unwound);31 return frame1(expected, addr_buf);
37}32}
3833
39// No-OS entrypoint34/// No-OS entrypoint
40export fn _start() callconv(.withStackAlign(.c, 1)) noreturn {35export fn _start() callconv(.withStackAlign(.c, 1)) noreturn {
41 var expected: [4]usize = undefined;36 var expected: [4]usize = undefined;
42 var unwound: [4]usize = undefined;37 var addr_buf: [4]usize = undefined;
43 frame0(&expected, &unwound);38 const trace = frame0(&expected, &addr_buf);
4439
45 // Verify result (no std.testing in freestanding)40 // Since we can't print from this freestanding test, we'll just use the exit
46 var missed: c_int = 0;41 // code to communicate error conditions.
47 for (expected, unwound) |expectFA, actualFA| {42
48 if (expectFA != actualFA) {43 // Unlike `unwind.zig`, we can expect *exactly* 4 frames, as we are the
49 missed += 1;44 // actual entry point and the frame pointer will be 0 on entry.
50 }45 if (trace.index != 4) exit(1);
51 }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}
5253
53 // Need to compile with the target OS as "freestanding" or "other" to54fn exit(code: u8) noreturn {
54 // exercise the StackIterator code, but when run as a regression test55 // We are intentionally compiling with the target OS being "freestanding" to
55 // need to actually exit. So assume we're running on x86_64-linux ...56 // exercise std.debug, but we still need to exit the process somehow; so do
57 // the appropriate x86_64-linux syscall.
56 asm volatile (58 asm volatile (
57 \\movl $60, %%eax59 \\movl $60, %%eax
58 \\syscall60 \\syscall
59 :61 :
60 : [missed] "{edi}" (missed),62 : [code] "{edi}" (code),
61 : .{ .edi = true, .eax = true });63 : .{ .edi = true, .eax = true });
6264
63 while (true) {} // unreached65 unreachable;
64}66}
test/standalone/windows_bat_args/fuzz.zig+40-35
...@@ -3,14 +3,14 @@ const builtin = @import("builtin");...@@ -3,14 +3,14 @@ const builtin = @import("builtin");
3const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
44
5pub fn main() anyerror!void {5pub fn main() anyerror!void {
6 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;6 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;
7 defer if (gpa.deinit() == .leak) @panic("found memory leaks");7 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);
8 const allocator = gpa.allocator();8 const gpa = debug_alloc_inst.allocator();
99
10 var it = try std.process.argsWithAllocator(allocator);10 var it = try std.process.argsWithAllocator(gpa);
11 defer it.deinit();11 defer it.deinit();
12 _ = it.next() orelse unreachable; // skip binary name12 _ = 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;
1414
15 const iterations: u64 = iterations: {15 const iterations: u64 = iterations: {
16 const arg = it.next() orelse "0";16 const arg = it.next() orelse "0";
...@@ -42,58 +42,63 @@ pub fn main() anyerror!void {...@@ -42,58 +42,63 @@ pub fn main() anyerror!void {
42 try tmp.dir.setAsCwd();42 try tmp.dir.setAsCwd();
43 defer tmp.parent_dir.setAsCwd() catch {};43 defer tmp.parent_dir.setAsCwd() catch {};
4444
45 var buf = try std.array_list.Managed(u8).initCapacity(allocator, 128);45 // `child_exe_path_orig` might be relative; make it relative to our new cwd.
46 defer buf.deinit();46 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });
47 try buf.appendSlice("@echo off\n");47 defer gpa.free(child_exe_path);
48 try buf.append('"');48
49 try buf.appendSlice(child_exe_path);49 var buf: std.ArrayList(u8) = .empty;
50 try buf.append('"');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 const preamble_len = buf.items.len;56 const preamble_len = buf.items.len;
5257
53 try buf.appendSlice(" %*");58 try buf.appendSlice(gpa, " %*");
54 try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items });59 try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items });
55 buf.shrinkRetainingCapacity(preamble_len);60 buf.shrinkRetainingCapacity(preamble_len);
5661
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 try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items });63 try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items });
59 buf.shrinkRetainingCapacity(preamble_len);64 buf.shrinkRetainingCapacity(preamble_len);
6065
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 try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items });67 try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items });
63 buf.shrinkRetainingCapacity(preamble_len);68 buf.shrinkRetainingCapacity(preamble_len);
6469
65 var i: u64 = 0;70 var i: u64 = 0;
66 while (iterations == 0 or i < iterations) {71 while (iterations == 0 or i < iterations) {
67 const rand_arg = try randomArg(allocator, rand);72 const rand_arg = try randomArg(gpa, rand);
68 defer allocator.free(rand_arg);73 defer gpa.free(rand_arg);
6974
70 try testExec(allocator, &.{rand_arg}, null);75 try testExec(gpa, &.{rand_arg}, null);
7176
72 i += 1;77 i += 1;
73 }78 }
74}79}
7580
76fn testExec(allocator: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {81fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {
77 try testExecBat(allocator, "args1.bat", args, env);82 try testExecBat(gpa, "args1.bat", args, env);
78 try testExecBat(allocator, "args2.bat", args, env);83 try testExecBat(gpa, "args2.bat", args, env);
79 try testExecBat(allocator, "args3.bat", args, env);84 try testExecBat(gpa, "args3.bat", args, env);
80}85}
8186
82fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {87fn testExecBat(gpa: 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);88 const argv = try gpa.alloc([]const u8, 1 + args.len);
84 defer argv.deinit();89 defer gpa.free(argv);
85 argv.appendAssumeCapacity(bat);90 argv[0] = bat;
86 argv.appendSliceAssumeCapacity(args);91 @memcpy(argv[1..], args);
8792
88 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");93 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
8994
90 const result = try std.process.Child.run(.{95 const result = try std.process.Child.run(.{
91 .allocator = allocator,96 .allocator = gpa,
92 .env_map = env,97 .env_map = env,
93 .argv = argv.items,98 .argv = argv,
94 });99 });
95 defer allocator.free(result.stdout);100 defer gpa.free(result.stdout);
96 defer allocator.free(result.stderr);101 defer gpa.free(result.stderr);
97102
98 try std.testing.expectEqualStrings("", result.stderr);103 try std.testing.expectEqualStrings("", result.stderr);
99 var it = std.mem.splitScalar(u8, result.stdout, '\x00');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,7 +114,7 @@ fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []co
109 }114 }
110}115}
111116
112fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {117fn randomArg(gpa: Allocator, rand: std.Random) ![]const u8 {
113 const Choice = enum {118 const Choice = enum {
114 backslash,119 backslash,
115 quote,120 quote,
...@@ -121,8 +126,8 @@ fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {...@@ -121,8 +126,8 @@ fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {
121 };126 };
122127
123 const choices = rand.uintAtMostBiased(u16, 256);128 const choices = rand.uintAtMostBiased(u16, 256);
124 var buf = try std.array_list.Managed(u8).initCapacity(allocator, choices);129 var buf: std.ArrayList(u8) = try .initCapacity(gpa, choices);
125 errdefer buf.deinit();130 errdefer buf.deinit(gpa);
126131
127 var last_codepoint: u21 = 0;132 var last_codepoint: u21 = 0;
128 for (0..choices) |_| {133 for (0..choices) |_| {
...@@ -149,12 +154,12 @@ fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {...@@ -149,12 +154,12 @@ fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {
149 continue;154 continue;
150 }155 }
151 }156 }
152 try buf.ensureUnusedCapacity(4);157 try buf.ensureUnusedCapacity(gpa, 4);
153 const unused_slice = buf.unusedCapacitySlice();158 const unused_slice = buf.unusedCapacitySlice();
154 const len = std.unicode.wtf8Encode(codepoint, unused_slice) catch unreachable;159 const len = std.unicode.wtf8Encode(codepoint, unused_slice) catch unreachable;
155 buf.items.len += len;160 buf.items.len += len;
156 last_codepoint = codepoint;161 last_codepoint = codepoint;
157 }162 }
158163
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,14 +1,14 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() anyerror!void {3pub fn main() anyerror!void {
4 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;4 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;
5 defer if (gpa.deinit() == .leak) @panic("found memory leaks");5 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);
6 const allocator = gpa.allocator();6 const gpa = debug_alloc_inst.allocator();
77
8 var it = try std.process.argsWithAllocator(allocator);8 var it = try std.process.argsWithAllocator(gpa);
9 defer it.deinit();9 defer it.deinit();
10 _ = it.next() orelse unreachable; // skip binary name10 _ = 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;
1212
13 var tmp = std.testing.tmpDir(.{});13 var tmp = std.testing.tmpDir(.{});
14 defer tmp.cleanup();14 defer tmp.cleanup();
...@@ -16,62 +16,67 @@ pub fn main() anyerror!void {...@@ -16,62 +16,67 @@ pub fn main() anyerror!void {
16 try tmp.dir.setAsCwd();16 try tmp.dir.setAsCwd();
17 defer tmp.parent_dir.setAsCwd() catch {};17 defer tmp.parent_dir.setAsCwd() catch {};
1818
19 var buf = try std.array_list.Managed(u8).initCapacity(allocator, 128);19 // `child_exe_path_orig` might be relative; make it relative to our new cwd.
20 defer buf.deinit();20 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });
21 try buf.appendSlice("@echo off\n");21 defer gpa.free(child_exe_path);
22 try buf.append('"');22
23 try buf.appendSlice(child_exe_path);23 var buf: std.ArrayList(u8) = .empty;
24 try buf.append('"');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 const preamble_len = buf.items.len;30 const preamble_len = buf.items.len;
2631
27 try buf.appendSlice(" %*");32 try buf.appendSlice(gpa, " %*");
28 try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items });33 try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items });
29 buf.shrinkRetainingCapacity(preamble_len);34 buf.shrinkRetainingCapacity(preamble_len);
3035
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 try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items });37 try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items });
33 buf.shrinkRetainingCapacity(preamble_len);38 buf.shrinkRetainingCapacity(preamble_len);
3439
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 try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items });41 try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items });
37 buf.shrinkRetainingCapacity(preamble_len);42 buf.shrinkRetainingCapacity(preamble_len);
3843
39 // Test cases are from https://github.com/rust-lang/rust/blob/master/tests/ui/std/windows-bat-args.rs44 // 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"});45 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\x00"});
41 try testExecError(error.InvalidBatchScriptArg, allocator, &.{"\n"});46 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\n"});
42 try testExecError(error.InvalidBatchScriptArg, allocator, &.{"\r"});47 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\r"});
43 try testExec(allocator, &.{ "a", "b" }, null);48 try testExec(gpa, &.{ "a", "b" }, null);
44 try testExec(allocator, &.{ "c is for cat", "d is for dog" }, null);49 try testExec(gpa, &.{ "c is for cat", "d is for dog" }, null);
45 try testExec(allocator, &.{ "\"", " \"" }, null);50 try testExec(gpa, &.{ "\"", " \"" }, null);
46 try testExec(allocator, &.{ "\\", "\\" }, null);51 try testExec(gpa, &.{ "\\", "\\" }, null);
47 try testExec(allocator, &.{">file.txt"}, null);52 try testExec(gpa, &.{">file.txt"}, null);
48 try testExec(allocator, &.{"whoami.exe"}, null);53 try testExec(gpa, &.{"whoami.exe"}, null);
49 try testExec(allocator, &.{"&a.exe"}, null);54 try testExec(gpa, &.{"&a.exe"}, null);
50 try testExec(allocator, &.{"&echo hello "}, null);55 try testExec(gpa, &.{"&echo hello "}, null);
51 try testExec(allocator, &.{ "&echo hello", "&whoami", ">file.txt" }, null);56 try testExec(gpa, &.{ "&echo hello", "&whoami", ">file.txt" }, null);
52 try testExec(allocator, &.{"!TMP!"}, null);57 try testExec(gpa, &.{"!TMP!"}, null);
53 try testExec(allocator, &.{"key=value"}, null);58 try testExec(gpa, &.{"key=value"}, null);
54 try testExec(allocator, &.{"\"key=value\""}, null);59 try testExec(gpa, &.{"\"key=value\""}, null);
55 try testExec(allocator, &.{"key = value"}, null);60 try testExec(gpa, &.{"key = value"}, null);
56 try testExec(allocator, &.{"key=[\"value\"]"}, null);61 try testExec(gpa, &.{"key=[\"value\"]"}, null);
57 try testExec(allocator, &.{ "", "a=b" }, null);62 try testExec(gpa, &.{ "", "a=b" }, null);
58 try testExec(allocator, &.{"key=\"foo bar\""}, null);63 try testExec(gpa, &.{"key=\"foo bar\""}, null);
59 try testExec(allocator, &.{"key=[\"my_value]"}, null);64 try testExec(gpa, &.{"key=[\"my_value]"}, null);
60 try testExec(allocator, &.{"key=[\"my_value\",\"other-value\"]"}, null);65 try testExec(gpa, &.{"key=[\"my_value\",\"other-value\"]"}, null);
61 try testExec(allocator, &.{"key\\=value"}, null);66 try testExec(gpa, &.{"key\\=value"}, null);
62 try testExec(allocator, &.{"key=\"&whoami\""}, null);67 try testExec(gpa, &.{"key=\"&whoami\""}, null);
63 try testExec(allocator, &.{"key=\"value\"=5"}, null);68 try testExec(gpa, &.{"key=\"value\"=5"}, null);
64 try testExec(allocator, &.{"key=[\">file.txt\"]"}, null);69 try testExec(gpa, &.{"key=[\">file.txt\"]"}, null);
65 try testExec(allocator, &.{"%hello"}, null);70 try testExec(gpa, &.{"%hello"}, null);
66 try testExec(allocator, &.{"%PATH%"}, null);71 try testExec(gpa, &.{"%PATH%"}, null);
67 try testExec(allocator, &.{"%%cd:~,%"}, null);72 try testExec(gpa, &.{"%%cd:~,%"}, null);
68 try testExec(allocator, &.{"%PATH%PATH%"}, null);73 try testExec(gpa, &.{"%PATH%PATH%"}, null);
69 try testExec(allocator, &.{"\">file.txt"}, null);74 try testExec(gpa, &.{"\">file.txt"}, null);
70 try testExec(allocator, &.{"abc\"&echo hello"}, null);75 try testExec(gpa, &.{"abc\"&echo hello"}, null);
71 try testExec(allocator, &.{"123\">file.txt"}, null);76 try testExec(gpa, &.{"123\">file.txt"}, null);
72 try testExec(allocator, &.{"\"&echo hello&whoami.exe"}, null);77 try testExec(gpa, &.{"\"&echo hello&whoami.exe"}, null);
73 try testExec(allocator, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null);78 try testExec(gpa, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null);
74 try testExec(allocator, &.{"&whoami.exe"}, null);79 try testExec(gpa, &.{"&whoami.exe"}, null);
7580
76 // Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution.81 // Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution.
77 // In many Windows APIs (including CreateProcess), trailing space and . characters are stripped82 // In many Windows APIs (including CreateProcess), trailing space and . characters are stripped
...@@ -89,17 +94,17 @@ pub fn main() anyerror!void {...@@ -89,17 +94,17 @@ pub fn main() anyerror!void {
89 // > "args1.bat .. "94 // > "args1.bat .. "
90 // '"args1.bat .. "' is not recognized as an internal or external command,95 // '"args1.bat .. "' is not recognized as an internal or external command,
91 // operable program or batch file.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 const absolute_with_trailing = blk: {98 const absolute_with_trailing = blk: {
94 const absolute_path = try std.fs.realpathAlloc(allocator, "args1.bat");99 const absolute_path = try std.fs.realpathAlloc(gpa, "args1.bat");
95 defer allocator.free(absolute_path);100 defer gpa.free(absolute_path);
96 break :blk try std.mem.concat(allocator, u8, &.{ absolute_path, " .. " });101 break :blk try std.mem.concat(gpa, u8, &.{ absolute_path, " .. " });
97 };102 };
98 defer allocator.free(absolute_with_trailing);103 defer gpa.free(absolute_with_trailing);
99 try std.testing.expectError(error.FileNotFound, testExecBat(allocator, absolute_with_trailing, &.{"abc"}, null));104 try std.testing.expectError(error.FileNotFound, testExecBat(gpa, absolute_with_trailing, &.{"abc"}, null));
100105
101 var env = env: {106 var env = env: {
102 var env = try std.process.getEnvMap(allocator);107 var env = try std.process.getEnvMap(gpa);
103 errdefer env.deinit();108 errdefer env.deinit();
104 // No escaping109 // No escaping
105 try env.put("FOO", "123");110 try env.put("FOO", "123");
...@@ -110,37 +115,37 @@ pub fn main() anyerror!void {...@@ -110,37 +115,37 @@ pub fn main() anyerror!void {
110 break :env env;115 break :env env;
111 };116 };
112 defer env.deinit();117 defer env.deinit();
113 try testExec(allocator, &.{"%FOO%"}, &env);118 try testExec(gpa, &.{"%FOO%"}, &env);
114119
115 // Ensure that none of the `>file.txt`s have caused file.txt to be created120 // Ensure that none of the `>file.txt`s have caused file.txt to be created
116 try std.testing.expectError(error.FileNotFound, tmp.dir.access("file.txt", .{}));121 try std.testing.expectError(error.FileNotFound, tmp.dir.access("file.txt", .{}));
117}122}
118123
119fn testExecError(err: anyerror, allocator: std.mem.Allocator, args: []const []const u8) !void {124fn testExecError(err: anyerror, gpa: std.mem.Allocator, args: []const []const u8) !void {
120 return std.testing.expectError(err, testExec(allocator, args, null));125 return std.testing.expectError(err, testExec(gpa, args, null));
121}126}
122127
123fn testExec(allocator: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {128fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {
124 try testExecBat(allocator, "args1.bat", args, env);129 try testExecBat(gpa, "args1.bat", args, env);
125 try testExecBat(allocator, "args2.bat", args, env);130 try testExecBat(gpa, "args2.bat", args, env);
126 try testExecBat(allocator, "args3.bat", args, env);131 try testExecBat(gpa, "args3.bat", args, env);
127}132}
128133
129fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {134fn testExecBat(gpa: 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);135 const argv = try gpa.alloc([]const u8, 1 + args.len);
131 defer argv.deinit();136 defer gpa.free(argv);
132 argv.appendAssumeCapacity(bat);137 argv[0] = bat;
133 argv.appendSliceAssumeCapacity(args);138 @memcpy(argv[1..], args);
134139
135 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");140 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
136141
137 const result = try std.process.Child.run(.{142 const result = try std.process.Child.run(.{
138 .allocator = allocator,143 .allocator = gpa,
139 .env_map = env,144 .env_map = env,
140 .argv = argv.items,145 .argv = argv,
141 });146 });
142 defer allocator.free(result.stdout);147 defer gpa.free(result.stdout);
143 defer allocator.free(result.stderr);148 defer gpa.free(result.stderr);
144149
145 try std.testing.expectEqualStrings("", result.stderr);150 try std.testing.expectEqualStrings("", result.stderr);
146 var it = std.mem.splitScalar(u8, result.stdout, '\x00');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,11 +6,13 @@ const OptimizeMode = std.builtin.OptimizeMode;
6const Step = std.Build.Step;6const Step = std.Build.Step;
77
8// Cases8// Cases
9const error_traces = @import("error_traces.zig");
9const stack_traces = @import("stack_traces.zig");10const stack_traces = @import("stack_traces.zig");
10const llvm_ir = @import("llvm_ir.zig");11const llvm_ir = @import("llvm_ir.zig");
11const libc = @import("libc.zig");12const libc = @import("libc.zig");
1213
13// Implementations14// Implementations
15pub const ErrorTracesContext = @import("src/ErrorTrace.zig");
14pub const StackTracesContext = @import("src/StackTrace.zig");16pub const StackTracesContext = @import("src/StackTrace.zig");
15pub const DebuggerContext = @import("src/Debugger.zig");17pub const DebuggerContext = @import("src/Debugger.zig");
16pub const LlvmIrContext = @import("src/LlvmIr.zig");18pub const LlvmIrContext = @import("src/LlvmIr.zig");
...@@ -1857,28 +1859,61 @@ const c_abi_targets = blk: {...@@ -1857,28 +1859,61 @@ const c_abi_targets = blk: {
1857 };1859 };
1858};1860};
18591861
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!
1865fn 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
1860pub fn addStackTraceTests(1895pub fn addStackTraceTests(
1861 b: *std.Build,1896 b: *std.Build,
1862 test_filters: []const []const u8,1897 test_filters: []const []const u8,
1863 optimize_modes: []const OptimizeMode,1898 skip_non_native: bool,
1864) *Step {1899) *Step {
1865 const check_exe = b.addExecutable(.{1900 const convert_exe = b.addExecutable(.{
1866 .name = "check-stack-trace",1901 .name = "convert-stack-trace",
1867 .root_module = b.createModule(.{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 .target = b.graph.host,1904 .target = b.graph.host,
1870 .optimize = .Debug,1905 .optimize = .Debug,
1871 }),1906 }),
1872 });1907 });
18731908
1874 const cases = b.allocator.create(StackTracesContext) catch @panic("OOM");1909 const cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
1910
1875 cases.* = .{1911 cases.* = .{
1876 .b = b,1912 .b = b,
1877 .step = b.step("test-stack-traces", "Run the stack trace tests"),1913 .step = b.step("test-stack-traces", "Run the stack trace tests"),
1878 .test_index = 0,
1879 .test_filters = test_filters,1914 .test_filters = test_filters,
1880 .optimize_modes = optimize_modes,1915 .targets = nativeAndCompatible32bit(b, skip_non_native),
1881 .check_exe = check_exe,1916 .convert_exe = convert_exe,
1882 };1917 };
18831918
1884 stack_traces.addCases(cases);1919 stack_traces.addCases(cases);
...@@ -1886,6 +1921,36 @@ pub fn addStackTraceTests(...@@ -1886,6 +1921,36 @@ pub fn addStackTraceTests(
1886 return cases.step;1921 return cases.step;
1887}1922}
18881923
1924pub 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
1889fn compilerHasPackageManager(b: *std.Build) bool {1954fn compilerHasPackageManager(b: *std.Build) bool {
1890 // We can only use dependencies if the compiler was built with support for package management.1955 // We can only use dependencies if the compiler was built with support for package management.
1891 // (zig2 doesn't support it, but we still need to construct a build graph to build stage3.)1956 // (zig2 doesn't support it, but we still need to construct a build graph to build stage3.)