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 {
563563 .skip_release = skip_release,
564564 }));
565565 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));
567568 test_step.dependOn(tests.addCliTests(b));
568569 if (tests.addDebuggerTests(b, .{
569570 .test_filters = test_filters,
lib/compiler/test_runner.zig+4-4
......@@ -140,7 +140,7 @@ fn mainServer() !void {
140140 else => {
141141 fail = true;
142142 if (@errorReturnTrace()) |trace| {
143 std.debug.dumpStackTrace(trace.*);
143 std.debug.dumpStackTrace(trace);
144144 }
145145 },
146146 };
......@@ -182,7 +182,7 @@ fn mainServer() !void {
182182 error.SkipZigTest => return,
183183 else => {
184184 if (@errorReturnTrace()) |trace| {
185 std.debug.dumpStackTrace(trace.*);
185 std.debug.dumpStackTrace(trace);
186186 }
187187 std.debug.print("failed with error.{t}\n", .{err});
188188 std.process.exit(1);
......@@ -261,7 +261,7 @@ fn mainTerminal() void {
261261 std.debug.print("FAIL ({t})\n", .{err});
262262 }
263263 if (@errorReturnTrace()) |trace| {
264 std.debug.dumpStackTrace(trace.*);
264 std.debug.dumpStackTrace(trace);
265265 }
266266 test_node.end();
267267 },
......@@ -398,7 +398,7 @@ pub fn fuzz(
398398 error.SkipZigTest => return,
399399 else => {
400400 std.debug.lockStdErr();
401 if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace.*);
401 if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace);
402402 std.debug.print("failed with error.{t}\n", .{err});
403403 std.process.exit(1);
404404 },
lib/std/Build.zig+1-1
......@@ -2195,7 +2195,7 @@ fn dependencyInner(
21952195 sub_builder.runBuild(bz) catch @panic("unhandled error");
21962196
21972197 if (sub_builder.validateUserInputDidItFail()) {
2198 std.debug.dumpCurrentStackTrace(@returnAddress());
2198 std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
21992199 }
22002200 }
22012201
lib/std/Build/Step.zig+5-33
......@@ -60,7 +60,7 @@ test_results: TestResults,
6060
6161/// The return address associated with creation of this step that can be useful
6262/// to print along with debugging messages.
63debug_stack_trace: []usize,
63debug_stack_trace: std.builtin.StackTrace,
6464
6565pub const TestResults = struct {
6666 fail_count: u32 = 0,
......@@ -220,16 +220,9 @@ pub fn init(options: StepOptions) Step {
220220 .state = .precheck_unstarted,
221221 .max_rss = options.max_rss,
222222 .debug_stack_trace = blk: {
223 if (!std.debug.sys_can_stack_trace) break :blk &.{};
224 const addresses = arena.alloc(usize, options.owner.debug_stack_frames_count) catch @panic("OOM");
225 @memset(addresses, 0);
223 const addr_buf = arena.alloc(usize, options.owner.debug_stack_frames_count) catch @panic("OOM");
226224 const first_ret_addr = options.first_ret_addr orelse @returnAddress();
227 var stack_trace = std.builtin.StackTrace{
228 .instruction_addresses = addresses,
229 .index = 0,
230 };
231 std.debug.captureStackTrace(first_ret_addr, &stack_trace);
232 break :blk addresses;
225 break :blk std.debug.captureCurrentStackTrace(.{ .first_address = first_ret_addr }, addr_buf);
233226 },
234227 .result_error_msgs = .{},
235228 .result_error_bundle = std.zig.ErrorBundle.empty,
......@@ -282,18 +275,6 @@ pub fn dependOn(step: *Step, other: *Step) void {
282275 step.dependencies.append(other) catch @panic("OOM");
283276}
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
297278fn makeNoOp(step: *Step, options: MakeOptions) anyerror!void {
298279 _ = options;
299280
......@@ -315,18 +296,9 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
315296
316297/// For debugging purposes, prints identifying information about this Step.
317298pub fn dump(step: *Step, w: *std.Io.Writer, tty_config: std.Io.tty.Config) void {
318 const debug_info = std.debug.getSelfDebugInfo() catch |err| {
319 w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{
320 @errorName(err),
321 }) catch {};
322 return;
323 };
324 if (step.getStackTrace()) |stack_trace| {
299 if (step.debug_stack_trace.instruction_addresses.len > 0) {
325300 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
326 std.debug.writeStackTrace(stack_trace, w, debug_info, tty_config) catch |err| {
327 w.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch {};
328 return;
329 };
301 std.debug.writeStackTrace(&step.debug_stack_trace, w, tty_config) catch {};
330302 } else {
331303 const field = "debug_stack_frames_count";
332304 comptime assert(@hasField(Build, field));
lib/std/Build/Step/CheckObject.zig+16-22
......@@ -1097,16 +1097,10 @@ const MachODumper = struct {
10971097
10981098 for (ctx.symtab.items) |sym| {
10991099 const sym_name = ctx.getString(sym.n_strx);
1100 if (sym.stab()) {
1101 const tt = switch (sym.n_type) {
1102 macho.N_SO => "SO",
1103 macho.N_OSO => "OSO",
1104 macho.N_BNSYM => "BNSYM",
1105 macho.N_ENSYM => "ENSYM",
1106 macho.N_FUN => "FUN",
1107 macho.N_GSYM => "GSYM",
1108 macho.N_STSYM => "STSYM",
1109 else => "UNKNOWN STAB",
1100 if (sym.n_type.bits.is_stab != 0) {
1101 const tt = switch (sym.n_type.stab) {
1102 _ => "UNKNOWN STAB",
1103 else => @tagName(sym.n_type.stab),
11101104 };
11111105 try writer.print("{x}", .{sym.n_value});
11121106 if (sym.n_sect > 0) {
......@@ -1114,27 +1108,27 @@ const MachODumper = struct {
11141108 try writer.print(" ({s},{s})", .{ sect.segName(), sect.sectName() });
11151109 }
11161110 try writer.print(" {s} (stab) {s}\n", .{ tt, sym_name });
1117 } else if (sym.sect()) {
1111 } else if (sym.n_type.bits.type == .sect) {
11181112 const sect = ctx.sections.items[sym.n_sect - 1];
11191113 try writer.print("{x} ({s},{s})", .{
11201114 sym.n_value,
11211115 sect.segName(),
11221116 sect.sectName(),
11231117 });
1124 if (sym.n_desc & macho.REFERENCED_DYNAMICALLY != 0) try writer.writeAll(" [referenced dynamically]");
1125 if (sym.weakDef()) try writer.writeAll(" weak");
1126 if (sym.weakRef()) try writer.writeAll(" weakref");
1127 if (sym.ext()) {
1128 if (sym.pext()) try writer.writeAll(" private");
1118 if (sym.n_desc.referenced_dynamically) try writer.writeAll(" [referenced dynamically]");
1119 if (sym.n_desc.weak_def_or_ref_to_weak) try writer.writeAll(" weak");
1120 if (sym.n_desc.weak_ref) try writer.writeAll(" weakref");
1121 if (sym.n_type.bits.ext) {
1122 if (sym.n_type.bits.pext) try writer.writeAll(" private");
11291123 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)");
11311125 try writer.print(" {s}\n", .{sym_name});
11321126 } else if (sym.tentative()) {
1133 const alignment = (sym.n_desc >> 8) & 0x0F;
1127 const alignment = (@as(u16, @bitCast(sym.n_desc)) >> 8) & 0x0F;
11341128 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");
11361130 try writer.print(" {s}\n", .{sym_name});
1137 } else if (sym.undf()) {
1131 } else if (sym.n_type.bits.type == .undf) {
11381132 const ordinal = @divFloor(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER);
11391133 const import_name = blk: {
11401134 if (ordinal <= 0) {
......@@ -1153,8 +1147,8 @@ const MachODumper = struct {
11531147 break :blk basename[0..ext];
11541148 };
11551149 try writer.writeAll("(undefined)");
1156 if (sym.weakRef()) try writer.writeAll(" weakref");
1157 if (sym.ext()) try writer.writeAll(" external");
1150 if (sym.n_desc.weak_ref) try writer.writeAll(" weakref");
1151 if (sym.n_type.bits.ext) try writer.writeAll(" external");
11581152 try writer.print(" {s} (from {s})\n", .{
11591153 sym_name,
11601154 import_name,
lib/std/Thread.zig+1-1
......@@ -530,7 +530,7 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
530530 @call(.auto, f, args) catch |err| {
531531 std.debug.print("error: {s}\n", .{@errorName(err)});
532532 if (@errorReturnTrace()) |trace| {
533 std.debug.dumpStackTrace(trace.*);
533 std.debug.dumpStackTrace(trace);
534534 }
535535 };
536536
lib/std/builtin.zig+5-8
......@@ -38,20 +38,17 @@ pub const StackTrace = struct {
3838 index: usize,
3939 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 {
4242 // TODO: re-evaluate whether to use format() methods at all.
4343 // Until then, avoid an error when using GeneralPurposeAllocator with WebAssembly
4444 // where it tries to call detectTTYConfig here.
4545 if (builtin.os.tag == .freestanding) return;
4646
47 const debug_info = std.debug.getSelfDebugInfo() catch |err| {
48 return writer.print("\nUnable to print stack trace: Unable to open debug info: {s}\n", .{@errorName(err)});
49 };
50 const tty_config = std.Io.tty.detectConfig(std.fs.File.stderr());
47 // TODO: why on earth are we using stderr's ttyconfig?
48 // If we want colored output, we should just make a formatter out of `writeStackTrace`.
49 const tty_config = std.Io.tty.detectConfig(.stderr());
5150 try writer.writeAll("\n");
52 std.debug.writeStackTrace(self, writer, debug_info, tty_config) catch |err| {
53 try writer.print("Unable to print stack trace: {s}\n", .{@errorName(err)});
54 };
51 try std.debug.writeStackTrace(st, writer, tty_config);
5552 }
5653};
5754
lib/std/c.zig+3-207
......@@ -7035,205 +7035,6 @@ pub const timezone = switch (native_os) {
70357035 else => void,
70367036};
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
72377038pub const user_desc = switch (native_os) {
72387039 .linux => linux.user_desc,
72397040 else => void,
......@@ -11193,6 +10994,9 @@ pub extern "c" fn dlclose(handle: *anyopaque) c_int;
1119310994pub extern "c" fn dlsym(handle: ?*anyopaque, symbol: [*:0]const u8) ?*anyopaque;
1119410995pub 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
1119611000pub extern "c" fn sync() void;
1119711001pub extern "c" fn syncfs(fd: c_int) c_int;
1119811002pub extern "c" fn fsync(fd: c_int) c_int;
......@@ -11238,13 +11042,6 @@ pub const LC = enum(c_int) {
1123811042
1123911043pub 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
1124811045pub const max_align_t = if (native_abi == .msvc or native_abi == .itanium)
1124911046 f64
1125011047else if (native_os.isDarwin())
......@@ -11668,7 +11465,6 @@ const private = struct {
1166811465 extern "c" fn shm_open(name: [*:0]const u8, flag: c_int, mode: mode_t) c_int;
1166911466
1167011467 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
1167311469 extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize;
1167411470 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 {
348348
349349pub 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
452351pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32;
453352pub extern "c" fn _NSGetExecutablePath(buf: [*:0]u8, bufsize: *u32) c_int;
454353pub extern "c" fn _dyld_image_count() u32;
455354pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header;
456355pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize;
457356pub 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
459366pub const COPYFILE = packed struct(u32) {
460367 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)
1313pub extern "c" fn umtx_sleep(ptr: *const volatile c_int, value: c_int, timeout: c_int) c_int;
1414pub 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
5616pub const E = enum(u16) {
5717 /// No error occurred.
5818 SUCCESS = 0,
lib/std/c/haiku.zig-263
......@@ -273,269 +273,6 @@ pub const E = enum(i32) {
273273
274274pub 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
539276pub const DirEnt = extern struct {
540277 /// device
541278 dev: dev_t,
lib/std/c/openbsd.zig-47
......@@ -144,53 +144,6 @@ pub const TCIO = enum(u32) {
144144 ION = 4,
145145};
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
194147pub const E = enum(u16) {
195148 /// No error occurred.
196149 SUCCESS = 0,
lib/std/coff.zig+10-9
......@@ -1083,26 +1083,27 @@ pub const Coff = struct {
10831083 age: u32 = undefined,
10841084
10851085 // 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 {
10871087 const pe_pointer_offset = 0x3C;
10881088 const pe_magic = "PE\x00\x00";
10891089
1090 var reader: std.Io.Reader = .fixed(data);
1091 reader.seek = pe_pointer_offset;
1092 const coff_header_offset = try reader.takeInt(u32, .little);
1093 reader.seek = coff_header_offset;
1094 const is_image = mem.eql(u8, pe_magic, try reader.takeArray(4));
1090 if (data.len < pe_pointer_offset + 4) return error.EndOfStream;
1091 const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little);
1092 if (data.len < header_offset + 4) return error.EndOfStream;
1093 const is_image = mem.eql(u8, data[header_offset..][0..4], pe_magic);
10951094
1096 var coff = @This(){
1095 const coff: Coff = .{
10971096 .data = data,
10981097 .is_image = is_image,
10991098 .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 },
11011103 };
11021104
11031105 // Do some basic validation upfront
11041106 if (is_image) {
1105 coff.coff_header_offset = coff.coff_header_offset + 4;
11061107 const coff_header = coff.getCoffHeader();
11071108 if (coff_header.size_of_optional_header == 0) return error.MissingPEHeader;
11081109 }
lib/std/debug.zig+662-799
......@@ -1,24 +1,102 @@
1const builtin = @import("builtin");
21const std = @import("std.zig");
32const math = std.math;
43const mem = std.mem;
54const posix = std.posix;
65const fs = std.fs;
76const testing = std.testing;
8const root = @import("root");
7const Allocator = mem.Allocator;
98const File = std.fs.File;
109const windows = std.os.windows;
11const native_arch = builtin.cpu.arch;
12const native_os = builtin.os.tag;
13const native_endian = native_arch.endian();
1410const Writer = std.Io.Writer;
1511const 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
1719pub const Dwarf = @import("debug/Dwarf.zig");
1820pub const Pdb = @import("debug/Pdb.zig");
19pub const SelfInfo = @import("debug/SelfInfo.zig");
21pub const ElfFile = @import("debug/ElfFile.zig");
2022pub const Info = @import("debug/Info.zig");
2123pub 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
23101pub const simple_panic = @import("debug/simple_panic.zig");
24102pub const no_panic = @import("debug/no_panic.zig");
......@@ -153,9 +231,14 @@ pub const SourceLocation = struct {
153231};
154232
155233pub const Symbol = struct {
156 name: []const u8 = "???",
157 compile_unit_name: []const u8 = "???",
158 source_location: ?SourceLocation = null,
234 name: ?[]const u8,
235 compile_unit_name: ?[]const u8,
236 source_location: ?SourceLocation,
237 pub const unknown: Symbol = .{
238 .name = null,
239 .compile_unit_name = null,
240 .source_location = null,
241 };
159242};
160243
161244/// Deprecated because it returns the optimization mode of the standard
......@@ -166,18 +249,12 @@ pub const runtime_safety = switch (builtin.mode) {
166249 .ReleaseFast, .ReleaseSmall => false,
167250};
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.
169257pub 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
181258 // `@returnAddress()` in LLVM 10 gives
182259 // "Non-Emscripten WebAssembly hasn't implemented __builtin_return_address".
183260 // On Emscripten, Zig only supports `@returnAddress()` in debug builds
......@@ -186,7 +263,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
186263 .wasm64,
187264 => native_os == .emscripten and builtin.mode == .Debug,
188265
189 // `@returnAddress()` is unsupported in LLVM 13.
266 // `@returnAddress()` is unsupported in LLVM 21.
190267 .bpfel,
191268 .bpfeb,
192269 => false,
......@@ -209,8 +286,12 @@ pub fn unlockStdErr() void {
209286///
210287/// During the lock, any `std.Progress` information is cleared from the terminal.
211288///
212/// Returns a `Writer` with empty buffer, meaning that it is
213/// in fact unbuffered and does not need to be flushed.
289/// The lock is recursive, so it is valid for the same thread to call `lockStderrWriter` multiple
290/// times. The primary motivation is that this allows the panic handler to safely dump the stack
291/// trace and panic message even if the mutex was held at the panic site.
292///
293/// The returned `Writer` does not need to be manually flushed: flushing is performed automatically
294/// when the matching `unlockStderrWriter` call occurs.
214295pub fn lockStderrWriter(buffer: []u8) *Writer {
215296 return std.Progress.lockStderrWriter(buffer);
216297}
......@@ -231,16 +312,13 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
231312 nosuspend bw.print(fmt, args) catch return;
232313}
233314
234/// TODO multithreaded awareness
235var self_debug_info: ?SelfInfo = null;
236
237pub fn getSelfDebugInfo() !*SelfInfo {
238 if (self_debug_info) |*info| {
239 return info;
240 } else {
241 self_debug_info = try SelfInfo.open(getDebugInfoAllocator());
242 return &self_debug_info.?;
243 }
315/// Marked `inline` to propagate a comptime-known error to callers.
316pub inline fn getSelfDebugInfo() !*SelfInfo {
317 if (SelfInfo == void) return error.UnsupportedTarget;
318 const S = struct {
319 var self_info: SelfInfo = .init;
320 };
321 return &S.self_info;
244322}
245323
246324/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
......@@ -321,226 +399,8 @@ test dumpHexFallible {
321399 try std.testing.expectEqualStrings(expected, aw.written());
322400}
323401
324/// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned.
325pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
326 const stderr = lockStderrWriter(&.{});
327 defer unlockStderrWriter();
328 nosuspend dumpCurrentStackTraceToWriter(start_addr, stderr) catch return;
329}
330
331/// Prints the current stack trace to the provided writer.
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}
402/// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic.
403pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *const cpu_context.Native;
544404
545405/// Invokes detectable illegal behavior when `ok` is `false`.
546406///
......@@ -579,8 +439,8 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn {
579439 panicExtra(@returnAddress(), format, args);
580440}
581441
582/// Equivalent to `@panic` but with a formatted message, and with an explicitly
583/// provided return address.
442/// Equivalent to `@panic` but with a formatted message and an explicitly provided return address
443/// which will be the first address in the stack trace.
584444pub fn panicExtra(
585445 ret_addr: ?usize,
586446 comptime format: []const u8,
......@@ -610,6 +470,24 @@ var panicking = std.atomic.Value(u8).init(0);
610470/// This is used to catch and handle panics triggered by the panic handler.
611471threadlocal 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
613491/// Dumps a stack trace to standard error, then aborts.
614492pub fn defaultPanic(
615493 msg: []const u8,
......@@ -617,8 +495,8 @@ pub fn defaultPanic(
617495) noreturn {
618496 @branchHint(.cold);
619497
620 // For backends that cannot handle the language features depended on by the
621 // default panic handler, we have a simpler panic handler:
498 if (use_trap_panic) @trap();
499
622500 switch (builtin.zig_backend) {
623501 .stage2_aarch64,
624502 .stage2_arm,
......@@ -683,41 +561,48 @@ pub fn defaultPanic(
683561 resetSegfaultHandler();
684562 }
685563
686 // Note there is similar logic in handleSegfaultPosix and handleSegfaultWindowsExtra.
687 nosuspend switch (panic_stage) {
564 // There is very similar logic to the following in `handleSegfault`.
565 switch (panic_stage) {
688566 0 => {
689567 panic_stage = 1;
690
691568 _ = panicking.fetchAdd(1, .seq_cst);
692569
693 {
570 trace: {
571 const tty_config = tty.detectConfig(.stderr());
572
694573 const stderr = lockStderrWriter(&.{});
695574 defer unlockStderrWriter();
696575
697576 if (builtin.single_threaded) {
698 stderr.print("panic: ", .{}) catch posix.abort();
577 stderr.print("panic: ", .{}) catch break :trace;
699578 } else {
700579 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;
702581 }
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.*);
706 dumpCurrentStackTraceToWriter(first_trace_addr orelse @returnAddress(), stderr) catch {};
584 if (@errorReturnTrace()) |t| if (t.index > 0) {
585 stderr.writeAll("error return context:\n") catch break :trace;
586 writeStackTrace(t, stderr, tty_config) catch break :trace;
587 stderr.writeAll("\nstack trace:\n") catch break :trace;
588 };
589 writeCurrentStackTrace(.{
590 .first_address = first_trace_addr orelse @returnAddress(),
591 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
592 }, stderr, tty_config) catch break :trace;
707593 }
708594
709595 waitForOtherThreadToFinishPanicking();
710596 },
711597 1 => {
712598 panic_stage = 2;
713
714599 // A panic happened while trying to print a previous panic message.
715600 // We're still holding the mutex but that's fine as we're going to
716601 // call abort().
717602 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};
718603 },
719604 else => {}, // Panicked while printing the recursive panic message.
720 };
605 }
721606
722607 posix.abort();
723608}
......@@ -736,391 +621,417 @@ fn waitForOtherThreadToFinishPanicking() void {
736621 }
737622}
738623
739pub fn writeStackTrace(
740 stack_trace: std.builtin.StackTrace,
741 writer: *Writer,
742 debug_info: *SelfInfo,
743 tty_config: tty.Config,
744) !void {
745 if (builtin.strip_debug_info) return error.MissingDebugInfo;
746 var frame_index: usize = 0;
747 var frames_left: usize = @min(stack_trace.index, stack_trace.instruction_addresses.len);
748
749 while (frames_left != 0) : ({
750 frames_left -= 1;
751 frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len;
752 }) {
753 const return_address = stack_trace.instruction_addresses[frame_index];
754 try printSourceAtAddress(debug_info, writer, return_address - 1, tty_config);
755 }
624pub const StackUnwindOptions = struct {
625 /// If not `null`, we will ignore all frames up until this return address. This is typically
626 /// used to omit intermediate handling code (for instance, a panic handler and its machinery)
627 /// from stack traces.
628 first_address: ?usize = null,
629 /// If not `null`, we will unwind from this `cpu_context.Native` instead of the current top of
630 /// the stack. The main use case here is printing stack traces from signal handlers, where the
631 /// kernel provides a `*const cpu_context.Native` of the state before the signal.
632 context: ?CpuContextPtr = null,
633 /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort.
634 /// If `false`, only known-safe mechanisms will be attempted.
635 allow_unsafe_unwind: bool = false,
636};
756637
757 if (stack_trace.index > stack_trace.instruction_addresses.len) {
758 const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len;
638/// Capture and return the current stack trace. The returned `StackTrace` stores its addresses in
639/// the given buffer, so `addr_buf` must have a lifetime at least equal to the `StackTrace`.
640///
641/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
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) {
760813 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});
762815 tty_config.setColor(writer, .reset) catch {};
763816 }
764817}
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)
767 @typeInfo(@typeInfo(@TypeOf(StackIterator.next_unwind)).@"fn".return_type.?).error_union.error_set
768else
769 void;
770
771pub const StackIterator = struct {
772 // Skip every frame before this address is found.
773 first_address: ?usize,
774 // Last known value of the frame pointer register.
828const StackIterator = union(enum) {
829 /// Unwinding using debug info (e.g. DWARF CFI).
830 di: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn,
831 /// We will first report the *current* PC of this `UnwindContext`, then we will switch to `di`.
832 di_first: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn,
833 /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable.
775834 fp: usize,
776835
777 // When SelfInfo and a register context is available, this iterator can unwind
778 // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer),
779 // using DWARF and MachO unwind info.
780 unwind_state: if (have_ucontext) ?struct {
781 debug_info: *SelfInfo,
782 dwarf_context: SelfInfo.UnwindContext,
783 last_error: ?UnwindError = null,
784 failed: bool = false,
785 } else void = if (have_ucontext) null else {},
786
787 pub fn init(first_address: ?usize, fp: usize) StackIterator {
788 if (native_arch.isSPARC()) {
836 /// It is important that this function is marked `inline` so that it can safely use
837 /// `@frameAddress` and `cpu_context.Native.current` as the caller's stack frame and
838 /// our own are one and the same.
839 inline fn init(opt_context_ptr: ?CpuContextPtr) error{CannotUnwindFromContext}!StackIterator {
840 if (builtin.cpu.arch.isSPARC()) {
789841 // Flush all the register windows on stack.
790 asm volatile (if (builtin.cpu.has(.sparc, .v9))
791 "flushw"
792 else
793 "ta 3" // ST_FLUSH_WINDOWS
794 ::: .{ .memory = true });
795 }
796
797 return .{
798 .first_address = first_address,
799 .fp = fp,
800 };
801 }
802
803 pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t, fp: usize) !StackIterator {
804 // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that
805 // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder.
806 if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64)
807 return init(first_address, @truncate(context.mcontext.ss.fp));
808
809 if (SelfInfo.supports_unwinding) {
810 var iterator = init(first_address, fp);
811 iterator.unwind_state = .{
812 .debug_info = debug_info,
813 .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context),
814 };
815 return iterator;
816 }
817
818 return init(first_address, fp);
819 }
820
821 pub fn deinit(it: *StackIterator) void {
822 if (have_ucontext and it.unwind_state != null) it.unwind_state.?.dwarf_context.deinit();
823 }
824
825 pub fn getLastError(it: *StackIterator) ?struct {
826 err: UnwindError,
827 address: usize,
828 } {
829 if (!have_ucontext) return null;
830 if (it.unwind_state) |*unwind_state| {
831 if (unwind_state.last_error) |err| {
832 unwind_state.last_error = null;
833 return .{
834 .err = err,
835 .address = unwind_state.dwarf_context.pc,
836 };
842 if (builtin.cpu.has(.sparc, .v9)) {
843 asm volatile ("flushw" ::: .{ .memory = true });
844 } else {
845 asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS
837846 }
838847 }
839
840 return null;
841 }
842
843 // Offset of the saved BP wrt the frame pointer.
844 const fp_offset = if (native_arch.isRISCV())
845 // On RISC-V the frame pointer points to the top of the saved register
846 // area, on pretty much every other architecture it points to the stack
847 // slot where the previous frame pointer is saved.
848 2 * @sizeOf(usize)
849 else if (native_arch.isSPARC())
850 // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS.
851 14 * @sizeOf(usize)
852 else
853 0;
854
855 const fp_bias = if (native_arch.isSPARC())
856 // On SPARC frame pointers are biased by a constant.
857 2047
858 else
859 0;
860
861 // Positive offset of the saved PC wrt the frame pointer.
862 const pc_offset = if (native_arch == .powerpc64le)
863 2 * @sizeOf(usize)
864 else
865 @sizeOf(usize);
866
867 pub fn next(it: *StackIterator) ?usize {
868 var address = it.next_internal() orelse return null;
869
870 if (it.first_address) |first_address| {
871 while (address != first_address) {
872 address = it.next_internal() orelse return null;
873 }
874 it.first_address = null;
848 if (opt_context_ptr) |context_ptr| {
849 if (SelfInfo == void or !SelfInfo.can_unwind) return error.CannotUnwindFromContext;
850 // Use `di_first` here so we report the PC in the context before unwinding any further.
851 return .{ .di_first = .init(context_ptr) };
875852 }
876
877 return address;
878 }
879
880 fn next_unwind(it: *StackIterator) !usize {
881 const unwind_state = &it.unwind_state.?;
882 const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc);
883 switch (native_os) {
884 .macos, .ios, .watchos, .tvos, .visionos => {
885 // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding
886 // via DWARF before attempting to use the compact unwind info will produce incorrect results.
887 if (module.unwind_info) |unwind_info| {
888 if (SelfInfo.unwindFrameMachO(
889 unwind_state.debug_info.allocator,
890 module.base_address,
891 &unwind_state.dwarf_context,
892 unwind_info,
893 module.eh_frame,
894 )) |return_address| {
895 return return_address;
896 } else |err| {
897 if (err != error.RequiresDWARFUnwind) return err;
898 }
899 } else return error.MissingUnwindInfo;
900 },
901 else => {},
853 // Workaround the C backend being unable to use inline assembly on MSVC by disabling the
854 // call to `current`. This effectively constrains stack trace collection and dumping to FP
855 // unwinding when building with CBE for MSVC.
856 if (!(builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) and
857 SelfInfo != void and
858 SelfInfo.can_unwind and
859 cpu_context.Native != noreturn)
860 {
861 // We don't need `di_first` here, because our PC is in `std.debug`; we're only interested
862 // in our caller's frame and above.
863 return .{ .di = .init(&.current()) };
902864 }
903
904 if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| {
905 return SelfInfo.unwindFrameDwarf(
906 unwind_state.debug_info.allocator,
907 di,
908 module.base_address,
909 &unwind_state.dwarf_context,
910 null,
911 );
912 } else return error.MissingDebugInfo;
865 return .{ .fp = @frameAddress() };
913866 }
914
915 fn next_internal(it: *StackIterator) ?usize {
916 if (have_ucontext) {
917 if (it.unwind_state) |*unwind_state| {
918 if (!unwind_state.failed) {
919 if (unwind_state.dwarf_context.pc == 0) return null;
920 defer it.fp = unwind_state.dwarf_context.getFp() catch 0;
921 if (it.next_unwind()) |return_address| {
922 return return_address;
923 } else |err| {
924 unwind_state.last_error = err;
925 unwind_state.failed = true;
926
927 // Fall back to fp-based unwinding on the first failure.
928 // We can't attempt it again for other modules higher in the
929 // stack because the full register state won't have been unwound.
930 }
931 }
932 }
867 fn deinit(si: *StackIterator) void {
868 switch (si.*) {
869 .fp => {},
870 .di, .di_first => |*unwind_context| unwind_context.deinit(getDebugInfoAllocator()),
933871 }
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;
958872 }
959};
960873
961pub fn writeCurrentStackTrace(
962 writer: *Writer,
963 debug_info: *SelfInfo,
964 tty_config: tty.Config,
965 start_addr: ?usize,
966) !void {
967 if (native_os == .windows) {
968 var context: ThreadContext = undefined;
969 assert(getContext(&context));
970 return writeStackTraceWindows(writer, debug_info, tty_config, &context, start_addr);
874 /// On aarch64-macos, Apple mandate that the frame pointer is always used.
875 /// TODO: are there any other architectures with guarantees like this?
876 const fp_unwind_is_safe = builtin.cpu.arch == .aarch64 and builtin.os.tag.isDarwin();
877
878 /// Whether the current unwind strategy is allowed given `allow_unsafe`.
879 fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool {
880 return switch (it.*) {
881 .di, .di_first => true,
882 // If we omitted frame pointers from *this* compilation, FP unwinding would crash
883 // immediately regardless of anything. But FPs could also be omitted from a different
884 // linked object, so it's not guaranteed to be safe, unless the target specifically
885 // requires it.
886 .fp => !builtin.omit_frame_pointer and (fp_unwind_is_safe or allow_unsafe),
887 };
971888 }
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| {
981 printLastUnwindError(&it, debug_info, writer, tty_config);
982
983 // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,
984 // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid
985 // an overflow. We do not need to signal `StackIterator` as it will correctly detect this
986 // condition on the subsequent iteration and return `null` thus terminating the loop.
987 // same behaviour for x86-windows-msvc
988 const address = return_address -| 1;
989 try printSourceAtAddress(debug_info, writer, address, tty_config);
990 } else printLastUnwindError(&it, debug_info, writer, tty_config);
991}
890 const Result = union(enum) {
891 /// A stack frame has been found; this is the corresponding return address.
892 frame: usize,
893 /// The end of the stack has been reached.
894 end,
895 /// We were using `SelfInfo.UnwindInfo`, but are now switching to FP unwinding due to this error.
896 switch_to_fp: struct {
897 address: usize,
898 err: SelfInfoError,
899 },
900 };
992901
993pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize {
994 if (builtin.cpu.arch == .x86) {
995 // RtlVirtualUnwind doesn't exist on x86
996 return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null);
997 }
902 fn next(it: *StackIterator) Result {
903 switch (it.*) {
904 .di_first => |unwind_context| {
905 const first_pc = unwind_context.pc;
906 if (first_pc == 0) return .end;
907 it.* = .{ .di = unwind_context };
908 // The caller expects *return* addresses, where they will subtract 1 to find the address of the call.
909 // However, we have the actual current PC, which should not be adjusted. Compensate by adding 1.
910 return .{ .frame = first_pc +| 1 };
911 },
912 .di => |*unwind_context| {
913 const di = getSelfDebugInfo() catch unreachable;
914 const di_gpa = getDebugInfoAllocator();
915 const ret_addr = di.unwindFrame(di_gpa, unwind_context) catch |err| {
916 const pc = unwind_context.pc;
917 it.* = .{ .fp = unwind_context.getFp() };
918 return .{ .switch_to_fp = .{
919 .address = pc,
920 .err = err,
921 } };
922 };
923 if (ret_addr <= 1) return .end;
924 return .{ .frame = ret_addr };
925 },
926 .fp => |fp| {
927 if (fp == 0) return .end; // we reached the "sentinel" base pointer
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;
1002 if (existing_context) |context_ptr| {
1003 context = context_ptr.*;
1004 } else {
1005 context = std.mem.zeroes(windows.CONTEXT);
1006 windows.ntdll.RtlCaptureContext(&context);
1007 }
932 if (bp_addr == 0 or !mem.isAligned(bp_addr, @alignOf(usize)) or
933 ra_addr == 0 or !mem.isAligned(ra_addr, @alignOf(usize)))
934 {
935 // This isn't valid, but it most likely indicates end of stack.
936 return .end;
937 }
1008938
1009 var i: usize = 0;
1010 var image_base: windows.DWORD64 = undefined;
1011 var history_table: windows.UNWIND_HISTORY_TABLE = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE);
1012
1013 while (i < addresses.len) : (i += 1) {
1014 const current_regs = context.getRegs();
1015 if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &history_table)) |runtime_function| {
1016 var handler_data: ?*anyopaque = null;
1017 var establisher_frame: u64 = undefined;
1018 _ = windows.ntdll.RtlVirtualUnwind(
1019 windows.UNW_FLAG_NHANDLER,
1020 image_base,
1021 current_regs.ip,
1022 runtime_function,
1023 &context,
1024 &handler_data,
1025 &establisher_frame,
1026 null,
1027 );
1028 } else {
1029 // leaf function
1030 context.setIp(@as(*usize, @ptrFromInt(current_regs.sp)).*);
1031 context.setSp(current_regs.sp + @sizeOf(usize));
1032 }
939 const bp_ptr: *const usize = @ptrFromInt(bp_addr);
940 const ra_ptr: *const usize = @ptrFromInt(ra_addr);
941 const bp = applyOffset(bp_ptr.*, bp_bias) orelse return .end;
1033942
1034 const next_regs = context.getRegs();
1035 if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) {
1036 break;
1037 }
943 // The stack grows downards, so `bp > fp` should always hold. If it doesn't, this
944 // frame is invalid, so we'll treat it as though it we reached end of stack. The
945 // exception is address 0, which is a graceful end-of-stack signal, in which case
946 // *this* return address is valid and the *next* iteration will be the last.
947 if (bp != 0 and bp <= fp) return .end;
1038948
1039 if (next_regs.ip == 0) {
1040 break;
949 it.fp = bp;
950 const ra = stripInstructionPtrAuthCode(ra_ptr.*);
951 if (ra <= 1) return .end;
952 return .{ .frame = ra };
953 },
1041954 }
1042
1043 addresses[i] = next_regs.ip;
1044955 }
1045956
1046 return i;
1047}
957 /// Offset of the saved base pointer (previous frame pointer) wrt the frame pointer.
958 const bp_offset = off: {
959 // On RISC-V the frame pointer points to the top of the saved register
960 // area, on pretty much every other architecture it points to the stack
961 // slot where the previous frame pointer is saved.
962 if (native_arch.isRISCV()) break :off -2 * @sizeOf(usize);
963 // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS.
964 if (native_arch.isSPARC()) break :off 14 * @sizeOf(usize);
965 break :off 0;
966 };
1048967
1049pub fn writeStackTraceWindows(
1050 writer: *Writer,
1051 debug_info: *SelfInfo,
1052 tty_config: tty.Config,
1053 context: *const windows.CONTEXT,
1054 start_addr: ?usize,
1055) !void {
1056 var addr_buf: [1024]usize = undefined;
1057 const n = walkStackWindows(addr_buf[0..], context);
1058 const addrs = addr_buf[0..n];
1059 const start_i: usize = if (start_addr) |saddr| blk: {
1060 for (addrs, 0..) |addr, i| {
1061 if (addr == saddr) break :blk i;
1062 }
1063 return;
1064 } else 0;
1065 for (addrs[start_i..]) |addr| {
1066 try printSourceAtAddress(debug_info, writer, addr - 1, tty_config);
1067 }
1068}
968 /// Offset of the saved return address wrt the frame pointer.
969 const ra_offset = off: {
970 if (native_arch == .powerpc64le) break :off 2 * @sizeOf(usize);
971 break :off @sizeOf(usize);
972 };
1069973
1070fn printUnknownSource(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
1071 const module_name = debug_info.getModuleNameForAddress(address);
1072 return printLineInfo(
1073 writer,
1074 null,
1075 address,
1076 "???",
1077 module_name orelse "???",
1078 tty_config,
1079 printLineFromFileAnyOs,
1080 );
1081}
974 /// Value to add to a base pointer after loading it from the stack. Yes, SPARC really does this.
975 const bp_bias = bias: {
976 if (native_arch.isSPARC()) break :bias 2047;
977 break :bias 0;
978 };
1082979
1083fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: tty.Config) void {
1084 if (!have_ucontext) return;
1085 if (it.getLastError()) |unwind_error| {
1086 printUnwindError(debug_info, writer, unwind_error.address, unwind_error.err, tty_config) catch {};
980 fn applyOffset(addr: usize, comptime off: comptime_int) ?usize {
981 if (off >= 0) return math.add(usize, addr, off) catch return null;
982 return math.sub(usize, addr, -off) catch return null;
1087983 }
1088}
984};
1089985
1090fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, err: UnwindError, tty_config: tty.Config) !void {
1091 const module_name = debug_info.getModuleNameForAddress(address) orelse "???";
1092 try tty_config.setColor(writer, .dim);
1093 if (err == error.MissingDebugInfo) {
1094 try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });
1095 } else {
1096 try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err });
986/// Some platforms use pointer authentication: the upper bits of instruction pointers contain a
987/// signature. This function clears those signature bits to make the pointer directly usable.
988pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
989 if (native_arch.isAARCH64()) {
990 // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
991 // The save / restore is because `xpaclri` operates on x30 (LR)
992 return asm (
993 \\mov x16, x30
994 \\mov x30, x15
995 \\hint 0x07
996 \\mov x15, x30
997 \\mov x30, x16
998 : [ret] "={x15}" (-> usize),
999 : [ptr] "{x15}" (ptr),
1000 : .{ .x16 = true });
10971001 }
1098 try tty_config.setColor(writer, .reset);
1099}
11001002
1101pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
1102 const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {
1103 error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),
1104 else => return err,
1105 };
1003 return ptr;
1004}
11061005
1107 const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) {
1108 error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),
1109 else => return err,
1006fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
1007 const symbol: Symbol = debug_info.getSymbol(gpa, address) catch |err| switch (err) {
1008 error.MissingDebugInfo,
1009 error.UnsupportedDebugInfo,
1010 error.InvalidDebugInfo,
1011 => .unknown,
1012 error.ReadFailed, error.Unexpected => s: {
1013 tty_config.setColor(writer, .dim) catch {};
1014 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1015 tty_config.setColor(writer, .reset) catch {};
1016 break :s .unknown;
1017 },
1018 error.OutOfMemory => s: {
1019 tty_config.setColor(writer, .dim) catch {};
1020 try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
1021 tty_config.setColor(writer, .reset) catch {};
1022 break :s .unknown;
1023 },
11101024 };
1111 defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name);
1112
1025 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
11131026 return printLineInfo(
11141027 writer,
1115 symbol_info.source_location,
1028 symbol.source_location,
11161029 address,
1117 symbol_info.name,
1118 symbol_info.compile_unit_name,
1030 symbol.name orelse "???",
1031 symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",
11191032 tty_config,
1120 printLineFromFileAnyOs,
11211033 );
11221034}
1123
11241035fn printLineInfo(
11251036 writer: *Writer,
11261037 source_location: ?SourceLocation,
......@@ -1128,10 +1039,9 @@ fn printLineInfo(
11281039 symbol_name: []const u8,
11291040 compile_unit_name: []const u8,
11301041 tty_config: tty.Config,
1131 comptime printLineFromFile: anytype,
1132) !void {
1042) Writer.Error!void {
11331043 nosuspend {
1134 try tty_config.setColor(writer, .bold);
1044 tty_config.setColor(writer, .bold) catch {};
11351045
11361046 if (source_location) |*sl| {
11371047 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
......@@ -1139,11 +1049,11 @@ fn printLineInfo(
11391049 try writer.writeAll("???:?:?");
11401050 }
11411051
1142 try tty_config.setColor(writer, .reset);
1052 tty_config.setColor(writer, .reset) catch {};
11431053 try writer.writeAll(": ");
1144 try tty_config.setColor(writer, .dim);
1054 tty_config.setColor(writer, .dim) catch {};
11451055 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 {};
11471057 try writer.writeAll("\n");
11481058
11491059 // Show the matching source code line if possible
......@@ -1154,22 +1064,24 @@ fn printLineInfo(
11541064 const space_needed = @as(usize, @intCast(sl.column - 1));
11551065
11561066 try writer.splatByteAll(' ', space_needed);
1157 try tty_config.setColor(writer, .green);
1067 tty_config.setColor(writer, .green) catch {};
11581068 try writer.writeAll("^");
1159 try tty_config.setColor(writer, .reset);
1069 tty_config.setColor(writer, .reset) catch {};
11601070 }
11611071 try writer.writeAll("\n");
1162 } else |err| switch (err) {
1163 error.EndOfFile, error.FileNotFound => {},
1164 error.BadPathName => {},
1165 error.AccessDenied => {},
1166 else => return err,
1072 } else |_| {
1073 // Ignore all errors; it's a better UX to just print the source location without the
1074 // corresponding line number. The user can always open the source file themselves.
11671075 }
11681076 }
11691077 }
11701078}
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 {
11731085 // Need this to always block even in async I/O mode, because this could potentially
11741086 // be called from e.g. the event loop code crashing.
11751087 var f = try fs.cwd().openFile(source_location.file_name, .{});
......@@ -1223,7 +1135,7 @@ fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !voi
12231135 }
12241136}
12251137
1226test printLineFromFileAnyOs {
1138test printLineFromFile {
12271139 var aw: Writer.Allocating = .init(std.testing.allocator);
12281140 defer aw.deinit();
12291141 const output_stream = &aw.writer;
......@@ -1245,9 +1157,9 @@ test printLineFromFileAnyOs {
12451157 defer allocator.free(path);
12461158 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 });
12511163 try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());
12521164 aw.clearRetainingCapacity();
12531165 }
......@@ -1263,11 +1175,11 @@ test printLineFromFileAnyOs {
12631175 ,
12641176 });
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 });
12671179 try expectEqualStrings("1\n", aw.written());
12681180 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 });
12711183 try expectEqualStrings("3\n", aw.written());
12721184 aw.clearRetainingCapacity();
12731185 }
......@@ -1286,7 +1198,7 @@ test printLineFromFileAnyOs {
12861198 try writer.splatByteAll('a', overlap);
12871199 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 });
12901202 try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());
12911203 aw.clearRetainingCapacity();
12921204 }
......@@ -1300,7 +1212,7 @@ test printLineFromFileAnyOs {
13001212 const writer = &file_writer.interface;
13011213 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 });
13041216 try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());
13051217 aw.clearRetainingCapacity();
13061218 }
......@@ -1314,19 +1226,19 @@ test printLineFromFileAnyOs {
13141226 const writer = &file_writer.interface;
13151227 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 });
13201232 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());
13211233 aw.clearRetainingCapacity();
13221234
13231235 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 });
13261238 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());
13271239 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 });
13301242 try expectEqualStrings("a\n", aw.written());
13311243 aw.clearRetainingCapacity();
13321244 }
......@@ -1342,26 +1254,29 @@ test printLineFromFileAnyOs {
13421254 try writer.splatByteAll('\n', real_file_start);
13431255 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 });
13461258 try expectEqualStrings("abc\n", aw.written());
13471259 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 });
13501262 try expectEqualStrings("def\n", aw.written());
13511263 aw.clearRetainingCapacity();
13521264 }
13531265}
13541266
1355/// TODO multithreaded awareness
1356var debug_info_allocator: ?mem.Allocator = null;
1357var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;
1358fn getDebugInfoAllocator() mem.Allocator {
1359 if (debug_info_allocator) |a| return a;
1360
1361 debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
1362 const allocator = debug_info_arena_allocator.allocator();
1363 debug_info_allocator = allocator;
1364 return allocator;
1267/// The returned allocator should be thread-safe if the compilation is multi-threaded, because
1268/// multiple threads could capture and/or print stack traces simultaneously.
1269fn getDebugInfoAllocator() Allocator {
1270 // Allow overriding the debug info allocator by exposing `root.debug.getDebugInfoAllocator`.
1271 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getDebugInfoAllocator")) {
1272 return root.debug.getDebugInfoAllocator();
1273 }
1274 // Otherwise, use a global arena backed by the page allocator
1275 const S = struct {
1276 var arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
1277 var ts_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena.allocator() };
1278 };
1279 return S.ts_arena.allocator();
13651280}
13661281
13671282/// 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) {
13721287 .solaris,
13731288 .illumos,
13741289 .windows,
1290 .freebsd,
1291 .openbsd,
13751292 => true,
13761293
1377 .freebsd, .openbsd => have_ucontext,
13781294 else => false,
13791295};
13801296
......@@ -1396,7 +1312,16 @@ pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) void {
13961312 posix.sigaction(posix.SIG.FPE, act, null);
13971313}
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)
14001325pub fn attachSegfaultHandler() void {
14011326 if (!have_segfault_handling_support) {
14021327 @compileError("segfault handler not supported for this target");
......@@ -1430,52 +1355,9 @@ fn resetSegfaultHandler() void {
14301355}
14311356
14321357fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn {
1433 // Reset to the default handler so that if a segfault happens in this handler it will crash
1434 // the process. Also when this handler returns, the original instruction will be repeated
1435 // and the resulting segfault will crash the process rather than continually dump stack traces.
1436 resetSegfaultHandler();
1437
1438 const addr = switch (native_os) {
1439 .linux => @intFromPtr(info.fields.sigfault.addr),
1440 .freebsd, .macos => @intFromPtr(info.addr),
1441 .netbsd => @intFromPtr(info.info.reason.fault.addr),
1442 .openbsd => @intFromPtr(info.data.fault.addr),
1443 .solaris, .illumos => @intFromPtr(info.reason.fault.addr),
1444 else => unreachable,
1445 };
1446
1447 const code = if (native_os == .netbsd) info.info.code else info.code;
1448 nosuspend switch (panic_stage) {
1449 0 => {
1450 panic_stage = 1;
1451 _ = panicking.fetchAdd(1, .seq_cst);
1452
1453 {
1454 lockStdErr();
1455 defer unlockStdErr();
1456
1457 dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr);
1458 }
1459
1460 waitForOtherThreadToFinishPanicking();
1461 },
1462 else => {
1463 // panic mutex already locked
1464 dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr);
1465 },
1466 };
1467
1468 // We cannot allow the signal handler to return because when it runs the original instruction
1469 // again, the memory may be mapped and undefined behavior would occur rather than repeating
1470 // the segfault. So we simply abort here.
1471 posix.abort();
1472}
1473
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
1358 if (use_trap_panic) @trap();
1359 const addr: ?usize, const name: []const u8 = info: {
1360 if (native_os == .linux and native_arch == .x86_64) {
14791361 // x86_64 doesn't have a full 64-bit virtual address space.
14801362 // Addresses outside of that address space are non-canonical
14811363 // 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)
14831365 // but can also happen when no addressable memory is involved;
14841366 // for example when reading/writing model-specific registers
14851367 // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode).
1486 stderr.writeAll("General protection exception (no address available)\n")
1487 else
1488 stderr.print("Segmentation fault at address 0x{x}\n", .{addr}),
1489 posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}),
1490 posix.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}),
1491 posix.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}),
1492 else => unreachable,
1493 } catch posix.abort();
1494
1495 switch (native_arch) {
1496 .x86,
1497 .x86_64,
1498 .arm,
1499 .armeb,
1500 .thumb,
1501 .thumbeb,
1502 .aarch64,
1503 .aarch64_be,
1504 => {
1505 // Some kernels don't align `ctx_ptr` properly. Handle this defensively.
1506 const ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr);
1507 var new_ctx: posix.ucontext_t = ctx.*;
1508 if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) {
1509 // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`,
1510 // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the
1511 // contents to the right place so that the `mcontext` pointer will be correct after the
1512 // `relocateContext` call below.
1513 new_ctx.__mcontext_data = @as(*align(1) extern struct {
1514 onstack: c_int,
1515 sigmask: std.c.sigset_t,
1516 stack: std.c.stack_t,
1517 link: ?*std.c.ucontext_t,
1518 mcsize: u64,
1519 mcontext: *std.c.mcontext_t,
1520 __mcontext_data: std.c.mcontext_t align(@sizeOf(usize)), // Disable padding after `mcontext`.
1521 }, @ptrCast(ctx)).__mcontext_data;
1368 const SI_KERNEL = 0x80;
1369 if (sig == posix.SIG.SEGV and info.code == SI_KERNEL) {
1370 break :info .{ null, "General protection exception" };
15221371 }
1523 relocateContext(&new_ctx);
1524 dumpStackTraceFromBase(&new_ctx, stderr);
1525 },
1526 else => {},
1527 }
1372 }
1373 const addr: usize = switch (native_os) {
1374 .linux => @intFromPtr(info.fields.sigfault.addr),
1375 .freebsd, .macos => @intFromPtr(info.addr),
1376 .netbsd => @intFromPtr(info.info.reason.fault.addr),
1377 .openbsd => @intFromPtr(info.data.fault.addr),
1378 .solaris, .illumos => @intFromPtr(info.reason.fault.addr),
1379 else => comptime unreachable,
1380 };
1381 const name = switch (sig) {
1382 posix.SIG.SEGV => "Segmentation fault",
1383 posix.SIG.ILL => "Illegal instruction",
1384 posix.SIG.BUS => "Bus error",
1385 posix.SIG.FPE => "Arithmetic exception",
1386 else => unreachable,
1387 };
1388 break :info .{ addr, name };
1389 };
1390 const opt_cpu_context: ?cpu_context.Native = cpu_context.fromPosixSignalContext(ctx_ptr);
1391 handleSegfault(addr, name, if (opt_cpu_context) |*ctx| ctx else null);
15281392}
15291393
15301394fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long {
1531 switch (info.ExceptionRecord.ExceptionCode) {
1532 windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, 0, "Unaligned Memory Access"),
1533 windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, 1, null),
1534 windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, 2, null),
1535 windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, 0, "Stack Overflow"),
1395 if (use_trap_panic) @trap();
1396 const name: []const u8, const addr: ?usize = switch (info.ExceptionRecord.ExceptionCode) {
1397 windows.EXCEPTION_DATATYPE_MISALIGNMENT => .{ "Unaligned memory access", null },
1398 windows.EXCEPTION_ACCESS_VIOLATION => .{ "Segmentation fault", info.ExceptionRecord.ExceptionInformation[1] },
1399 windows.EXCEPTION_ILLEGAL_INSTRUCTION => .{ "Illegal instruction", info.ContextRecord.getRegs().ip },
1400 windows.EXCEPTION_STACK_OVERFLOW => .{ "Stack overflow", null },
15361401 else => return windows.EXCEPTION_CONTINUE_SEARCH,
1537 }
1402 };
1403 handleSegfault(addr, name, &cpu_context.fromWindowsContext(info.ContextRecord));
15381404}
15391405
1540fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) noreturn {
1541 // For backends that cannot handle the language features used by this segfault handler, we have a simpler one,
1542 switch (builtin.zig_backend) {
1543 .stage2_x86_64 => if (builtin.target.ofmt == .coff) @trap(),
1544 else => {},
1406fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn {
1407 // Allow overriding the target-agnostic segfault handler by exposing `root.debug.handleSegfault`.
1408 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "handleSegfault")) {
1409 return root.debug.handleSegfault(addr, name, opt_ctx);
15451410 }
1411 return defaultHandleSegfault(addr, name, opt_ctx);
1412}
15461413
1547 comptime assert(windows.CONTEXT != void);
1548 nosuspend switch (panic_stage) {
1414pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn {
1415 // There is very similar logic to the following in `defaultPanic`.
1416 switch (panic_stage) {
15491417 0 => {
15501418 panic_stage = 1;
15511419 _ = panicking.fetchAdd(1, .seq_cst);
15521420
1553 {
1421 trace: {
1422 const tty_config = tty.detectConfig(.stderr());
1423
15541424 const stderr = lockStderrWriter(&.{});
15551425 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 }
15581438 }
1559
1560 waitForOtherThreadToFinishPanicking();
15611439 },
15621440 1 => {
15631441 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().
15641445 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};
15651446 },
1566 else => {},
1567 };
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();
1447 else => {}, // Panicked while printing the recursive panic message.
1448 }
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();
15801454}
15811455
15821456pub fn dumpStackPointerAddr(prefix: []const u8) void {
......@@ -1587,26 +1461,23 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void {
15871461}
15881462
15891463test "manage resources correctly" {
1590 if (builtin.strip_debug_info) return error.SkipZigTest;
1591
1592 if (native_os == .wasi) return error.SkipZigTest;
1593
1594 if (native_os == .windows) {
1595 // https://github.com/ziglang/zig/issues/13963
1596 return error.SkipZigTest;
1597 }
1598
1599 // self-hosted debug info is still too buggy
1600 if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest;
1601
1602 var discarding: Writer.Discarding = .init(&.{});
1603 var di = try SelfInfo.open(testing.allocator);
1604 defer di.deinit();
1605 try printSourceAtAddress(&di, &discarding.writer, showMyTrace(), tty.detectConfig(.stderr()));
1606}
1607
1608noinline fn showMyTrace() usize {
1609 return @returnAddress();
1464 if (SelfInfo == void) return error.SkipZigTest;
1465 const S = struct {
1466 noinline fn showMyTrace() usize {
1467 return @returnAddress();
1468 }
1469 };
1470 const gpa = std.testing.allocator;
1471 var discarding: std.Io.Writer.Discarding = .init(&.{});
1472 var di: SelfInfo = .init;
1473 defer di.deinit(gpa);
1474 try printSourceAtAddress(
1475 gpa,
1476 &di,
1477 &discarding.writer,
1478 S.showMyTrace(),
1479 tty.detectConfig(.stderr()),
1480 );
16101481}
16111482
16121483/// 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
16531524
16541525 if (t.index < size) {
16551526 t.notes[t.index] = note;
1656 t.addrs[t.index] = [1]usize{0} ** stack_frame_count;
1657 var stack_trace: std.builtin.StackTrace = .{
1658 .index = 0,
1659 .instruction_addresses = &t.addrs[t.index],
1660 };
1661 captureStackTrace(addr, &stack_trace);
1527 const addrs = &t.addrs[t.index];
1528 const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs);
1529 if (st.index < addrs.len) {
1530 @memset(addrs[st.index..], 0); // zero unused frames to indicate end of trace
1531 }
16621532 }
16631533 // Keep counting even if the end is reached so that the
16641534 // 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
16721542 const stderr = lockStderrWriter(&.{});
16731543 defer unlockStderrWriter();
16741544 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 };
16821545 for (t.addrs[0..end], 0..) |frames_array, i| {
16831546 stderr.print("{s}:\n", .{t.notes[i]}) catch return;
16841547 var frames_array_mutable = frames_array;
......@@ -1687,7 +1550,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
16871550 .index = frames.len,
16881551 .instruction_addresses = frames,
16891552 };
1690 writeStackTrace(stack_trace, stderr, debug_info, tty_config) catch continue;
1553 writeStackTrace(&stack_trace, stderr, tty_config) catch return;
16911554 }
16921555 if (t.index > end) {
16931556 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;
145145pub fn resolveAddressesDwarf(
146146 cov: *Coverage,
147147 gpa: Allocator,
148 endian: std.builtin.Endian,
148149 /// Asserts the addresses are in ascending order.
149150 sorted_pc_addrs: []const u64,
150151 /// Asserts its length equals length of `sorted_pc_addrs`.
......@@ -184,7 +185,7 @@ pub fn resolveAddressesDwarf(
184185 if (cu.src_loc_cache == null) {
185186 cov.mutex.unlock();
186187 defer cov.mutex.lock();
187 d.populateSrcLocCache(gpa, cu) catch |err| switch (err) {
188 d.populateSrcLocCache(gpa, endian, cu) catch |err| switch (err) {
188189 error.MissingDebugInfo, error.InvalidDebugInfo => {
189190 out.* = SourceLocation.invalid;
190191 continue :next_pc;
lib/std/debug/Dwarf.zig+307-1221
......@@ -1,22 +1,18 @@
11//! Implements parsing, decoding, and caching of DWARF information.
22//!
3//! This API does not assume the current executable is itself the thing being
4//! debugged, however, it does assume the debug info has the same CPU
5//! architecture and OS as the current executable. It is planned to remove this
6//! limitation.
3//! This API makes no assumptions about the relationship between the host and
4//! the target being debugged. In other words, any DWARF information can be used
5//! from any host via this API. Note, however, that the limits of 32-bit
6//! addressing can cause very large 64-bit binaries to be impossible to open on
7//! 32-bit hosts.
78//!
89//! For unopinionated types and bits, see `std.dwarf`.
910
10const builtin = @import("builtin");
11const native_endian = builtin.cpu.arch.endian();
12
1311const std = @import("../std.zig");
1412const Allocator = std.mem.Allocator;
15const elf = std.elf;
1613const mem = std.mem;
1714const DW = std.dwarf;
1815const AT = DW.AT;
19const EH = DW.EH;
2016const FORM = DW.FORM;
2117const Format = DW.Format;
2218const RLE = DW.RLE;
......@@ -24,7 +20,6 @@ const UT = DW.UT;
2420const assert = std.debug.assert;
2521const cast = std.math.cast;
2622const maxInt = std.math.maxInt;
27const Path = std.Build.Cache.Path;
2823const ArrayList = std.ArrayList;
2924const Endian = std.builtin.Endian;
3025const Reader = std.Io.Reader;
......@@ -32,15 +27,13 @@ const Reader = std.Io.Reader;
3227const Dwarf = @This();
3328
3429pub const expression = @import("Dwarf/expression.zig");
35pub const abi = @import("Dwarf/abi.zig");
36pub const call_frame = @import("Dwarf/call_frame.zig");
30pub const Unwind = @import("Dwarf/Unwind.zig");
31pub const SelfUnwinder = @import("Dwarf/SelfUnwinder.zig");
3732
3833/// Useful to temporarily enable while working on this file.
3934const debug_debug_mode = false;
4035
41endian: Endian,
42sections: SectionArray = null_section_array,
43is_macho: bool,
36sections: SectionArray = @splat(null),
4437
4538/// Filled later by the initializer
4639abbrev_table_list: ArrayList(Abbrev.Table) = .empty,
......@@ -49,14 +42,6 @@ compile_unit_list: ArrayList(CompileUnit) = .empty,
4942/// Filled later by the initializer
5043func_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
6045/// Populated by `populateRanges`.
6146ranges: ArrayList(Range) = .empty,
6247
......@@ -69,10 +54,7 @@ pub const Range = struct {
6954
7055pub const Section = struct {
7156 data: []const u8,
72 // Module-relative virtual address.
73 // Only set if the section data was loaded from disk.
74 virtual_address: ?usize = null,
75 // If `data` is owned by this Dwarf.
57 /// If `data` is owned by this Dwarf.
7658 owned: bool,
7759
7860 pub const Id = enum {
......@@ -87,21 +69,7 @@ pub const Section = struct {
8769 debug_rnglists,
8870 debug_addr,
8971 debug_names,
90 debug_frame,
91 eh_frame,
92 eh_frame_hdr,
9372 };
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 }
10573};
10674
10775pub const Abbrev = struct {
......@@ -110,8 +78,8 @@ pub const Abbrev = struct {
11078 has_children: bool,
11179 attrs: []Attr,
11280
113 fn deinit(abbrev: *Abbrev, allocator: Allocator) void {
114 allocator.free(abbrev.attrs);
81 fn deinit(abbrev: *Abbrev, gpa: Allocator) void {
82 gpa.free(abbrev.attrs);
11583 abbrev.* = undefined;
11684 }
11785
......@@ -127,11 +95,11 @@ pub const Abbrev = struct {
12795 offset: u64,
12896 abbrevs: []Abbrev,
12997
130 fn deinit(table: *Table, allocator: Allocator) void {
98 fn deinit(table: *Table, gpa: Allocator) void {
13199 for (table.abbrevs) |*abbrev| {
132 abbrev.deinit(allocator);
100 abbrev.deinit(gpa);
133101 }
134 allocator.free(table.abbrevs);
102 gpa.free(table.abbrevs);
135103 table.* = undefined;
136104 }
137105
......@@ -146,6 +114,7 @@ pub const Abbrev = struct {
146114pub const CompileUnit = struct {
147115 version: u16,
148116 format: Format,
117 addr_size_bytes: u8,
149118 die: Die,
150119 pc_range: ?PcRange,
151120
......@@ -196,7 +165,7 @@ pub const CompileUnit = struct {
196165
197166pub const FormValue = union(enum) {
198167 addr: u64,
199 addrx: usize,
168 addrx: u64,
200169 block: []const u8,
201170 udata: u64,
202171 data16: *const [16]u8,
......@@ -208,7 +177,7 @@ pub const FormValue = union(enum) {
208177 ref_addr: u64,
209178 string: [:0]const u8,
210179 strp: u64,
211 strx: usize,
180 strx: u64,
212181 line_strp: u64,
213182 loclistx: u64,
214183 rnglistx: u64,
......@@ -243,8 +212,8 @@ pub const Die = struct {
243212 value: FormValue,
244213 };
245214
246 fn deinit(self: *Die, allocator: Allocator) void {
247 allocator.free(self.attrs);
215 fn deinit(self: *Die, gpa: Allocator) void {
216 gpa.free(self.attrs);
248217 self.* = undefined;
249218 }
250219
......@@ -258,13 +227,14 @@ pub const Die = struct {
258227 fn getAttrAddr(
259228 self: *const Die,
260229 di: *const Dwarf,
230 endian: Endian,
261231 id: u64,
262 compile_unit: CompileUnit,
232 compile_unit: *const CompileUnit,
263233 ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 {
264234 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
265235 return switch (form_value.*) {
266236 .addr => |value| value,
267 .addrx => |index| di.readDebugAddr(compile_unit, index),
237 .addrx => |index| di.readDebugAddr(endian, compile_unit, index),
268238 else => bad(),
269239 };
270240 }
......@@ -294,9 +264,10 @@ pub const Die = struct {
294264 pub fn getAttrString(
295265 self: *const Die,
296266 di: *Dwarf,
267 endian: Endian,
297268 id: u64,
298269 opt_str: ?[]const u8,
299 compile_unit: CompileUnit,
270 compile_unit: *const CompileUnit,
300271 ) error{ InvalidDebugInfo, MissingDebugInfo }![]const u8 {
301272 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
302273 switch (form_value.*) {
......@@ -309,13 +280,13 @@ pub const Die = struct {
309280 .@"32" => {
310281 const byte_offset = compile_unit.str_offsets_base + 4 * index;
311282 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);
313284 return getStringGeneric(opt_str, offset);
314285 },
315286 .@"64" => {
316287 const byte_offset = compile_unit.str_offsets_base + 8 * index;
317288 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);
319290 return getStringGeneric(opt_str, offset);
320291 },
321292 }
......@@ -326,440 +297,17 @@ pub const Die = struct {
326297 }
327298};
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
751300const num_sections = std.enums.directEnumArrayLen(Section.Id, 0);
752301pub const SectionArray = [num_sections]?Section;
753pub const null_section_array = [_]?Section{null} ** num_sections;
754302
755303pub const OpenError = ScanError;
756304
757305/// Initialize DWARF info. The caller has the responsibility to initialize most
758306/// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the
759307/// main binary file (not the secondary debug info file).
760pub fn open(d: *Dwarf, gpa: Allocator) OpenError!void {
761 try d.scanAllFunctions(gpa);
762 try d.scanAllCompileUnits(gpa);
308pub fn open(d: *Dwarf, gpa: Allocator, endian: Endian) OpenError!void {
309 try d.scanAllFunctions(gpa, endian);
310 try d.scanAllCompileUnits(gpa, endian);
763311}
764312
765313const PcRange = struct {
......@@ -776,10 +324,6 @@ pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 {
776324 return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;
777325}
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
783327pub fn deinit(di: *Dwarf, gpa: Allocator) void {
784328 for (di.sections) |opt_section| {
785329 if (opt_section) |s| if (s.owned) gpa.free(s.data);
......@@ -798,14 +342,18 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void {
798342 }
799343 di.compile_unit_list.deinit(gpa);
800344 di.func_list.deinit(gpa);
801 di.cie_map.deinit(gpa);
802 di.fde_list.deinit(gpa);
803345 di.ranges.deinit(gpa);
804346 di.* = undefined;
805347}
806348
807pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 {
808 for (di.func_list.items) |*func| {
349pub fn getSymbolName(di: *const Dwarf, address: u64) ?[]const u8 {
350 // Iterate the function list backwards so that we see child DIEs before their parents. This is
351 // important because `DW_TAG_inlined_subroutine` DIEs will have a range which is a sub-range of
352 // their caller, and we want to return the callee's name, not the caller's.
353 var i: usize = di.func_list.items.len;
354 while (i > 0) {
355 i -= 1;
356 const func = &di.func_list.items[i];
809357 if (func.pc_range) |range| {
810358 if (address >= range.start and address < range.end) {
811359 return func.name;
......@@ -825,35 +373,33 @@ pub const ScanError = error{
825373 StreamTooLong,
826374} || Allocator.Error;
827375
828fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
829 const endian = di.endian;
830 var fbr: Reader = .fixed(di.section(.debug_info).?);
376fn scanAllFunctions(di: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void {
377 var fr: Reader = .fixed(di.section(.debug_info).?);
831378 var this_unit_offset: u64 = 0;
832379
833 while (this_unit_offset < fbr.buffer.len) {
834 fbr.seek = @intCast(this_unit_offset);
380 while (this_unit_offset < fr.buffer.len) {
381 fr.seek = @intCast(this_unit_offset);
835382
836 const unit_header = try readUnitHeader(&fbr, endian);
383 const unit_header = try readUnitHeader(&fr, endian);
837384 if (unit_header.unit_length == 0) return;
838385 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);
841388 if (version < 2 or version > 5) return bad();
842389
843390 var address_size: u8 = undefined;
844391 var debug_abbrev_offset: u64 = undefined;
845392 if (version >= 5) {
846 const unit_type = try fbr.takeByte();
393 const unit_type = try fr.takeByte();
847394 if (unit_type != DW.UT.compile) return bad();
848 address_size = try fbr.takeByte();
849 debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);
395 address_size = try fr.takeByte();
396 debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian);
850397 } else {
851 debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);
852 address_size = try fbr.takeByte();
398 debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian);
399 address_size = try fr.takeByte();
853400 }
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
858404 var max_attrs: usize = 0;
859405 var zig_padding_abbrev_code: u7 = 0;
......@@ -868,8 +414,8 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
868414 }
869415 }
870416 }
871 const attrs_buf = try allocator.alloc(Die.Attr, max_attrs * 3);
872 defer allocator.free(attrs_buf);
417 const attrs_buf = try gpa.alloc(Die.Attr, max_attrs * 3);
418 defer gpa.free(attrs_buf);
873419 var attrs_bufs: [3][]Die.Attr = undefined;
874420 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 {
878424 var compile_unit: CompileUnit = .{
879425 .version = version,
880426 .format = unit_header.format,
427 .addr_size_bytes = address_size,
881428 .die = undefined,
882429 .pc_range = null,
883430
......@@ -890,16 +437,17 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
890437 };
891438
892439 while (true) {
893 fbr.seek = std.mem.indexOfNonePos(u8, fbr.buffer, fbr.seek, &.{
440 fr.seek = std.mem.indexOfNonePos(u8, fr.buffer, fr.seek, &.{
894441 zig_padding_abbrev_code, 0,
895 }) orelse fbr.buffer.len;
896 if (fbr.seek >= next_unit_pos) break;
442 }) orelse fr.buffer.len;
443 if (fr.seek >= next_unit_pos) break;
897444 var die_obj = (try parseDie(
898 &fbr,
445 &fr,
899446 attrs_bufs[0],
900447 abbrev_table,
901448 unit_header.format,
902449 endian,
450 address_size,
903451 )) orelse continue;
904452
905453 switch (die_obj.tag_id) {
......@@ -920,34 +468,36 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
920468 // Prevent endless loops
921469 for (0..3) |_| {
922470 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);
924472 } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| {
925 const after_die_offset = fbr.seek;
926 defer fbr.seek = after_die_offset;
473 const after_die_offset = fr.seek;
474 defer fr.seek = after_die_offset;
927475
928476 // Follow the DIE it points to and repeat
929477 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);
931479 this_die_obj = (try parseDie(
932 &fbr,
480 &fr,
933481 attrs_bufs[2],
934482 abbrev_table, // wrong abbrev table for different cu
935483 unit_header.format,
936484 endian,
485 address_size,
937486 )) orelse return bad();
938487 } else if (this_die_obj.getAttr(AT.specification)) |_| {
939 const after_die_offset = fbr.seek;
940 defer fbr.seek = after_die_offset;
488 const after_die_offset = fr.seek;
489 defer fr.seek = after_die_offset;
941490
942491 // Follow the DIE it points to and repeat
943492 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);
945494 this_die_obj = (try parseDie(
946 &fbr,
495 &fr,
947496 attrs_bufs[2],
948497 abbrev_table, // wrong abbrev table for different cu
949498 unit_header.format,
950499 endian,
500 address_size,
951501 )) orelse return bad();
952502 } else {
953503 break :x null;
......@@ -957,7 +507,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
957507 break :x null;
958508 };
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: {
961511 if (die_obj.getAttr(AT.high_pc)) |high_pc_value| {
962512 const pc_end = switch (high_pc_value.*) {
963513 .addr => |value| value,
......@@ -965,7 +515,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
965515 else => return bad(),
966516 };
967517
968 try di.func_list.append(allocator, .{
518 try di.func_list.append(gpa, .{
969519 .name = fn_name,
970520 .pc_range = .{
971521 .start = low_pc,
......@@ -983,14 +533,14 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
983533 };
984534
985535 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| {
987537 if (err != error.MissingDebugInfo) return err;
988538 break :blk;
989539 };
990540
991541 while (try iter.next()) |range| {
992542 range_added = true;
993 try di.func_list.append(allocator, .{
543 try di.func_list.append(gpa, .{
994544 .name = fn_name,
995545 .pc_range = .{
996546 .start = range.start,
......@@ -1001,7 +551,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
1001551 }
1002552
1003553 if (fn_name != null and !range_added) {
1004 try di.func_list.append(allocator, .{
554 try di.func_list.append(gpa, .{
1005555 .name = fn_name,
1006556 .pc_range = null,
1007557 });
......@@ -1015,38 +565,36 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
1015565 }
1016566}
1017567
1018fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
1019 const endian = di.endian;
1020 var fbr: Reader = .fixed(di.section(.debug_info).?);
568fn scanAllCompileUnits(di: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void {
569 var fr: Reader = .fixed(di.section(.debug_info).?);
1021570 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);
1024573 defer attrs_buf.deinit();
1025574
1026 while (this_unit_offset < fbr.buffer.len) {
1027 fbr.seek = @intCast(this_unit_offset);
575 while (this_unit_offset < fr.buffer.len) {
576 fr.seek = @intCast(this_unit_offset);
1028577
1029 const unit_header = try readUnitHeader(&fbr, endian);
578 const unit_header = try readUnitHeader(&fr, endian);
1030579 if (unit_header.unit_length == 0) return;
1031580 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);
1034583 if (version < 2 or version > 5) return bad();
1035584
1036585 var address_size: u8 = undefined;
1037586 var debug_abbrev_offset: u64 = undefined;
1038587 if (version >= 5) {
1039 const unit_type = try fbr.takeByte();
588 const unit_type = try fr.takeByte();
1040589 if (unit_type != UT.compile) return bad();
1041 address_size = try fbr.takeByte();
1042 debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);
590 address_size = try fr.takeByte();
591 debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian);
1043592 } else {
1044 debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);
1045 address_size = try fbr.takeByte();
593 debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian);
594 address_size = try fr.takeByte();
1046595 }
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
1051599 var max_attrs: usize = 0;
1052600 for (abbrev_table.abbrevs) |abbrev| {
......@@ -1055,20 +603,22 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
1055603 try attrs_buf.resize(max_attrs);
1056604
1057605 var compile_unit_die = (try parseDie(
1058 &fbr,
606 &fr,
1059607 attrs_buf.items,
1060608 abbrev_table,
1061609 unit_header.format,
1062610 endian,
611 address_size,
1063612 )) orelse return bad();
1064613
1065614 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
1069618 var compile_unit: CompileUnit = .{
1070619 .version = version,
1071620 .format = unit_header.format,
621 .addr_size_bytes = address_size,
1072622 .pc_range = null,
1073623 .die = compile_unit_die,
1074624 .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 {
1080630 };
1081631
1082632 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| {
1084634 if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| {
1085635 const pc_end = switch (high_pc_value.*) {
1086636 .addr => |value| value,
......@@ -1100,13 +650,13 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
1100650 }
1101651 };
1102652
1103 try di.compile_unit_list.append(allocator, compile_unit);
653 try di.compile_unit_list.append(gpa, compile_unit);
1104654
1105655 this_unit_offset += next_offset;
1106656 }
1107657}
1108658
1109pub fn populateRanges(d: *Dwarf, gpa: Allocator) ScanError!void {
659pub fn populateRanges(d: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void {
1110660 assert(d.ranges.items.len == 0);
1111661
1112662 for (d.compile_unit_list.items, 0..) |*cu, cu_index| {
......@@ -1119,7 +669,7 @@ pub fn populateRanges(d: *Dwarf, gpa: Allocator) ScanError!void {
1119669 continue;
1120670 }
1121671 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;
1123673 while (try iter.next()) |range| {
1124674 // Not sure why LLVM thinks it's OK to emit these...
1125675 if (range.start == range.end) continue;
......@@ -1144,10 +694,11 @@ const DebugRangeIterator = struct {
1144694 base_address: u64,
1145695 section_type: Section.Id,
1146696 di: *const Dwarf,
697 endian: Endian,
1147698 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() {
1151702 const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges;
1152703 const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo;
1153704
......@@ -1156,15 +707,15 @@ const DebugRangeIterator = struct {
1156707 .rnglistx => |idx| off: {
1157708 switch (compile_unit.format) {
1158709 .@"32" => {
1159 const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx));
710 const offset_loc = compile_unit.rnglists_base + 4 * idx;
1160711 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);
1162713 break :off compile_unit.rnglists_base + offset;
1163714 },
1164715 .@"64" => {
1165 const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx));
716 const offset_loc = compile_unit.rnglists_base + 8 * idx;
1166717 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);
1168719 break :off compile_unit.rnglists_base + offset;
1169720 },
1170721 }
......@@ -1176,42 +727,44 @@ const DebugRangeIterator = struct {
1176727 // specified by DW_AT.low_pc or to some other value encoded
1177728 // in the list itself.
1178729 // 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) {
1180731 error.MissingDebugInfo => 0,
1181732 else => return err,
1182733 };
1183734
1184 var fbr: Reader = .fixed(debug_ranges);
1185 fbr.seek = cast(usize, ranges_offset) orelse return bad();
735 var fr: Reader = .fixed(debug_ranges);
736 fr.seek = cast(usize, ranges_offset) orelse return bad();
1186737
1187738 return .{
1188739 .base_address = base_address,
1189740 .section_type = section_type,
1190741 .di = di,
742 .endian = endian,
1191743 .compile_unit = compile_unit,
1192 .fbr = fbr,
744 .fr = fr,
1193745 };
1194746 }
1195747
1196748 // Returns the next range in the list, or null if the end was reached.
1197749 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;
1199752 switch (self.section_type) {
1200753 .debug_rnglists => {
1201 const kind = try self.fbr.takeByte();
754 const kind = try self.fr.takeByte();
1202755 switch (kind) {
1203756 RLE.end_of_list => return null,
1204757 RLE.base_addressx => {
1205 const index = try self.fbr.takeLeb128(usize);
1206 self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index);
758 const index = try self.fr.takeLeb128(u64);
759 self.base_address = try self.di.readDebugAddr(endian, self.compile_unit, index);
1207760 return try self.next();
1208761 },
1209762 RLE.startx_endx => {
1210 const start_index = try self.fbr.takeLeb128(usize);
1211 const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index);
763 const start_index = try self.fr.takeLeb128(u64);
764 const start_addr = try self.di.readDebugAddr(endian, self.compile_unit, start_index);
1212765
1213 const end_index = try self.fbr.takeLeb128(usize);
1214 const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index);
766 const end_index = try self.fr.takeLeb128(u64);
767 const end_addr = try self.di.readDebugAddr(endian, self.compile_unit, end_index);
1215768
1216769 return .{
1217770 .start = start_addr,
......@@ -1219,10 +772,10 @@ const DebugRangeIterator = struct {
1219772 };
1220773 },
1221774 RLE.startx_length => {
1222 const start_index = try self.fbr.takeLeb128(usize);
1223 const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index);
775 const start_index = try self.fr.takeLeb128(u64);
776 const start_addr = try self.di.readDebugAddr(endian, self.compile_unit, start_index);
1224777
1225 const len = try self.fbr.takeLeb128(usize);
778 const len = try self.fr.takeLeb128(u64);
1226779 const end_addr = start_addr + len;
1227780
1228781 return .{
......@@ -1231,8 +784,8 @@ const DebugRangeIterator = struct {
1231784 };
1232785 },
1233786 RLE.offset_pair => {
1234 const start_addr = try self.fbr.takeLeb128(usize);
1235 const end_addr = try self.fbr.takeLeb128(usize);
787 const start_addr = try self.fr.takeLeb128(u64);
788 const end_addr = try self.fr.takeLeb128(u64);
1236789
1237790 // This is the only kind that uses the base address
1238791 return .{
......@@ -1241,12 +794,12 @@ const DebugRangeIterator = struct {
1241794 };
1242795 },
1243796 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);
1245798 return try self.next();
1246799 },
1247800 RLE.start_end => {
1248 const start_addr = try self.fbr.takeInt(usize, endian);
1249 const end_addr = try self.fbr.takeInt(usize, endian);
801 const start_addr = try readAddress(&self.fr, endian, addr_size_bytes);
802 const end_addr = try readAddress(&self.fr, endian, addr_size_bytes);
1250803
1251804 return .{
1252805 .start = start_addr,
......@@ -1254,8 +807,8 @@ const DebugRangeIterator = struct {
1254807 };
1255808 },
1256809 RLE.start_length => {
1257 const start_addr = try self.fbr.takeInt(usize, endian);
1258 const len = try self.fbr.takeLeb128(usize);
810 const start_addr = try readAddress(&self.fr, endian, addr_size_bytes);
811 const len = try self.fr.takeLeb128(u64);
1259812 const end_addr = start_addr + len;
1260813
1261814 return .{
......@@ -1267,12 +820,13 @@ const DebugRangeIterator = struct {
1267820 }
1268821 },
1269822 .debug_ranges => {
1270 const start_addr = try self.fbr.takeInt(usize, endian);
1271 const end_addr = try self.fbr.takeInt(usize, endian);
823 const start_addr = try readAddress(&self.fr, endian, addr_size_bytes);
824 const end_addr = try readAddress(&self.fr, endian, addr_size_bytes);
1272825 if (start_addr == 0 and end_addr == 0) return null;
1273826
1274 // This entry selects a new value for the base address
1275 if (start_addr == maxInt(usize)) {
827 // The entry with start_addr = max_representable_address selects a new value for the base address
828 const max_representable_address = ~@as(u64, 0) >> @intCast(64 - addr_size_bytes);
829 if (start_addr == max_representable_address) {
1276830 self.base_address = end_addr;
1277831 return try self.next();
1278832 }
......@@ -1288,14 +842,14 @@ const DebugRangeIterator = struct {
1288842};
1289843
1290844/// 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 {
1292846 for (di.compile_unit_list.items) |*compile_unit| {
1293847 if (compile_unit.pc_range) |range| {
1294848 if (target_address >= range.start and target_address < range.end) return compile_unit;
1295849 }
1296850
1297851 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;
1299853 while (try iter.next()) |range| {
1300854 if (target_address >= range.start and target_address < range.end) return compile_unit;
1301855 }
......@@ -1306,49 +860,49 @@ pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*CompileUnit {
1306860
1307861/// Gets an already existing AbbrevTable given the abbrev_offset, or if not found,
1308862/// 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 {
1310864 for (di.abbrev_table_list.items) |*table| {
1311865 if (table.offset == abbrev_offset) {
1312866 return table;
1313867 }
1314868 }
1315869 try di.abbrev_table_list.append(
1316 allocator,
1317 try di.parseAbbrevTable(allocator, abbrev_offset),
870 gpa,
871 try di.parseAbbrevTable(gpa, abbrev_offset),
1318872 );
1319873 return &di.abbrev_table_list.items[di.abbrev_table_list.items.len - 1];
1320874}
1321875
1322fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table {
1323 var fbr: Reader = .fixed(di.section(.debug_abbrev).?);
1324 fbr.seek = cast(usize, offset) orelse return bad();
876fn parseAbbrevTable(di: *Dwarf, gpa: Allocator, offset: u64) !Abbrev.Table {
877 var fr: Reader = .fixed(di.section(.debug_abbrev).?);
878 fr.seek = cast(usize, offset) orelse return bad();
1325879
1326 var abbrevs = std.array_list.Managed(Abbrev).init(allocator);
880 var abbrevs = std.array_list.Managed(Abbrev).init(gpa);
1327881 defer {
1328882 for (abbrevs.items) |*abbrev| {
1329 abbrev.deinit(allocator);
883 abbrev.deinit(gpa);
1330884 }
1331885 abbrevs.deinit();
1332886 }
1333887
1334 var attrs = std.array_list.Managed(Abbrev.Attr).init(allocator);
888 var attrs = std.array_list.Managed(Abbrev.Attr).init(gpa);
1335889 defer attrs.deinit();
1336890
1337891 while (true) {
1338 const code = try fbr.takeLeb128(u64);
892 const code = try fr.takeLeb128(u64);
1339893 if (code == 0) break;
1340 const tag_id = try fbr.takeLeb128(u64);
1341 const has_children = (try fbr.takeByte()) == DW.CHILDREN.yes;
894 const tag_id = try fr.takeLeb128(u64);
895 const has_children = (try fr.takeByte()) == DW.CHILDREN.yes;
1342896
1343897 while (true) {
1344 const attr_id = try fbr.takeLeb128(u64);
1345 const form_id = try fbr.takeLeb128(u64);
898 const attr_id = try fr.takeLeb128(u64);
899 const form_id = try fr.takeLeb128(u64);
1346900 if (attr_id == 0 and form_id == 0) break;
1347901 try attrs.append(.{
1348902 .id = attr_id,
1349903 .form_id = form_id,
1350904 .payload = switch (form_id) {
1351 FORM.implicit_const => try fbr.takeLeb128(i64),
905 FORM.implicit_const => try fr.takeLeb128(i64),
1352906 else => undefined,
1353907 },
1354908 });
......@@ -1369,20 +923,21 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table
1369923}
1370924
1371925fn parseDie(
1372 fbr: *Reader,
926 fr: *Reader,
1373927 attrs_buf: []Die.Attr,
1374928 abbrev_table: *const Abbrev.Table,
1375929 format: Format,
1376930 endian: Endian,
931 addr_size_bytes: u8,
1377932) ScanError!?Die {
1378 const abbrev_code = try fbr.takeLeb128(u64);
933 const abbrev_code = try fr.takeLeb128(u64);
1379934 if (abbrev_code == 0) return null;
1380935 const table_entry = abbrev_table.get(abbrev_code) orelse return bad();
1381936
1382937 const attrs = attrs_buf[0..table_entry.attrs.len];
1383938 for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = .{
1384939 .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),
1386941 };
1387942 return .{
1388943 .tag_id = table_entry.tag_id,
......@@ -1392,55 +947,50 @@ fn parseDie(
1392947}
1393948
1394949/// Ensures that addresses in the returned LineTable are monotonically increasing.
1395fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !CompileUnit.SrcLocCache {
1396 const endian = d.endian;
1397 const compile_unit_cwd = try compile_unit.die.getAttrString(d, AT.comp_dir, d.section(.debug_line_str), compile_unit.*);
950fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit: *const CompileUnit) !CompileUnit.SrcLocCache {
951 const compile_unit_cwd = try compile_unit.die.getAttrString(d, endian, AT.comp_dir, d.section(.debug_line_str), compile_unit);
1398952 const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
1399953
1400 var fbr: Reader = .fixed(d.section(.debug_line).?);
1401 fbr.seek = @intCast(line_info_offset);
954 var fr: Reader = .fixed(d.section(.debug_line).?);
955 fr.seek = @intCast(line_info_offset);
1402956
1403 const unit_header = try readUnitHeader(&fbr, endian);
957 const unit_header = try readUnitHeader(&fr, endian);
1404958 if (unit_header.unit_length == 0) return missing();
1405959
1406960 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);
1409963 if (version < 2) return bad();
1410964
1411 const addr_size: u8, const seg_size: u8 = if (version >= 5) .{
1412 try fbr.takeByte(),
1413 try fbr.takeByte(),
965 const addr_size_bytes: u8, const seg_size: u8 = if (version >= 5) .{
966 try fr.takeByte(),
967 try fr.takeByte(),
1414968 } else .{
1415 switch (unit_header.format) {
1416 .@"32" => 4,
1417 .@"64" => 8,
1418 },
969 compile_unit.addr_size_bytes,
1419970 0,
1420971 };
1421 _ = addr_size;
1422 _ = seg_size;
972 if (seg_size != 0) return bad(); // unsupported
1423973
1424 const prologue_length = try readAddress(&fbr, unit_header.format, endian);
1425 const prog_start_offset = fbr.seek + prologue_length;
974 const prologue_length = try readFormatSizedInt(&fr, unit_header.format, endian);
975 const prog_start_offset = fr.seek + prologue_length;
1426976
1427 const minimum_instruction_length = try fbr.takeByte();
977 const minimum_instruction_length = try fr.takeByte();
1428978 if (minimum_instruction_length == 0) return bad();
1429979
1430980 if (version >= 4) {
1431 const maximum_operations_per_instruction = try fbr.takeByte();
981 const maximum_operations_per_instruction = try fr.takeByte();
1432982 _ = maximum_operations_per_instruction;
1433983 }
1434984
1435 const default_is_stmt = (try fbr.takeByte()) != 0;
1436 const line_base = try fbr.takeByteSigned();
985 const default_is_stmt = (try fr.takeByte()) != 0;
986 const line_base = try fr.takeByteSigned();
1437987
1438 const line_range = try fbr.takeByte();
988 const line_range = try fr.takeByte();
1439989 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
1445995 var directories: ArrayList(FileEntry) = .empty;
1446996 defer directories.deinit(gpa);
......@@ -1451,17 +1001,17 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
14511001 try directories.append(gpa, .{ .path = compile_unit_cwd });
14521002
14531003 while (true) {
1454 const dir = try fbr.takeSentinel(0);
1004 const dir = try fr.takeSentinel(0);
14551005 if (dir.len == 0) break;
14561006 try directories.append(gpa, .{ .path = dir });
14571007 }
14581008
14591009 while (true) {
1460 const file_name = try fbr.takeSentinel(0);
1010 const file_name = try fr.takeSentinel(0);
14611011 if (file_name.len == 0) break;
1462 const dir_index = try fbr.takeLeb128(u32);
1463 const mtime = try fbr.takeLeb128(u64);
1464 const size = try fbr.takeLeb128(u64);
1012 const dir_index = try fr.takeLeb128(u32);
1013 const mtime = try fr.takeLeb128(u64);
1014 const size = try fr.takeLeb128(u64);
14651015 try file_entries.append(gpa, .{
14661016 .path = file_name,
14671017 .dir_index = dir_index,
......@@ -1476,21 +1026,21 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
14761026 };
14771027 {
14781028 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();
14801030 if (directory_entry_format_count > dir_ent_fmt_buf.len) return bad();
14811031 for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| {
14821032 ent_fmt.* = .{
1483 .content_type_code = try fbr.takeLeb128(u8),
1484 .form_code = try fbr.takeLeb128(u16),
1033 .content_type_code = try fr.takeLeb128(u8),
1034 .form_code = try fr.takeLeb128(u16),
14851035 };
14861036 }
14871037
1488 const directories_count = try fbr.takeLeb128(usize);
1038 const directories_count = try fr.takeLeb128(usize);
14891039
14901040 for (try directories.addManyAsSlice(gpa, directories_count)) |*e| {
14911041 e.* = .{ .path = &.{} };
14921042 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);
14941044 switch (ent_fmt.content_type_code) {
14951045 DW.LNCT.path => e.path = try form_value.getString(d.*),
14961046 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) !
15071057 }
15081058
15091059 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();
15111061 if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad();
15121062 for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| {
15131063 ent_fmt.* = .{
1514 .content_type_code = try fbr.takeLeb128(u16),
1515 .form_code = try fbr.takeLeb128(u16),
1064 .content_type_code = try fr.takeLeb128(u16),
1065 .form_code = try fr.takeLeb128(u16),
15161066 };
15171067 }
15181068
1519 const file_names_count = try fbr.takeLeb128(usize);
1069 const file_names_count = try fr.takeLeb128(usize);
15201070 try file_entries.ensureUnusedCapacity(gpa, file_names_count);
15211071
15221072 for (try file_entries.addManyAsSlice(gpa, file_names_count)) |*e| {
15231073 e.* = .{ .path = &.{} };
15241074 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);
15261076 switch (ent_fmt.content_type_code) {
15271077 DW.LNCT.path => e.path = try form_value.getString(d.*),
15281078 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) !
15421092 var line_table: CompileUnit.SrcLocCache.LineTable = .{};
15431093 errdefer line_table.deinit(gpa);
15441094
1545 fbr.seek = @intCast(prog_start_offset);
1095 fr.seek = @intCast(prog_start_offset);
15461096
15471097 const next_unit_pos = line_info_offset + next_offset;
15481098
1549 while (fbr.seek < next_unit_pos) {
1550 const opcode = try fbr.takeByte();
1099 while (fr.seek < next_unit_pos) {
1100 const opcode = try fr.takeByte();
15511101
15521102 if (opcode == DW.LNS.extended_op) {
1553 const op_size = try fbr.takeLeb128(u64);
1103 const op_size = try fr.takeLeb128(u64);
15541104 if (op_size < 1) return bad();
1555 const sub_op = try fbr.takeByte();
1105 const sub_op = try fr.takeByte();
15561106 switch (sub_op) {
15571107 DW.LNE.end_sequence => {
15581108 // The row being added here is an "end" address, meaning
......@@ -1571,14 +1121,13 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
15711121 prog.reset();
15721122 },
15731123 DW.LNE.set_address => {
1574 const addr = try fbr.takeInt(usize, endian);
1575 prog.address = addr;
1124 prog.address = try readAddress(&fr, endian, addr_size_bytes);
15761125 },
15771126 DW.LNE.define_file => {
1578 const path = try fbr.takeSentinel(0);
1579 const dir_index = try fbr.takeLeb128(u32);
1580 const mtime = try fbr.takeLeb128(u64);
1581 const size = try fbr.takeLeb128(u64);
1127 const path = try fr.takeSentinel(0);
1128 const dir_index = try fr.takeLeb128(u32);
1129 const mtime = try fr.takeLeb128(u64);
1130 const size = try fr.takeLeb128(u64);
15821131 try file_entries.append(gpa, .{
15831132 .path = path,
15841133 .dir_index = dir_index,
......@@ -1586,7 +1135,7 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
15861135 .size = size,
15871136 });
15881137 },
1589 else => try fbr.discardAll64(op_size - 1),
1138 else => try fr.discardAll64(op_size - 1),
15901139 }
15911140 } else if (opcode >= opcode_base) {
15921141 // special opcodes
......@@ -1604,19 +1153,19 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
16041153 prog.basic_block = false;
16051154 },
16061155 DW.LNS.advance_pc => {
1607 const arg = try fbr.takeLeb128(usize);
1156 const arg = try fr.takeLeb128(u64);
16081157 prog.address += arg * minimum_instruction_length;
16091158 },
16101159 DW.LNS.advance_line => {
1611 const arg = try fbr.takeLeb128(i64);
1160 const arg = try fr.takeLeb128(i64);
16121161 prog.line += arg;
16131162 },
16141163 DW.LNS.set_file => {
1615 const arg = try fbr.takeLeb128(usize);
1164 const arg = try fr.takeLeb128(usize);
16161165 prog.file = arg;
16171166 },
16181167 DW.LNS.set_column => {
1619 const arg = try fbr.takeLeb128(u64);
1168 const arg = try fr.takeLeb128(u64);
16201169 prog.column = arg;
16211170 },
16221171 DW.LNS.negate_stmt => {
......@@ -1630,13 +1179,13 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
16301179 prog.address += inc_addr;
16311180 },
16321181 DW.LNS.fixed_advance_pc => {
1633 const arg = try fbr.takeInt(u16, endian);
1182 const arg = try fr.takeInt(u16, endian);
16341183 prog.address += arg;
16351184 },
16361185 DW.LNS.set_prologue_end => {},
16371186 else => {
16381187 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]);
16401189 },
16411190 }
16421191 }
......@@ -1661,18 +1210,19 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
16611210 };
16621211}
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 {
16651214 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);
16671216}
16681217
16691218pub fn getLineNumberInfo(
16701219 d: *Dwarf,
16711220 gpa: Allocator,
1221 endian: Endian,
16721222 compile_unit: *CompileUnit,
16731223 target_address: u64,
16741224) !std.debug.SourceLocation {
1675 try populateSrcLocCache(d, gpa, compile_unit);
1225 try d.populateSrcLocCache(gpa, endian, compile_unit);
16761226 const slc = &compile_unit.src_loc_cache.?;
16771227 const entry = try slc.findSource(target_address);
16781228 const file_index = entry.file - @intFromBool(slc.version < 5);
......@@ -1696,7 +1246,7 @@ fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 {
16961246 return getStringGeneric(di.section(.debug_line_str), offset);
16971247}
16981248
1699fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {
1249fn readDebugAddr(di: Dwarf, endian: Endian, compile_unit: *const CompileUnit, index: u64) !u64 {
17001250 const debug_addr = di.section(.debug_addr) orelse return bad();
17011251
17021252 // 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 {
17051255 // The header is 8 or 12 bytes depending on is_64.
17061256 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);
17091259 if (version != 5) return bad();
17101260
17111261 const addr_size = debug_addr[compile_unit.addr_base - 2];
17121262 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;
17151265 if (byte_offset + addr_size > debug_addr.len) return bad();
17161266 return switch (addr_size) {
1717 1 => debug_addr[byte_offset],
1718 2 => mem.readInt(u16, debug_addr[byte_offset..][0..2], di.endian),
1719 4 => mem.readInt(u32, debug_addr[byte_offset..][0..4], di.endian),
1720 8 => mem.readInt(u64, debug_addr[byte_offset..][0..8], di.endian),
1267 1 => debug_addr[@intCast(byte_offset)],
1268 2 => mem.readInt(u16, debug_addr[@intCast(byte_offset)..][0..2], endian),
1269 4 => mem.readInt(u32, debug_addr[@intCast(byte_offset)..][0..4], endian),
1270 8 => mem.readInt(u64, debug_addr[@intCast(byte_offset)..][0..8], endian),
17211271 else => bad(),
17221272 };
17231273}
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
18251275fn parseFormValue(
18261276 r: *Reader,
18271277 form_id: u64,
18281278 format: Format,
18291279 endian: Endian,
1280 addr_size_bytes: u8,
18301281 implicit_const: ?i64,
18311282) ScanError!FormValue {
18321283 return switch (form_id) {
18331284 // DWARF5.pdf page 213: the size of this value is encoded in the
18341285 // 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) },
18361287 FORM.addrx1 => .{ .addrx = try r.takeByte() },
18371288 FORM.addrx2 => .{ .addrx = try r.takeInt(u16, endian) },
18381289 FORM.addrx3 => .{ .addrx = try r.takeInt(u24, endian) },
18391290 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
18421293 FORM.block1 => .{ .block = try r.take(try r.takeByte()) },
18431294 FORM.block2 => .{ .block = try r.take(try r.takeInt(u16, endian)) },
......@@ -1854,7 +1305,7 @@ fn parseFormValue(
18541305 FORM.exprloc => .{ .exprloc = try r.take(try r.takeLeb128(usize)) },
18551306 FORM.flag => .{ .flag = (try r.takeByte()) != 0 },
18561307 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
18591310 FORM.ref1 => .{ .ref = try r.takeByte() },
18601311 FORM.ref2 => .{ .ref = try r.takeInt(u16, endian) },
......@@ -1862,18 +1313,18 @@ fn parseFormValue(
18621313 FORM.ref8 => .{ .ref = try r.takeInt(u64, endian) },
18631314 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) },
18661317 FORM.ref_sig8 => .{ .ref = try r.takeInt(u64, endian) },
18671318
18681319 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) },
18701321 FORM.strx1 => .{ .strx = try r.takeByte() },
18711322 FORM.strx2 => .{ .strx = try r.takeInt(u16, endian) },
18721323 FORM.strx3 => .{ .strx = try r.takeInt(u24, endian) },
18731324 FORM.strx4 => .{ .strx = try r.takeInt(u32, endian) },
18741325 FORM.strx => .{ .strx = try r.takeLeb128(usize) },
1875 FORM.line_strp => .{ .line_strp = try readAddress(r, format, endian) },
1876 FORM.indirect => parseFormValue(r, try r.takeLeb128(u64), format, endian, implicit_const),
1326 FORM.line_strp => .{ .line_strp = try readFormatSizedInt(r, format, endian) },
1327 FORM.indirect => parseFormValue(r, try r.takeLeb128(u64), format, endian, addr_size_bytes, implicit_const),
18771328 FORM.implicit_const => .{ .sdata = implicit_const orelse return bad() },
18781329 FORM.loclistx => .{ .loclistx = try r.takeLeb128(u64) },
18791330 FORM.rnglistx => .{ .rnglistx = try r.takeLeb128(u64) },
......@@ -1946,7 +1397,7 @@ const UnitHeader = struct {
19461397 unit_length: u64,
19471398};
19481399
1949fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader {
1400pub fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader {
19501401 return switch (try r.takeInt(u32, endian)) {
19511402 0...0xfffffff0 - 1 => |unit_length| .{
19521403 .format = .@"32",
......@@ -1963,7 +1414,7 @@ fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader {
19631414}
19641415
19651416/// 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 {
19671418 return switch (unwind_reg_number) {
19681419 1 => 3, // RBX
19691420 2 => 12, // R12
......@@ -1971,7 +1422,61 @@ pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {
19711422 4 => 14, // R14
19721423 5 => 15, // R15
19731424 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,
19751480 };
19761481}
19771482
......@@ -1982,11 +1487,11 @@ pub fn bad() error{InvalidDebugInfo} {
19821487 return error.InvalidDebugInfo;
19831488}
19841489
1985fn invalidDebugInfoDetected() void {
1490pub fn invalidDebugInfoDetected() void {
19861491 if (debug_debug_mode) @panic("bad dwarf");
19871492}
19881493
1989fn missing() error{MissingDebugInfo} {
1494pub fn missing() error{MissingDebugInfo} {
19901495 if (debug_debug_mode) @panic("missing dwarf");
19911496 return error.MissingDebugInfo;
19921497}
......@@ -2000,460 +1505,41 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
20001505 return str[casted_offset..last :0];
20011506}
20021507
2003const EhPointerContext = struct {
2004 // The address of the pointer field itself
2005 pc_rel_base: u64,
2006
2007 // Whether or not to follow indirect pointers. This should only be
2008 // used when decoding pointers at runtime using the current process's
2009 // debug info
2010 follow_indirect: bool,
2011
2012 // These relative addressing modes are only used in specific cases, and
2013 // might not be available / required in all parsing contexts
2014 data_rel_base: ?u64 = null,
2015 text_rel_base: ?u64 = null,
2016 function_rel_base: ?u64 = null,
2017};
2018
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,
1508pub fn getSymbol(di: *Dwarf, gpa: Allocator, endian: Endian, address: u64) !std.debug.Symbol {
1509 const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) {
1510 error.MissingDebugInfo, error.InvalidDebugInfo => return .unknown,
1511 else => return err,
20601512 };
2061
2062 if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) {
2063 if (@sizeOf(usize) != addr_size_bytes) {
2064 // See the documentation for `follow_indirect`
2065 return error.NonNativeIndirection;
2066 }
2067
2068 const native_ptr = cast(usize, ptr) orelse return error.PointerOverflow;
2069 return switch (addr_size_bytes) {
2070 2, 4, 8 => return @as(*const usize, @ptrFromInt(native_ptr)).*,
2071 else => return error.UnsupportedAddrSize,
2072 };
2073 } else {
2074 return ptr;
2075 }
2076}
2077
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(),
1513 return .{
1514 .name = di.getSymbolName(address),
1515 .compile_unit_name = compile_unit.die.getAttrString(di, endian, std.dwarf.AT.name, di.section(.debug_str), compile_unit) catch |err| switch (err) {
1516 error.MissingDebugInfo, error.InvalidDebugInfo => null,
1517 },
1518 .source_location = di.getLineNumberInfo(gpa, endian, compile_unit, address) catch |err| switch (err) {
1519 error.MissingDebugInfo, error.InvalidDebugInfo => null,
23901520 else => return err,
2391 };
2392 defer elf_file.close();
2393
2394 const end_pos = elf_file.getEndPos() catch return bad();
2395 const file_len = cast(usize, end_pos) orelse return error.Overflow;
2396
2397 const mapped_mem = std.posix.mmap(
2398 null,
2399 file_len,
2400 std.posix.PROT.READ,
2401 .{ .TYPE = .SHARED },
2402 elf_file.handle,
2403 0,
2404 ) catch |err| switch (err) {
2405 error.MappingAlreadyExists => unreachable,
2406 else => |e| return e,
2407 };
2408 errdefer std.posix.munmap(mapped_mem);
2409
2410 return load(
2411 gpa,
2412 mapped_mem,
2413 build_id,
2414 expected_crc,
2415 parent_sections,
2416 parent_mapped_mem,
2417 elf_file_path.sub_path,
2418 );
2419 }
2420};
2421
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];
1521 },
1522 };
24441523}
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 {
24471532 return switch (format) {
24481533 .@"32" => try r.takeInt(u32, endian),
24491534 .@"64" => try r.takeInt(u64, endian),
24501535 };
24511536}
24521537
2453fn nativeFormat() std.dwarf.Format {
2454 return switch (@sizeOf(usize)) {
2455 4 => .@"32",
2456 8 => .@"64",
2457 else => @compileError("unsupported @sizeOf(usize)"),
1538fn readAddress(r: *Reader, endian: Endian, addr_size_bytes: u8) !u64 {
1539 return switch (addr_size_bytes) {
1540 2 => try r.takeInt(u16, endian),
1541 4 => try r.takeInt(u32, endian),
1542 8 => try r.takeInt(u64, endian),
1543 else => return bad(),
24581544 };
24591545}
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();
55const std = @import("std");
66const leb = std.leb;
77const OP = std.dwarf.OP;
8const abi = std.debug.Dwarf.abi;
98const mem = std.mem;
109const assert = std.debug.assert;
1110const testing = std.testing;
1211const 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
1419/// Expressions can be evaluated in different contexts, each requiring its own set of inputs.
1520/// Callers should specify all the fields relevant to their context. If a field is required
1621/// by the expression and it isn't in the context, error.IncompleteExpressionContext is returned.
......@@ -23,9 +28,7 @@ pub const Context = struct {
2328 object_address: ?*const anyopaque = null,
2429 /// .debug_addr section
2530 debug_addr: ?[]const u8 = null,
26 /// Thread context
27 thread_context: ?*std.debug.ThreadContext = null,
28 reg_context: ?abi.RegisterContext = null,
31 cpu_context: ?*std.debug.cpu_context.Native = null,
2932 /// Call frame address, if in a CFI context
3033 cfa: ?usize = null,
3134 /// This expression is a sub-expression from an OP.entry_value instruction
......@@ -62,7 +65,9 @@ pub const Error = error{
6265 InvalidTypeLength,
6366
6467 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
6772/// A stack machine that can decode and run DWARF expressions.
6873/// Expressions can be decoded for non-native address size and endianness,
......@@ -369,29 +374,20 @@ pub fn StackMachine(comptime options: Options) type {
369374 OP.breg0...OP.breg31,
370375 OP.bregx,
371376 => {
372 if (context.thread_context == null) return error.IncompleteExpressionContext;
373
374 const base_register = operand.?.base_register;
375 var value: i64 = @intCast(mem.readInt(usize, (try abi.regBytes(
376 context.thread_context.?,
377 base_register.base_register,
378 context.reg_context,
379 ))[0..@sizeOf(usize)], native_endian));
380 value += base_register.offset;
381 try self.stack.append(allocator, .{ .generic = @intCast(value) });
377 const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext;
378
379 const br = operand.?.base_register;
380 const value: i64 = @intCast((try regNative(cpu_context, br.base_register)).*);
381 try self.stack.append(allocator, .{ .generic = @intCast(value + br.offset) });
382382 },
383383 OP.regval_type => {
384 const register_type = operand.?.register_type;
385 const value = mem.readInt(usize, (try abi.regBytes(
386 context.thread_context.?,
387 register_type.register,
388 context.reg_context,
389 ))[0..@sizeOf(usize)], native_endian);
384 const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext;
385 const rt = operand.?.register_type;
390386 try self.stack.append(allocator, .{
391387 .regval_type = .{
392 .type_offset = register_type.type_offset,
388 .type_offset = rt.type_offset,
393389 .type_size = @sizeOf(addr_type),
394 .value = value,
390 .value = (try regNative(cpu_context, rt.register)).*,
395391 },
396392 });
397393 },
......@@ -734,14 +730,14 @@ pub fn StackMachine(comptime options: Options) type {
734730
735731 // TODO: The spec states that this sub-expression needs to observe the state (ie. registers)
736732 // 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
739735 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
742738 var block_stream: std.Io.Reader = .fixed(block);
743739 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)).*;
745741 try self.stack.append(allocator, .{ .generic = value });
746742 } else {
747743 var stack_machine: Self = .{};
......@@ -1149,55 +1145,39 @@ test "basics" {
11491145 }
11501146
11511147 // Register values
1152 if (@sizeOf(std.debug.ThreadContext) != 0) {
1148 if (std.debug.cpu_context.Native != noreturn) {
11531149 stack_machine.reset();
11541150 program.clearRetainingCapacity();
11551151
1156 const reg_context = abi.RegisterContext{
1157 .eh_frame = true,
1158 .is_macho = builtin.os.tag == .macos,
1159 };
1160 var thread_context: std.debug.ThreadContext = undefined;
1161 std.debug.relocateContext(&thread_context);
1152 var cpu_context: std.debug.cpu_context.Native = undefined;
11621153 const context = Context{
1163 .thread_context = &thread_context,
1164 .reg_context = reg_context,
1154 .cpu_context = &cpu_context,
11651155 };
11661156
1167 // Only test register operations on arch / os that have them implemented
1168 if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| {
1169
1170 // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it
1171
1172 mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
1173 (try abi.regValueNative(&thread_context, abi.fpRegNum(native_arch, reg_context), reg_context)).* = 1;
1174 (try abi.regValueNative(&thread_context, abi.spRegNum(native_arch, reg_context), reg_context)).* = 2;
1175 (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch).?, reg_context)).* = 3;
1176
1177 try b.writeBreg(writer, abi.fpRegNum(native_arch, reg_context), @as(usize, 100));
1178 try b.writeBreg(writer, abi.spRegNum(native_arch, reg_context), @as(usize, 200));
1179 try b.writeBregx(writer, abi.ipRegNum(native_arch).?, @as(usize, 300));
1180 try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400));
1181
1182 _ = try stack_machine.run(program.written(), allocator, context, 0);
1183
1184 const regval_type = stack_machine.stack.pop().?.regval_type;
1185 try testing.expectEqual(@as(usize, 400), regval_type.type_offset);
1186 try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size);
1187 try testing.expectEqual(@as(usize, 0xee), regval_type.value);
1188
1189 try testing.expectEqual(@as(usize, 303), stack_machine.stack.pop().?.generic);
1190 try testing.expectEqual(@as(usize, 202), stack_machine.stack.pop().?.generic);
1191 try testing.expectEqual(@as(usize, 101), stack_machine.stack.pop().?.generic);
1192 } else |err| {
1193 switch (err) {
1194 error.UnimplementedArch,
1195 error.UnimplementedOs,
1196 error.ThreadContextNotSupported,
1197 => {},
1198 else => return err,
1199 }
1200 }
1157 const reg_bytes = try cpu_context.dwarfRegisterBytes(0);
1158
1159 // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it
1160
1161 mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
1162 (try regNative(&cpu_context, fp_reg_num)).* = 1;
1163 (try regNative(&cpu_context, sp_reg_num)).* = 2;
1164 (try regNative(&cpu_context, ip_reg_num)).* = 3;
1165
1166 try b.writeBreg(writer, fp_reg_num, @as(usize, 100));
1167 try b.writeBreg(writer, sp_reg_num, @as(usize, 200));
1168 try b.writeBregx(writer, ip_reg_num, @as(usize, 300));
1169 try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400));
1170
1171 _ = try stack_machine.run(program.written(), allocator, context, 0);
1172
1173 const regval_type = stack_machine.stack.pop().?.regval_type;
1174 try testing.expectEqual(@as(usize, 400), regval_type.type_offset);
1175 try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size);
1176 try testing.expectEqual(@as(usize, 0xee), regval_type.value);
1177
1178 try testing.expectEqual(@as(usize, 303), stack_machine.stack.pop().?.generic);
1179 try testing.expectEqual(@as(usize, 202), stack_machine.stack.pop().?.generic);
1180 try testing.expectEqual(@as(usize, 101), stack_machine.stack.pop().?.generic);
12011181 }
12021182
12031183 // Stack operations
......@@ -1585,38 +1565,21 @@ test "basics" {
15851565 }
15861566
15871567 // Register location description
1588 const reg_context = abi.RegisterContext{
1589 .eh_frame = true,
1590 .is_macho = builtin.os.tag == .macos,
1591 };
1592 var thread_context: std.debug.ThreadContext = undefined;
1593 std.debug.relocateContext(&thread_context);
1594 context = Context{
1595 .thread_context = &thread_context,
1596 .reg_context = reg_context,
1597 };
1568 var cpu_context: std.debug.cpu_context.Native = undefined;
1569 context = .{ .cpu_context = &cpu_context };
15981570
1599 if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| {
1600 mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
1571 const reg_bytes = try cpu_context.dwarfRegisterBytes(0);
1572 mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
16011573
1602 var sub_program: std.Io.Writer.Allocating = .init(allocator);
1603 defer sub_program.deinit();
1604 const sub_writer = &sub_program.writer;
1605 try b.writeReg(sub_writer, 0);
1574 var sub_program: std.Io.Writer.Allocating = .init(allocator);
1575 defer sub_program.deinit();
1576 const sub_writer = &sub_program.writer;
1577 try b.writeReg(sub_writer, 0);
16061578
1607 stack_machine.reset();
1608 program.clearRetainingCapacity();
1609 try b.writeEntryValue(writer, sub_program.written());
1610 _ = try stack_machine.run(program.written(), allocator, context, null);
1611 try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.pop().?.generic);
1612 } else |err| {
1613 switch (err) {
1614 error.UnimplementedArch,
1615 error.UnimplementedOs,
1616 error.ThreadContextNotSupported,
1617 => {},
1618 else => return err,
1619 }
1620 }
1579 stack_machine.reset();
1580 program.clearRetainingCapacity();
1581 try b.writeEntryValue(writer, sub_program.written());
1582 _ = try stack_machine.run(program.written(), allocator, context, null);
1583 try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.pop().?.generic);
16211584 }
16221585}
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 @@
99const std = @import("../std.zig");
1010const Allocator = std.mem.Allocator;
1111const Path = std.Build.Cache.Path;
12const Dwarf = std.debug.Dwarf;
12const ElfFile = std.debug.ElfFile;
1313const assert = std.debug.assert;
1414const Coverage = std.debug.Coverage;
1515const SourceLocation = std.debug.Coverage.SourceLocation;
......@@ -17,27 +17,35 @@ const SourceLocation = std.debug.Coverage.SourceLocation;
1717const Info = @This();
1818
1919/// Sorted by key, ascending.
20address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule),
20address_map: std.AutoArrayHashMapUnmanaged(u64, ElfFile),
2121/// Externally managed, outlives this `Info` instance.
2222coverage: *Coverage,
2323
24pub const LoadError = Dwarf.ElfModule.LoadError;
24pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || std.debug.Dwarf.ScanError || error{MissingDebugInfo};
2525
2626pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info {
27 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
28 var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, &sections, null);
29 try elf_module.dwarf.populateRanges(gpa);
27 var file = try path.root_dir.handle.openFile(path.sub_path, .{});
28 defer file.close();
29
30 var elf_file: ElfFile = try .load(gpa, file, null, &.none);
31 errdefer elf_file.deinit(gpa);
32
33 if (elf_file.dwarf == null) return error.MissingDebugInfo;
34 try elf_file.dwarf.?.open(gpa, elf_file.endian);
35 try elf_file.dwarf.?.populateRanges(gpa, elf_file.endian);
36
3037 var info: Info = .{
3138 .address_map = .{},
3239 .coverage = coverage,
3340 };
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
3543 return info;
3644}
3745
3846pub fn deinit(info: *Info, gpa: Allocator) void {
39 for (info.address_map.values()) |*elf_module| {
40 elf_module.dwarf.deinit(gpa);
47 for (info.address_map.values()) |*elf_file| {
48 elf_file.dwarf.?.deinit(gpa);
4149 }
4250 info.address_map.deinit(gpa);
4351 info.* = undefined;
......@@ -57,6 +65,6 @@ pub fn resolveAddresses(
5765) ResolveAddressesError!void {
5866 assert(sorted_pc_addrs.len == output.len);
5967 if (info.address_map.entries.len != 1) @panic("TODO");
60 const elf_module = &info.address_map.values()[0];
61 return info.coverage.resolveAddressesDwarf(gpa, sorted_pc_addrs, output, &elf_module.dwarf);
68 const elf_file = &info.address_map.values()[0];
69 return info.coverage.resolveAddressesDwarf(gpa, elf_file.endian, sorted_pc_addrs, output, &elf_file.dwarf.?);
6270}
lib/std/debug/Pdb.zig+19-18
......@@ -171,6 +171,7 @@ pub fn parseInfoStream(self: *Pdb) !void {
171171 const string_table_index = str_tab_index: {
172172 const name_bytes_len = try reader.takeInt(u32, .little);
173173 const name_bytes = try reader.readAlloc(gpa, name_bytes_len);
174 defer gpa.free(name_bytes);
174175
175176 const HashTableHeader = extern struct {
176177 size: u32,
......@@ -412,8 +413,7 @@ const Msf = struct {
412413 return error.InvalidDebugInfo;
413414 if (superblock.free_block_map_block != 1 and superblock.free_block_map_block != 2)
414415 return error.InvalidDebugInfo;
415 const file_len = try file_reader.getSize();
416 if (superblock.num_blocks * superblock.block_size != file_len)
416 if (superblock.num_blocks * superblock.block_size != try file_reader.getSize())
417417 return error.InvalidDebugInfo;
418418 switch (superblock.block_size) {
419419 // llvm only supports 4096 but we can handle any of these values
......@@ -427,6 +427,7 @@ const Msf = struct {
427427
428428 try file_reader.seekTo(superblock.block_size * superblock.block_map_addr);
429429 const dir_blocks = try gpa.alloc(u32, dir_block_count);
430 errdefer gpa.free(dir_blocks);
430431 for (dir_blocks) |*b| {
431432 b.* = try file_reader.interface.takeInt(u32, .little);
432433 }
......@@ -450,25 +451,25 @@ const Msf = struct {
450451 const streams = try gpa.alloc(MsfStream, stream_count);
451452 errdefer gpa.free(streams);
452453
453 for (streams, 0..) |*stream, i| {
454 const size = stream_sizes[i];
454 for (streams, stream_sizes) |*stream, size| {
455455 if (size == 0) {
456456 stream.* = .empty;
457 } else {
458 const blocks = try gpa.alloc(u32, size);
459 errdefer gpa.free(blocks);
460 for (blocks) |*block| {
461 const block_id = try directory.interface.takeInt(u32, .little);
462 const n = (block_id % superblock.block_size);
463 // 0 is for pdb.SuperBlock, 1 and 2 for FPMs.
464 if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.block_size > file_len)
465 return error.InvalidBlockIndex;
466 block.* = block_id;
467 }
468 const buffer = try gpa.alloc(u8, 64);
469 errdefer gpa.free(buffer);
470 stream.* = .init(superblock.block_size, file_reader, blocks, buffer);
457 continue;
458 }
459 const blocks = try gpa.alloc(u32, size);
460 errdefer gpa.free(blocks);
461 for (blocks) |*block| {
462 const block_id = try directory.interface.takeInt(u32, .little);
463 // Index 0 is reserved for the superblock.
464 // In theory, every page which is `n * block_size + 1` or `n * block_size + 2`
465 // is also reserved, for one of the FPMs. However, LLVM has been observed to map
466 // these into actual streams, so allow it for compatibility.
467 if (block_id == 0 or block_id >= superblock.num_blocks) return error.InvalidBlockIndex;
468 block.* = block_id;
471469 }
470 const buffer = try gpa.alloc(u8, 64);
471 errdefer gpa.free(buffer);
472 stream.* = .init(superblock.block_size, file_reader, blocks, buffer);
472473 }
473474
474475 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 @@
1pub const PE = struct {
2 pub const absptr = 0x00;
1pub const PE = packed struct(u8) {
2 type: Type,
3 rel: Rel,
4 /// Undocumented GCC extension
5 indirect: bool = false,
36
4 pub const size_mask = 0x7;
5 pub const sign_mask = 0x8;
6 pub const type_mask = size_mask | sign_mask;
7 /// This is a special encoding which does not correspond to named `type`/`rel` values.
8 pub const omit: PE = @bitCast(@as(u8, 0xFF));
79
8 pub const uleb128 = 0x01;
9 pub const udata2 = 0x02;
10 pub const udata4 = 0x03;
11 pub const udata8 = 0x04;
12 pub const sleb128 = 0x09;
13 pub const sdata2 = 0x0A;
14 pub const sdata4 = 0x0B;
15 pub const sdata8 = 0x0C;
10 pub const Type = enum(u4) {
11 absptr = 0x0,
12 uleb128 = 0x1,
13 udata2 = 0x2,
14 udata4 = 0x3,
15 udata8 = 0x4,
16 sleb128 = 0x9,
17 sdata2 = 0xA,
18 sdata4 = 0xB,
19 sdata8 = 0xC,
20 _,
21 };
1622
17 pub const rel_mask = 0x70;
18 pub const pcrel = 0x10;
19 pub const textrel = 0x20;
20 pub const datarel = 0x30;
21 pub const funcrel = 0x40;
22 pub const aligned = 0x50;
23
24 pub const indirect = 0x80;
25
26 pub const omit = 0xff;
23 /// The specification considers this a `u4`, but the GCC `indirect` field extension conflicts
24 /// with that, so we consider it a `u3` instead.
25 pub const Rel = enum(u3) {
26 abs = 0x0,
27 pcrel = 0x1,
28 textrel = 0x2,
29 datarel = 0x3,
30 funcrel = 0x4,
31 aligned = 0x5,
32 _,
33 };
2734};
lib/std/dynamic_library.zig+1-2
......@@ -95,8 +95,7 @@ pub fn get_DYNAMIC() ?[*]const elf.Dyn {
9595pub fn linkmap_iterator(phdrs: []const elf.Phdr) error{InvalidExe}!LinkMap.Iterator {
9696 _ = phdrs;
9797 const _DYNAMIC = get_DYNAMIC() orelse {
98 // No PT_DYNAMIC means this is either a statically-linked program or a
99 // badly corrupted dynamically-linked one.
98 // No PT_DYNAMIC means this is a statically-linked non-PIE program.
10099 return .{ .current = null };
101100 };
102101
lib/std/elf.zig+2-1
......@@ -744,7 +744,8 @@ pub const SectionHeaderBufferIterator = struct {
744744
745745 const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);
746746 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
749750 return takeShdr(&reader, it.elf_header);
750751 }
lib/std/heap/PageAllocator.zig+1-1
......@@ -183,7 +183,7 @@ pub fn realloc(uncasted_memory: []u8, new_len: usize, may_move: bool) ?[*]u8 {
183183
184184 if (posix.MREMAP != void) {
185185 // 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;
187187 return new_memory.ptr;
188188 }
189189
lib/std/heap/debug_allocator.zig+13-38
......@@ -505,23 +505,14 @@ pub fn DebugAllocator(comptime config: Config) type {
505505 return if (leaks) .leak else .ok;
506506 }
507507
508 fn collectStackTrace(first_trace_addr: usize, addresses: *[stack_n]usize) void {
509 if (stack_n == 0) return;
510 @memset(addresses, 0);
511 var stack_trace: StackTrace = .{
512 .instruction_addresses = addresses,
513 .index = 0,
514 };
515 std.debug.captureStackTrace(first_trace_addr, &stack_trace);
508 fn collectStackTrace(first_trace_addr: usize, addr_buf: *[stack_n]usize) void {
509 const st = std.debug.captureCurrentStackTrace(.{ .first_address = first_trace_addr }, addr_buf);
510 @memset(addr_buf[@min(st.index, addr_buf.len)..], 0);
516511 }
517512
518513 fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {
519 var addresses: [stack_n]usize = @splat(0);
520 var second_free_stack_trace: StackTrace = .{
521 .instruction_addresses = &addresses,
522 .index = 0,
523 };
524 std.debug.captureStackTrace(ret_addr, &second_free_stack_trace);
514 var addr_buf: [stack_n]usize = undefined;
515 const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
525516 log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{
526517 alloc_stack_trace, free_stack_trace, second_free_stack_trace,
527518 });
......@@ -562,12 +553,8 @@ pub fn DebugAllocator(comptime config: Config) type {
562553 }
563554
564555 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
565 var addresses: [stack_n]usize = [1]usize{0} ** stack_n;
566 var free_stack_trace: StackTrace = .{
567 .instruction_addresses = &addresses,
568 .index = 0,
569 };
570 std.debug.captureStackTrace(ret_addr, &free_stack_trace);
556 var addr_buf: [stack_n]usize = undefined;
557 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
571558 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
572559 entry.value_ptr.bytes.len,
573560 old_mem.len,
......@@ -672,12 +659,8 @@ pub fn DebugAllocator(comptime config: Config) type {
672659 }
673660
674661 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
675 var addresses: [stack_n]usize = [1]usize{0} ** stack_n;
676 var free_stack_trace = StackTrace{
677 .instruction_addresses = &addresses,
678 .index = 0,
679 };
680 std.debug.captureStackTrace(ret_addr, &free_stack_trace);
662 var addr_buf: [stack_n]usize = undefined;
663 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
681664 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
682665 entry.value_ptr.bytes.len,
683666 old_mem.len,
......@@ -900,12 +883,8 @@ pub fn DebugAllocator(comptime config: Config) type {
900883 if (requested_size == 0) @panic("Invalid free");
901884 const slot_alignment = bucket.log2PtrAligns(slot_count)[slot_index];
902885 if (old_memory.len != requested_size or alignment != slot_alignment) {
903 var addresses: [stack_n]usize = [1]usize{0} ** stack_n;
904 var free_stack_trace: StackTrace = .{
905 .instruction_addresses = &addresses,
906 .index = 0,
907 };
908 std.debug.captureStackTrace(return_address, &free_stack_trace);
886 var addr_buf: [stack_n]usize = undefined;
887 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
909888 if (old_memory.len != requested_size) {
910889 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
911890 requested_size,
......@@ -999,12 +978,8 @@ pub fn DebugAllocator(comptime config: Config) type {
999978 if (requested_size == 0) @panic("Invalid free");
1000979 const slot_alignment = bucket.log2PtrAligns(slot_count)[slot_index];
1001980 if (memory.len != requested_size or alignment != slot_alignment) {
1002 var addresses: [stack_n]usize = [1]usize{0} ** stack_n;
1003 var free_stack_trace: StackTrace = .{
1004 .instruction_addresses = &addresses,
1005 .index = 0,
1006 };
1007 std.debug.captureStackTrace(return_address, &free_stack_trace);
981 var addr_buf: [stack_n]usize = undefined;
982 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
1008983 if (memory.len != requested_size) {
1009984 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
1010985 requested_size,
lib/std/macho.zig+66-53
......@@ -839,62 +839,75 @@ pub const nlist = extern struct {
839839
840840pub const nlist_64 = extern struct {
841841 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 },
843891 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 },
845907 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
895909 pub fn tentative(sym: nlist_64) bool {
896 if (!sym.undf()) return false;
897 return sym.n_value != 0;
910 return sym.n_type.bits.type == .undf and sym.n_value != 0;
898911 }
899912};
900913
......@@ -2046,7 +2059,7 @@ pub const unwind_info_compressed_second_level_page_header = extern struct {
20462059 // encodings array
20472060};
20482061
2049pub const UnwindInfoCompressedEntry = packed struct {
2062pub const UnwindInfoCompressedEntry = packed struct(u32) {
20502063 funcOffset: u24,
20512064 encodingIndex: u8,
20522065};
lib/std/os/freebsd.zig+73
......@@ -3,6 +3,7 @@ const fd_t = std.c.fd_t;
33const off_t = std.c.off_t;
44const unexpectedErrno = std.posix.unexpectedErrno;
55const errno = std.posix.errno;
6const builtin = @import("builtin");
67
78pub const CopyFileRangeError = std.posix.UnexpectedError || error{
89 /// 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,
4748 else => |err| return unexpectedErrno(err),
4849 }
4950}
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) {
4949 .s390x => @import("linux/s390x.zig"),
5050 else => struct {
5151 pub const ucontext_t = void;
52 pub const getcontext = {};
5352 },
5453};
5554
......@@ -112,7 +111,6 @@ pub const timeval = arch_bits.timeval;
112111pub const timezone = arch_bits.timezone;
113112pub const ucontext_t = arch_bits.ucontext_t;
114113pub const user_desc = arch_bits.user_desc;
115pub const getcontext = arch_bits.getcontext;
116114
117115pub const tls = @import("linux/tls.zig");
118116pub const BPF = @import("linux/bpf.zig");
lib/std/os/linux/aarch64.zig-3
......@@ -260,7 +260,4 @@ pub const ucontext_t = extern struct {
260260 mcontext: mcontext_t,
261261};
262262
263/// TODO
264pub const getcontext = {};
265
266263pub const Elf_Symndx = u32;
lib/std/os/linux/arm.zig-3
......@@ -310,7 +310,4 @@ pub const ucontext_t = extern struct {
310310 regspace: [64]u64,
311311};
312312
313/// TODO
314pub const getcontext = {};
315
316313pub const Elf_Symndx = u32;
lib/std/os/linux/hexagon.zig-3
......@@ -237,6 +237,3 @@ pub const VDSO = void;
237237
238238/// TODO
239239pub 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 {
250250};
251251
252252pub 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;
258258
259259/// TODO
260260pub 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;
349349
350350/// TODO
351351pub 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;
328328
329329/// TODO
330330pub 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 {
381381};
382382
383383pub 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 {
376376};
377377
378378pub 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 {
255255 sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
256256 mcontext: mcontext_t,
257257};
258
259/// TODO
260pub const getcontext = {};
lib/std/os/linux/riscv64.zig-3
......@@ -255,6 +255,3 @@ pub const ucontext_t = extern struct {
255255 sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
256256 mcontext: mcontext_t,
257257};
258
259/// TODO
260pub const getcontext = {};
lib/std/os/linux/s390x.zig-3
......@@ -273,6 +273,3 @@ pub const mcontext_t = extern struct {
273273 __regs2: [18]u32,
274274 __regs3: [16]f64,
275275};
276
277/// TODO
278pub const getcontext = {};
lib/std/os/linux/sparc64.zig-3
......@@ -426,6 +426,3 @@ pub const ucontext_t = extern struct {
426426 stack: stack_t,
427427 sigset: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
428428};
429
430/// TODO
431pub const getcontext = {};
lib/std/os/linux/x86.zig+15-25
......@@ -80,28 +80,32 @@ pub fn syscall6(
8080 arg5: usize,
8181 arg6: usize,
8282) usize {
83 // arg5/arg6 are passed via memory as we're out of registers if ebp is used as frame pointer, or
84 // if we're compiling with PIC. We push arg5/arg6 on the stack before changing ebp/esp as the
85 // compiler may reference arg5/arg6 as an offset relative to ebp/esp.
83 // arg6 can't be passed to asm in a register because ebp might be reserved as the frame pointer
84 // and there are no more GPRs available; so we'll need a memory operand for it. Adding that
85 // memory operand means that on PIC we might need a reference to the GOT, which in turn needs
86 // *its* own GPR, so we need to pass another arg in memory too! This is surprisingly hard to get
87 // right, because we can't touch esp or ebp until we're done with the memory input (as that
88 // input could be relative to esp or ebp).
89 const args56: [2]usize = .{ arg5, arg6 };
8690 return asm volatile (
87 \\ push %[arg5]
88 \\ push %[arg6]
89 \\ push %%edi
91 \\ push %[args56]
9092 \\ push %%ebp
91 \\ mov 12(%%esp), %%edi
92 \\ mov 8(%%esp), %%ebp
93 \\ mov 4(%%esp), %%ebp
94 \\ mov %%edi, 4(%%esp)
95 \\ // The saved %edi and %ebp are on the stack, and %ebp points to `args56`.
96 \\ // Prepare the last two args, syscall, then pop the saved %ebp and %edi.
97 \\ mov (%%ebp), %%edi
98 \\ mov 4(%%ebp), %%ebp
9399 \\ int $0x80
94100 \\ pop %%ebp
95101 \\ pop %%edi
96 \\ add $8, %%esp
97102 : [ret] "={eax}" (-> usize),
98103 : [number] "{eax}" (@intFromEnum(number)),
99104 [arg1] "{ebx}" (arg1),
100105 [arg2] "{ecx}" (arg2),
101106 [arg3] "{edx}" (arg3),
102107 [arg4] "{esi}" (arg4),
103 [arg5] "rm" (arg5),
104 [arg6] "rm" (arg6),
108 [args56] "rm" (&args56),
105109 : .{ .memory = true });
106110}
107111
......@@ -432,17 +436,3 @@ pub fn getContextInternal() callconv(.naked) usize {
432436 [sigset_size] "i" (linux.NSIG / 8),
433437 : .{ .cc = true, .memory = true, .eax = true, .ecx = true, .edx = true });
434438}
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 {
352352 sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a glibc-compatible (1024-bit) sigmask.
353353 fpregs_mem: [64]usize, // Not part of kernel ABI, only part of glibc ucontext_t
354354};
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 {
28492849 std.debug.print("error.Unexpected: GetLastError({d}): {f}\n", .{
28502850 err, std.unicode.fmtUtf16Le(buf_wstr[0..len]),
28512851 });
2852 std.debug.dumpCurrentStackTrace(@returnAddress());
2852 std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
28532853 }
28542854 return error.Unexpected;
28552855}
......@@ -2863,7 +2863,7 @@ pub fn unexpectedWSAError(err: ws2_32.WinsockError) UnexpectedError {
28632863pub fn unexpectedStatus(status: NTSTATUS) UnexpectedError {
28642864 if (std.posix.unexpected_error_tracing) {
28652865 std.debug.print("error.Unexpected NTSTATUS=0x{x}\n", .{@intFromEnum(status)});
2866 std.debug.dumpCurrentStackTrace(@returnAddress());
2866 std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
28672867 }
28682868 return error.Unexpected;
28692869}
lib/std/posix.zig+2-5
......@@ -47,7 +47,6 @@ else switch (native_os) {
4747 .linux => linux,
4848 .plan9 => std.os.plan9,
4949 else => struct {
50 pub const ucontext_t = void;
5150 pub const pid_t = void;
5251 pub const pollfd = void;
5352 pub const fd_t = void;
......@@ -141,7 +140,6 @@ pub const in_pktinfo = system.in_pktinfo;
141140pub const in6_pktinfo = system.in6_pktinfo;
142141pub const ino_t = system.ino_t;
143142pub const linger = system.linger;
144pub const mcontext_t = system.mcontext_t;
145143pub const mode_t = system.mode_t;
146144pub const msghdr = system.msghdr;
147145pub const msghdr_const = system.msghdr_const;
......@@ -170,7 +168,6 @@ pub const timespec = system.timespec;
170168pub const timestamp_t = system.timestamp_t;
171169pub const timeval = system.timeval;
172170pub const timezone = system.timezone;
173pub const ucontext_t = system.ucontext_t;
174171pub const uid_t = system.uid_t;
175172pub const user_desc = system.user_desc;
176173pub const utsname = system.utsname;
......@@ -692,7 +689,7 @@ pub fn abort() noreturn {
692689 // even when linking libc on Windows we use our own abort implementation.
693690 // See https://github.com/ziglang/zig/issues/2071 for more details.
694691 if (native_os == .windows) {
695 if (builtin.mode == .Debug) {
692 if (builtin.mode == .Debug and windows.peb().BeingDebugged != 0) {
696693 @breakpoint();
697694 }
698695 windows.kernel32.ExitProcess(3);
......@@ -7590,7 +7587,7 @@ pub const UnexpectedError = error{
75907587pub fn unexpectedErrno(err: E) UnexpectedError {
75917588 if (unexpected_error_tracing) {
75927589 std.debug.print("unexpected errno: {d}\n", .{@intFromEnum(err)});
7593 std.debug.dumpCurrentStackTrace(null);
7590 std.debug.dumpCurrentStackTrace(.{});
75947591 }
75957592 return error.Unexpected;
75967593}
lib/std/start.zig+5-2
......@@ -635,8 +635,11 @@ pub inline fn callMain() u8 {
635635 else => {},
636636 }
637637 std.log.err("{s}", .{@errorName(err)});
638 if (@errorReturnTrace()) |trace| {
639 std.debug.dumpStackTrace(trace.*);
638 switch (native_os) {
639 .freestanding, .other => {},
640 else => if (@errorReturnTrace()) |trace| {
641 std.debug.dumpStackTrace(trace);
642 },
640643 }
641644 return 1;
642645 };
lib/std/std.zig+16
......@@ -171,6 +171,22 @@ pub const Options = struct {
171171 http_enable_ssl_key_log_file: bool = @import("builtin").mode == .Debug,
172172
173173 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,
174190};
175191
176192// 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(
6464 const self: *FailingAllocator = @ptrCast(@alignCast(ctx));
6565 if (self.alloc_index == self.fail_index) {
6666 if (!self.has_induced_failure) {
67 @memset(&self.stack_addresses, 0);
68 var stack_trace = std.builtin.StackTrace{
69 .instruction_addresses = &self.stack_addresses,
70 .index = 0,
71 };
72 std.debug.captureStackTrace(return_address, &stack_trace);
67 const st = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &self.stack_addresses);
68 @memset(self.stack_addresses[@min(st.index, self.stack_addresses.len)..], 0);
7369 self.has_induced_failure = true;
7470 }
7571 return null;
lib/zig.h+8
......@@ -1510,8 +1510,16 @@ static inline zig_u128 zig_shl_u128(zig_u128 lhs, uint8_t rhs) {
15101510}
15111511
15121512static 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
15131520 zig_i128 sign_mask = lhs < zig_make_i128(0, 0) ? -zig_make_i128(0, 1) : zig_make_i128(0, 0);
15141521 return ((lhs ^ sign_mask) >> rhs) ^ sign_mask;
1522#endif
15151523}
15161524
15171525static inline zig_i128 zig_shl_i128(zig_i128 lhs, uint8_t rhs) {
src/Sema.zig+3-3
......@@ -1130,8 +1130,8 @@ fn analyzeBodyInner(
11301130 const tags = sema.code.instructions.items(.tag);
11311131 const datas = sema.code.instructions.items(.data);
11321132
1133 var crash_info = crash_report.prepAnalyzeBody(sema, block, body);
1134 crash_info.push();
1133 var crash_info: crash_report.AnalyzeBody = undefined;
1134 crash_info.push(sema, block, body);
11351135 defer crash_info.pop();
11361136
11371137 // 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
26322632 std.debug.print("compile error during Sema:\n", .{});
26332633 var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory");
26342634 error_bundle.renderToStdErr(.{ .ttyconf = .no_color });
2635 crash_report.compilerPanic("unexpected compile error occurred", null);
2635 std.debug.panicExtra(@returnAddress(), "unexpected compile error occurred", .{});
26362636 }
26372637
26382638 if (block) |start_block| {
src/Zcu/PerThread.zig+4
......@@ -28,6 +28,7 @@ const Value = @import("../Value.zig");
2828const Zcu = @import("../Zcu.zig");
2929const Compilation = @import("../Compilation.zig");
3030const codegen = @import("../codegen.zig");
31const crash_report = @import("../crash_report.zig");
3132const Zir = std.zig.Zir;
3233const Zoir = std.zig.Zoir;
3334const ZonGen = std.zig.ZonGen;
......@@ -4390,6 +4391,9 @@ pub fn addDependency(pt: Zcu.PerThread, unit: AnalUnit, dependee: InternPool.Dep
43904391pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air, out: *@import("../link.zig").ZcuTask.LinkFunc.SharedMir) void {
43914392 const zcu = pt.zcu;
43924393
4394 crash_report.CodegenFunc.start(zcu, func_index);
4395 defer crash_report.CodegenFunc.stop(func_index);
4396
43934397 var timer = zcu.comp.startTimer();
43944398
43954399 const success: bool = if (runCodegenInner(pt, func_index, air)) |mir| success: {
src/crash_report.zig+109-461
......@@ -1,34 +1,31 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const build_options = @import("build_options");
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)
1/// We override the panic implementation to our own one, so we can print our own information before
2/// calling the default panic handler. This declaration must be re-exposed from `@import("root")`.
3pub const panic = if (dev.env == .bootstrap)
234 std.debug.simple_panic
245else
25 std.debug.FullPanic(std.debug.defaultPanic);
6 std.debug.FullPanic(panicImpl);
267
27/// Install signal handlers to identify crashes and report diagnostics.
28pub fn initialize() void {
29 if (build_options.enable_debug_extensions and debug.have_segfault_handling_support) {
30 attachSegfaultHandler();
31 }
8/// We let std install its segfault handler, but we override the target-agnostic handler it calls,
9/// so we can print our own information before calling the default segfault logic. This declaration
10/// must be re-exposed from `@import("root")`.
11pub const debug = struct {
12 pub const handleSegfault = handleSegfaultImpl;
13};
14
15/// Printed in panic messages when suggesting a command to run, allowing copy-pasting the command.
16/// Set by `main` as soon as arguments are known. The value here is a default in case we somehow
17/// crash earlier than that.
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());
3229}
3330
3431pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct {
......@@ -38,63 +35,98 @@ pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct {
3835 body: []const Zir.Inst.Index,
3936 body_index: usize,
4037
41 pub fn push(self: *@This()) void {
42 const head = &zir_state;
43 debug.assert(self.parent == null);
44 self.parent = head.*;
45 head.* = self;
46 }
38 threadlocal var current: ?*AnalyzeBody = null;
4739
48 pub fn pop(self: *@This()) void {
49 const head = &zir_state;
50 const old = head.*.?;
51 debug.assert(old == self);
52 head.* = old.parent;
40 pub fn setBodyIndex(ab: *AnalyzeBody, index: usize) void {
41 ab.body_index = index;
5342 }
5443
55 pub fn setBodyIndex(self: *@This(), index: usize) void {
56 self.body_index = index;
44 pub fn push(ab: *AnalyzeBody, sema: *Sema, block: *Sema.Block, body: []const Zir.Inst.Index) void {
45 ab.* = .{
46 .parent = current,
47 .sema = sema,
48 .block = block,
49 .body = body,
50 .body_index = 0,
51 };
52 current = ab;
53 }
54 pub fn pop(ab: *AnalyzeBody) void {
55 std.debug.assert(current.? == ab); // `Sema.analyzeBodyInner` did not match push/pop calls
56 current = ab.parent;
5757 }
5858} else struct {
59 pub inline fn push(_: @This()) void {}
60 pub inline fn pop(_: @This()) void {}
59 const current: ?noreturn = null;
60 // Dummy implementation, with functions marked `inline` to avoid interfering with tail calls.
61 pub inline fn push(_: AnalyzeBody, _: *Sema, _: *Sema.Block, _: []const Zir.Inst.Index) void {}
62 pub inline fn pop(_: AnalyzeBody) void {}
6163 pub inline fn setBodyIndex(_: @This(), _: usize) void {}
6264};
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 {
67 return if (build_options.enable_debug_extensions) .{
68 .parent = null,
69 .sema = sema,
70 .block = block,
71 .body = body,
72 .body_index = 0,
73 } else .{};
74}
85fn dumpCrashContext() Io.Writer.Error!void {
86 const S = struct {
87 /// In the case of recursive panics or segfaults, don't print the context for a second time.
88 threadlocal var already_dumped = false;
89 /// TODO: make this unnecessary. It exists because `print_zir` currently needs an allocator,
90 /// but that shouldn't be necessary---it's already only used in one place.
91 threadlocal var crash_heap: [64 * 1024]u8 = undefined;
92 };
93 if (S.already_dumped) return;
94 S.already_dumped = true;
95
96 // TODO: this does mean that a different thread could grab the stderr mutex between the context
97 // and the actual panic printing, which would be quite confusing.
98 const stderr = std.debug.lockStderrWriter(&.{});
99 defer std.debug.unlockStderrWriter();
75100
76fn dumpStatusReport() !void {
77 const anal = zir_state orelse return;
78 // Note: We have the panic mutex here, so we can safely use the global crash heap.
79 var fba = std.heap.FixedBufferAllocator.init(&crash_heap);
80 const allocator = fba.allocator();
101 try stderr.writeAll("Compiler crash context:\n");
81102
82 var stderr_fw = std.fs.File.stderr().writer(&.{});
83 const stderr = &stderr_fw.interface;
103 if (CodegenFunc.current) |*cg| {
104 const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav;
105 const func_fqn = cg.zcu.intern_pool.getNav(func_nav).fqn;
106 try stderr.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)});
107 } else if (AnalyzeBody.current) |anal| {
108 try dumpCrashContextSema(anal, stderr, &S.crash_heap);
109 } else {
110 try stderr.writeAll("(no context)\n\n");
111 }
112}
113fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void {
84114 const block: *Sema.Block = anal.block;
85115 const zcu = anal.sema.pt.zcu;
116 const comp = zcu.comp;
117
118 var fba: std.heap.FixedBufferAllocator = .init(crash_heap);
86119
87120 const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu) orelse {
88121 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)});
90123 return;
91124 };
92125
93 try stderr.writeAll("Analyzing ");
94 try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(zcu.comp)});
126 try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(comp)});
95127
96128 print_zir.renderInstructionContext(
97 allocator,
129 fba.allocator(),
98130 anal.body,
99131 anal.body_index,
100132 file,
......@@ -107,16 +139,16 @@ fn dumpStatusReport() !void {
107139 };
108140 try stderr.print(
109141 \\ For full context, use the command
110 \\ zig ast-check -t {f}
142 \\ {s} ast-check -t {f}
111143 \\
112144 \\
113 , .{file.path.fmt(zcu.comp)});
145 , .{ zig_argv0, file.path.fmt(comp) });
114146
115147 var parent = anal.parent;
116148 while (parent) |curr| {
117149 fba.reset();
118150 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)});
120152 _, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu) orelse {
121153 try stderr.writeAll(" > [lost instruction; this should not happen]\n");
122154 parent = curr.parent;
......@@ -124,7 +156,7 @@ fn dumpStatusReport() !void {
124156 };
125157 try stderr.writeAll(" > ");
126158 print_zir.renderSingleInstruction(
127 allocator,
159 fba.allocator(),
128160 curr.body[curr.body_index],
129161 cur_block_file,
130162 cur_block_src_base_node,
......@@ -142,398 +174,14 @@ fn dumpStatusReport() !void {
142174 try stderr.writeByte('\n');
143175}
144176
145var crash_heap: [16 * 4096]u8 = undefined;
146
147pub fn compilerPanic(msg: []const u8, maybe_ret_addr: ?usize) noreturn {
148 @branchHint(.cold);
149 PanicSwitch.preDispatch();
150 const ret_addr = maybe_ret_addr orelse @returnAddress();
151 const stack_ctx: StackContext = .{ .current = .{ .ret_addr = ret_addr } };
152 PanicSwitch.dispatch(@errorReturnTrace(), stack_ctx, msg);
153}
154
155/// Attaches a global SIGSEGV handler
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;
177const std = @import("std");
178const Io = std.Io;
179const Zir = std.zig.Zir;
526180
527 var stderr_fw = std.fs.File.stderr().writer(&.{});
528 const stderr = &stderr_fw.interface;
529 stderr.writeAll("\nPanicked while dumping inner panic stack: ") catch {};
530 stderr.writeAll(msg) catch {};
531 stderr.writeByte('\n') catch {};
181const Sema = @import("Sema.zig");
182const Zcu = @import("Zcu.zig");
183const InternPool = @import("InternPool.zig");
184const dev = @import("dev.zig");
185const print_zir = @import("print_zir.zig");
532186
533 // If we succeed, restore all the way to dumping the stack.
534 state.recover_verbosity = .message_and_stack;
535 },
536 .silent => {},
537 }
538 }
539};
187const build_options = @import("build_options");
src/link/Dwarf.zig+2-2
......@@ -4064,6 +4064,7 @@ fn updateLazyValue(
40644064 },
40654065 .error_union => |error_union| {
40664066 try wip_nav.abbrevCode(.aggregate_comptime_value);
4067 try wip_nav.refType(.fromInterned(error_union.ty));
40674068 var err_buf: [4]u8 = undefined;
40684069 const err_bytes = err_buf[0 .. std.math.divCeil(u17, zcu.errorSetBits(), 8) catch unreachable];
40694070 dwarf.writeInt(err_bytes, switch (error_union.val) {
......@@ -4101,7 +4102,6 @@ fn updateLazyValue(
41014102 try diw.writeUleb128(err_bytes.len);
41024103 try diw.writeAll(err_bytes);
41034104 }
4104 try wip_nav.refType(.fromInterned(error_union.ty));
41054105 try diw.writeUleb128(@intFromEnum(AbbrevCode.null));
41064106 },
41074107 .enum_literal => |enum_literal| {
......@@ -4852,7 +4852,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Erro
48524852 hw.writeSleb128(dwarf.debug_frame.header.data_alignment_factor) catch unreachable;
48534853 hw.writeUleb128(dwarf.debug_frame.header.return_address_register) catch unreachable;
48544854 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;
48564856 hw.writeByte(DW.CFA.def_cfa_sf) catch unreachable;
48574857 hw.writeUleb128(Register.rsp.dwarfNum()) catch unreachable;
48584858 hw.writeSleb128(-1) catch unreachable;
src/link/Elf.zig+22-17
......@@ -1884,6 +1884,16 @@ fn initSyntheticSections(self: *Elf) !void {
18841884 const ptr_size = self.ptrWidthBytes();
18851885 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
18871897 const needs_eh_frame = blk: {
18881898 if (self.zigObjectPtr()) |zo|
18891899 if (zo.eh_frame_index != null) break :blk true;
......@@ -1891,6 +1901,7 @@ fn initSyntheticSections(self: *Elf) !void {
18911901 if (self.file(index).?.object.cies.items.len > 0) break true;
18921902 } else false;
18931903 };
1904
18941905 if (needs_eh_frame) {
18951906 if (self.section_indexes.eh_frame == null) {
18961907 self.section_indexes.eh_frame = self.sectionByName(".eh_frame") orelse try self.addSection(.{
......@@ -1922,13 +1933,17 @@ fn initSyntheticSections(self: *Elf) !void {
19221933 });
19231934 }
19241935
1925 if (self.section_indexes.got_plt == null) {
1926 self.section_indexes.got_plt = try self.addSection(.{
1927 .name = try self.insertShString(".got.plt"),
1928 .type = elf.SHT_PROGBITS,
1929 .flags = elf.SHF_ALLOC | elf.SHF_WRITE,
1930 .addralign = @alignOf(u64),
1931 });
1936 if (have_dynamic_linker) {
1937 if (self.section_indexes.got_plt == null) {
1938 self.section_indexes.got_plt = try self.addSection(.{
1939 .name = try self.insertShString(".got.plt"),
1940 .type = elf.SHT_PROGBITS,
1941 .flags = elf.SHF_ALLOC | elf.SHF_WRITE,
1942 .addralign = @alignOf(u64),
1943 });
1944 }
1945 } else {
1946 assert(self.plt.symbols.items.len == 0);
19321947 }
19331948
19341949 const needs_rela_dyn = blk: {
......@@ -1989,16 +2004,6 @@ fn initSyntheticSections(self: *Elf) !void {
19892004 });
19902005 }
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
20022007 if (needs_interp and self.section_indexes.interp == null) {
20032008 self.section_indexes.interp = try self.addSection(.{
20042009 .name = try self.insertShString(".interp"),
src/link/Elf/eh_frame.zig+51-41
......@@ -234,7 +234,14 @@ pub fn calcEhFrameSize(elf_file: *Elf) !usize {
234234 return offset;
235235}
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
237243pub fn calcEhFrameHdrSize(elf_file: *Elf) usize {
244 if (!haveEhFrameHdrSearchTable(elf_file)) return 8;
238245 var count: usize = 0;
239246 for (elf_file.objects.items) |index| {
240247 for (elf_file.file(index).?.object.fdes.items) |fde| {
......@@ -242,7 +249,7 @@ pub fn calcEhFrameHdrSize(elf_file: *Elf) usize {
242249 count += 1;
243250 }
244251 }
245 return eh_frame_hdr_header_size + count * 8;
252 return 12 + count * 8;
246253}
247254
248255pub fn calcEhFrameRelocs(elf_file: *Elf) usize {
......@@ -455,76 +462,77 @@ pub fn writeEhFrameRelocs(elf_file: *Elf, relocs: *std.array_list.Managed(elf.El
455462}
456463
457464pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void {
458 const comp = elf_file.base.comp;
459 const gpa = comp.gpa;
465 const endian = elf_file.getTarget().cpu.arch.endian();
466 const have_table = haveEhFrameHdrSearchTable(elf_file);
460467
461468 try writer.writeByte(1); // version
462 try writer.writeByte(DW_EH_PE.pcrel | DW_EH_PE.sdata4);
463 try writer.writeByte(DW_EH_PE.udata4);
464 try writer.writeByte(DW_EH_PE.datarel | DW_EH_PE.sdata4);
469 try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .sdata4, .rel = .pcrel }))); // eh_frame_ptr_enc
470 if (have_table) {
471 try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .udata4, .rel = .abs }))); // fde_count_enc
472 try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .sdata4, .rel = .datarel }))); // table_enc
473 } else {
474 try writer.writeByte(@bitCast(DW_EH_PE.omit)); // fde_count_enc
475 try writer.writeByte(@bitCast(DW_EH_PE.omit)); // table_enc
476 }
465477
466478 const shdrs = elf_file.sections.items(.shdr);
467479 const eh_frame_shdr = shdrs[elf_file.section_indexes.eh_frame.?];
468480 const eh_frame_hdr_shdr = shdrs[elf_file.section_indexes.eh_frame_hdr.?];
469 const num_fdes = @as(u32, @intCast(@divExact(eh_frame_hdr_shdr.sh_size - eh_frame_hdr_header_size, 8)));
470 const existing_size = existing_size: {
471 const zo = elf_file.zigObjectPtr() orelse break :existing_size 0;
472 const sym = zo.symbol(zo.eh_frame_index orelse break :existing_size 0);
473 break :existing_size sym.atom(elf_file).?.size;
474 };
481 // eh_frame_ptr
475482 try writer.writeInt(
476483 u32,
477484 @as(u32, @bitCast(@as(
478485 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),
480487 ))),
481488 .little,
482489 );
483 try writer.writeInt(u32, num_fdes, .little);
484490
485 const Entry = extern struct {
486 init_addr: u32,
487 fde_addr: u32,
491 if (!have_table) return;
488492
489 pub fn lessThan(ctx: void, lhs: @This(), rhs: @This()) bool {
490 _ = ctx;
491 return lhs.init_addr < rhs.init_addr;
493 const gpa = elf_file.base.comp.gpa;
494
495 // This must be an `extern struct` because we will write the bytes directly to the file.
496 const Entry = extern struct {
497 first_pc_rel: i32,
498 fde_addr_rel: i32,
499 fn lessThan(_: void, lhs: @This(), rhs: @This()) bool {
500 return lhs.first_pc_rel < rhs.first_pc_rel;
492501 }
493502 };
494
495 var entries = std.array_list.Managed(Entry).init(gpa);
496 defer entries.deinit();
497 try entries.ensureTotalCapacityPrecise(num_fdes);
498
499 for (elf_file.objects.items) |index| {
500 const object = elf_file.file(index).?.object;
503 // The number of entries was already computed by `calcEhFrameHdrSize`.
504 const num_fdes: u32 = @intCast(@divExact(eh_frame_hdr_shdr.sh_size - 12, 8));
505 try writer.writeInt(u32, num_fdes, endian);
506
507 var entries: std.ArrayList(Entry) = try .initCapacity(gpa, num_fdes);
508 defer entries.deinit(gpa);
509 for (elf_file.objects.items) |file_index| {
510 const object = elf_file.file(file_index).?.object;
501511 for (object.fdes.items) |fde| {
502512 if (!fde.alive) continue;
503
504513 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...
506515 const rel = relocs[0];
507516 const ref = object.resolveSymbol(rel.r_sym(), elf_file);
508517 const sym = elf_file.symbol(ref).?;
509 const P = @as(i64, @intCast(fde.address(elf_file)));
510 const S = @as(i64, @intCast(sym.address(.{}, elf_file)));
511 const A = rel.r_addend;
518 const fde_addr_abs: i64 = @intCast(fde.address(elf_file));
519 const fde_addr_rel: i64 = fde_addr_abs - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr));
520 const first_pc_abs: i64 = @as(i64, @intCast(sym.address(.{}, elf_file))) + rel.r_addend;
521 const first_pc_rel: i64 = first_pc_abs - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr));
512522 entries.appendAssumeCapacity(.{
513 .init_addr = @bitCast(@as(i32, @truncate(S + A - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))),
514 .fde_addr = @as(
515 u32,
516 @bitCast(@as(i32, @truncate(P - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))),
517 ),
523 .first_pc_rel = @truncate(first_pc_rel),
524 .fde_addr_rel = @truncate(fde_addr_rel),
518525 });
519526 }
520527 }
521
528 assert(entries.items.len == num_fdes);
522529 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));
524534}
525535
526const eh_frame_hdr_header_size: usize = 12;
527
528536const x86_64 = struct {
529537 fn resolveReloc(rec: anytype, elf_file: *Elf, rel: elf.Elf64_Rela, source: i64, target: i64, data: []u8) !void {
530538 const r_type: elf.R_X86_64 = @enumFromInt(rel.r_type());
......@@ -587,3 +595,5 @@ const DW_EH_PE = std.dwarf.EH.PE;
587595const Elf = @import("../Elf.zig");
588596const Object = @import("Object.zig");
589597const Symbol = @import("Symbol.zig");
598
599const builtin = @import("builtin");
src/link/MachO.zig+3-3
......@@ -4149,9 +4149,9 @@ pub const SymtabCtx = struct {
41494149
41504150pub const null_sym = macho.nlist_64{
41514151 .n_strx = 0,
4152 .n_type = 0,
4152 .n_type = @bitCast(@as(u8, 0)),
41534153 .n_sect = 0,
4154 .n_desc = 0,
4154 .n_desc = @bitCast(@as(u16, 0)),
41554155 .n_value = 0,
41564156};
41574157
......@@ -5070,7 +5070,7 @@ pub fn getKernError(err: std.c.kern_return_t) KernE {
50705070pub fn unexpectedKernError(err: KernE) std.posix.UnexpectedError {
50715071 if (std.posix.unexpected_error_tracing) {
50725072 std.debug.print("unexpected error: {d}\n", .{@intFromEnum(err)});
5073 std.debug.dumpCurrentStackTrace(null);
5073 std.debug.dumpCurrentStackTrace(.{});
50745074 }
50755075 return error.Unexpected;
50765076}
src/link/MachO/InternalObject.zig+15-15
......@@ -69,9 +69,9 @@ pub fn initSymbols(self: *InternalObject, macho_file: *MachO) !void {
6969 const nlist = obj.symtab.addOneAssumeCapacity();
7070 nlist.* = .{
7171 .n_strx = name.pos,
72 .n_type = args.type,
72 .n_type = @bitCast(args.type),
7373 .n_sect = 0,
74 .n_desc = args.desc,
74 .n_desc = @bitCast(args.desc),
7575 .n_value = 0,
7676 };
7777 symbol.nlist_idx = nlist_idx;
......@@ -143,7 +143,7 @@ pub fn resolveSymbols(self: *InternalObject, macho_file: *MachO) !void {
143143 }
144144 global.* = gop.index;
145145
146 if (nlist.undf()) continue;
146 if (nlist.n_type.bits.type == .undf) continue;
147147 if (gop.ref.getFile(macho_file) == null) {
148148 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
149149 continue;
......@@ -171,7 +171,7 @@ pub fn resolveBoundarySymbols(self: *InternalObject, macho_file: *MachO) !void {
171171 const object = macho_file.getFile(index).?.object;
172172 for (object.symbols.items, 0..) |sym, i| {
173173 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;
175175 const ref = object.getSymbolRef(@intCast(i), macho_file);
176176 if (ref.getFile(macho_file) != null) continue;
177177 const name = sym.getName(macho_file);
......@@ -206,9 +206,9 @@ pub fn resolveBoundarySymbols(self: *InternalObject, macho_file: *MachO) !void {
206206 const nlist = self.symtab.addOneAssumeCapacity();
207207 nlist.* = .{
208208 .n_strx = name_str.pos,
209 .n_type = macho.N_SECT,
209 .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } },
210210 .n_sect = 0,
211 .n_desc = 0,
211 .n_desc = @bitCast(@as(u16, 0)),
212212 .n_value = 0,
213213 };
214214 sym.nlist_idx = nlist_idx;
......@@ -226,7 +226,7 @@ pub fn markLive(self: *InternalObject, macho_file: *MachO) void {
226226
227227 for (0..self.symbols.items.len) |i| {
228228 const nlist = self.symtab.items[i];
229 if (!nlist.ext()) continue;
229 if (!nlist.n_type.bits.ext) continue;
230230
231231 const ref = self.getSymbolRef(@intCast(i), macho_file);
232232 const file = ref.getFile(macho_file) orelse continue;
......@@ -273,9 +273,9 @@ fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_fil
273273 const nlist = try self.symtab.addOne(gpa);
274274 nlist.* = .{
275275 .n_strx = name_str.pos,
276 .n_type = macho.N_SECT,
276 .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } },
277277 .n_sect = @intCast(n_sect + 1),
278 .n_desc = 0,
278 .n_desc = @bitCast(@as(u16, 0)),
279279 .n_value = 0,
280280 };
281281 sym.nlist_idx = nlist_idx;
......@@ -326,9 +326,9 @@ fn addObjcSelrefsSection(self: *InternalObject, methname_sym_index: Symbol.Index
326326 const nlist = try self.symtab.addOne(gpa);
327327 nlist.* = .{
328328 .n_strx = 0,
329 .n_type = macho.N_SECT,
329 .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } },
330330 .n_sect = @intCast(n_sect + 1),
331 .n_desc = 0,
331 .n_desc = @bitCast(@as(u16, 0)),
332332 .n_value = 0,
333333 };
334334 sym.nlist_idx = nlist_idx;
......@@ -352,8 +352,8 @@ pub fn resolveObjcMsgSendSymbols(self: *InternalObject, macho_file: *MachO) !voi
352352
353353 for (object.symbols.items, 0..) |sym, i| {
354354 const nlist = object.symtab.items(.nlist)[i];
355 if (!nlist.ext()) continue;
356 if (!nlist.undf()) continue;
355 if (!nlist.n_type.bits.ext) continue;
356 if (nlist.n_type.bits.type != .undf) continue;
357357
358358 const ref = object.getSymbolRef(@intCast(i), macho_file);
359359 if (ref.getFile(macho_file) != null) continue;
......@@ -381,9 +381,9 @@ pub fn resolveObjcMsgSendSymbols(self: *InternalObject, macho_file: *MachO) !voi
381381 const nlist = try self.symtab.addOne(gpa);
382382 nlist.* = .{
383383 .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 } },
385385 .n_sect = 0,
386 .n_desc = 0,
386 .n_desc = @bitCast(@as(u16, 0)),
387387 .n_value = 0,
388388 };
389389 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 {
179179 idx: usize,
180180
181181 fn rank(ctx: *const Object, nl: macho.nlist_64) u8 {
182 if (!nl.ext()) {
182 if (!nl.n_type.bits.ext) {
183183 const name = ctx.getNStrx(nl.n_strx);
184184 if (name.len == 0) return 5;
185185 if (name[0] == 'l' or name[0] == 'L') return 4;
186186 return 3;
187187 }
188 return if (nl.weakDef()) 2 else 1;
188 return if (nl.n_desc.weak_def_or_ref_to_weak) 2 else 1;
189189 }
190190
191191 fn lessThan(ctx: *const Object, lhs: @This(), rhs: @This()) bool {
......@@ -202,7 +202,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
202202 var nlists = try std.array_list.Managed(NlistIdx).initCapacity(gpa, self.symtab.items(.nlist).len);
203203 defer nlists.deinit();
204204 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;
206206 nlists.appendAssumeCapacity(.{ .nlist = nlist, .idx = i });
207207 }
208208 mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan);
......@@ -488,9 +488,9 @@ fn initCstringLiterals(self: *Object, allocator: Allocator, file: File.Handle, m
488488 self.symtab.set(nlist_index, .{
489489 .nlist = .{
490490 .n_strx = name_str.pos,
491 .n_type = macho.N_SECT,
491 .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } },
492492 .n_sect = @intCast(atom.n_sect + 1),
493 .n_desc = 0,
493 .n_desc = @bitCast(@as(u16, 0)),
494494 .n_value = atom.getInputAddress(macho_file),
495495 },
496496 .size = atom.size,
......@@ -555,9 +555,9 @@ fn initFixedSizeLiterals(self: *Object, allocator: Allocator, macho_file: *MachO
555555 self.symtab.set(nlist_index, .{
556556 .nlist = .{
557557 .n_strx = name_str.pos,
558 .n_type = macho.N_SECT,
558 .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } },
559559 .n_sect = @intCast(atom.n_sect + 1),
560 .n_desc = 0,
560 .n_desc = @bitCast(@as(u16, 0)),
561561 .n_value = atom.getInputAddress(macho_file),
562562 },
563563 .size = atom.size,
......@@ -613,9 +613,9 @@ fn initPointerLiterals(self: *Object, allocator: Allocator, macho_file: *MachO)
613613 self.symtab.set(nlist_index, .{
614614 .nlist = .{
615615 .n_strx = name_str.pos,
616 .n_type = macho.N_SECT,
616 .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } },
617617 .n_sect = @intCast(atom.n_sect + 1),
618 .n_desc = 0,
618 .n_desc = @bitCast(@as(u16, 0)),
619619 .n_value = atom.getInputAddress(macho_file),
620620 },
621621 .size = atom.size,
......@@ -805,7 +805,7 @@ fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void {
805805 const tracy = trace(@src());
806806 defer tracy.end();
807807 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) {
809809 const sect = self.sections.items(.header)[nlist.n_sect - 1];
810810 const subs = self.sections.items(.subsections)[nlist.n_sect - 1].items;
811811 if (nlist.n_value == sect.addr) {
......@@ -852,30 +852,30 @@ fn initSymbols(self: *Object, allocator: Allocator, macho_file: *MachO) !void {
852852 symbol.extra = self.addSymbolExtraAssumeCapacity(.{});
853853
854854 if (self.getAtom(atom_index)) |atom| {
855 assert(!nlist.abs());
855 assert(nlist.n_type.bits.type != .abs);
856856 symbol.value -= atom.getInputAddress(macho_file);
857857 symbol.atom_ref = .{ .index = atom_index, .file = self.index };
858858 }
859859
860 symbol.flags.weak = nlist.weakDef();
861 symbol.flags.abs = nlist.abs();
860 symbol.flags.weak = nlist.n_desc.weak_def_or_ref_to_weak;
861 symbol.flags.abs = nlist.n_type.bits.type == .abs;
862862 symbol.flags.tentative = nlist.tentative();
863 symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip();
864 symbol.flags.dyn_ref = nlist.n_desc & macho.REFERENCED_DYNAMICALLY != 0;
863 symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.n_desc.discarded_or_no_dead_strip;
864 symbol.flags.dyn_ref = nlist.n_desc.referenced_dynamically;
865865 symbol.flags.interposable = false;
866866 // 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() and
869 if (nlist.n_type.bits.type == .sect and
870870 self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
871871 {
872872 symbol.flags.tlv = true;
873873 }
874874
875 if (nlist.ext()) {
876 if (nlist.undf()) {
877 symbol.flags.weak_ref = nlist.weakRef();
878 } else if (nlist.pext() or (nlist.weakDef() and nlist.weakRef()) or self.hidden) {
875 if (nlist.n_type.bits.ext) {
876 if (nlist.n_type.bits.type == .undf) {
877 symbol.flags.weak_ref = nlist.n_desc.weak_ref;
878 } else if (nlist.n_type.bits.pext or (nlist.n_desc.weak_def_or_ref_to_weak and nlist.n_desc.weak_ref) or self.hidden) {
879879 symbol.visibility = .hidden;
880880 } else {
881881 symbol.visibility = .global;
......@@ -902,10 +902,10 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f
902902 };
903903
904904 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);
906906 } else @intCast(self.symtab.items(.nlist).len);
907907 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);
909909 } else @intCast(self.symtab.items(.nlist).len);
910910
911911 if (start == end) return;
......@@ -919,7 +919,7 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f
919919 var addr_lookup = std.StringHashMap(u64).init(allocator);
920920 defer addr_lookup.deinit();
921921 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)) {
923923 try addr_lookup.putNoClobber(self.getNStrx(sym.n_strx), sym.n_value);
924924 }
925925 }
......@@ -927,41 +927,43 @@ fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_f
927927 var i: u32 = start;
928928 while (i < end) : (i += 1) {
929929 const open = syms[i];
930 if (open.n_type != macho.N_SO) {
930 if (open.n_type.stab != .so) {
931931 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),
933933 });
934934 return error.MalformedObject;
935935 }
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
939939 var sf: StabFile = .{ .comp_dir = i };
940940 // TODO validate
941941 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) {
944944 const nlist = syms[i];
945945 var stab: StabFile.Stab = .{};
946 switch (nlist.n_type) {
947 macho.N_BNSYM => {
946 switch (nlist.n_type.stab) {
947 .bnsym => {
948948 stab.is_func = true;
949949 stab.index = sym_lookup.find(nlist.n_value);
950950 // TODO validate
951951 i += 3;
952952 },
953 macho.N_GSYM => {
953 .gsym => {
954954 stab.is_func = false;
955955 stab.index = sym_lookup.find(addr_lookup.get(self.getNStrx(nlist.n_strx)).?);
956956 },
957 macho.N_STSYM => {
957 .stsym => {
958958 stab.is_func = false;
959959 stab.index = sym_lookup.find(nlist.n_value);
960960 },
961 _ => {
962 try macho_file.reportParseError2(self.index, "unhandled symbol stab type 0x{x}", .{@intFromEnum(nlist.n_type.stab)});
963 return error.MalformedObject;
964 },
961965 else => {
962 try macho_file.reportParseError2(self.index, "unhandled symbol stab type 0x{x}", .{
963 nlist.n_type,
964 });
966 try macho_file.reportParseError2(self.index, "unhandled symbol stab type '{t}'", .{nlist.n_type.stab});
965967 return error.MalformedObject;
966968 },
967969 }
......@@ -1132,7 +1134,7 @@ fn initUnwindRecords(self: *Object, allocator: Allocator, sect_id: u8, file: Fil
11321134 fn find(fs: @This(), addr: u64) ?Symbol.Index {
11331135 for (0..fs.ctx.symbols.items.len) |i| {
11341136 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);
11361138 }
11371139 return null;
11381140 }
......@@ -1241,8 +1243,8 @@ fn parseUnwindRecords(self: *Object, allocator: Allocator, cpu_arch: std.Target.
12411243
12421244 const slice = self.symtab.slice();
12431245 for (slice.items(.nlist), slice.items(.atom), slice.items(.size)) |nlist, atom, size| {
1244 if (nlist.stab()) continue;
1245 if (!nlist.sect()) continue;
1246 if (nlist.n_type.bits.is_stab != 0) continue;
1247 if (nlist.n_type.bits.type != .sect) continue;
12461248 const sect = self.sections.items(.header)[nlist.n_sect - 1];
12471249 if (sect.isCode() and sect.size > 0) {
12481250 try superposition.ensureUnusedCapacity(1);
......@@ -1458,8 +1460,8 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void {
14581460 const gpa = macho_file.base.comp.gpa;
14591461
14601462 for (self.symtab.items(.nlist), self.symtab.items(.atom), self.globals.items, 0..) |nlist, atom_index, *global, i| {
1461 if (!nlist.ext()) continue;
1462 if (nlist.sect()) {
1463 if (!nlist.n_type.bits.ext) continue;
1464 if (nlist.n_type.bits.type == .sect) {
14631465 const atom = self.getAtom(atom_index).?;
14641466 if (!atom.isAlive()) continue;
14651467 }
......@@ -1473,7 +1475,7 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void {
14731475 }
14741476 global.* = gop.index;
14751477
1476 if (nlist.undf() and !nlist.tentative()) continue;
1478 if (nlist.n_type.bits.type == .undf and !nlist.tentative()) continue;
14771479 if (gop.ref.getFile(macho_file) == null) {
14781480 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
14791481 continue;
......@@ -1481,7 +1483,7 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void {
14811483
14821484 if (self.asFile().getSymbolRank(.{
14831485 .archive = !self.alive,
1484 .weak = nlist.weakDef(),
1486 .weak = nlist.n_desc.weak_def_or_ref_to_weak,
14851487 .tentative = nlist.tentative(),
14861488 }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) {
14871489 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
......@@ -1495,12 +1497,12 @@ pub fn markLive(self: *Object, macho_file: *MachO) void {
14951497
14961498 for (0..self.symbols.items.len) |i| {
14971499 const nlist = self.symtab.items(.nlist)[i];
1498 if (!nlist.ext()) continue;
1500 if (!nlist.n_type.bits.ext) continue;
14991501
15001502 const ref = self.getSymbolRef(@intCast(i), macho_file);
15011503 const file = ref.getFile(macho_file) orelse continue;
15021504 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);
15041506 if (should_keep and file == .object and !file.object.alive) {
15051507 file.object.alive = true;
15061508 file.object.markLive(macho_file);
......@@ -1565,7 +1567,7 @@ pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void {
15651567 const name = try std.fmt.allocPrintSentinel(gpa, "__DATA$__common${s}", .{sym.getName(macho_file)}, 0);
15661568 defer gpa.free(name);
15671569
1568 const alignment = (nlist.n_desc >> 8) & 0x0f;
1570 const alignment = (@as(u16, @bitCast(nlist.n_desc)) >> 8) & 0x0f;
15691571 const n_sect = try self.addSection(gpa, "__DATA", "__common");
15701572 const atom_index = try self.addAtom(gpa, .{
15711573 .name = try self.addString(gpa, name),
......@@ -1589,9 +1591,9 @@ pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void {
15891591 sym.visibility = .global;
15901592
15911593 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 } };
15931595 nlist.n_sect = 0;
1594 nlist.n_desc = 0;
1596 nlist.n_desc = @bitCast(@as(u16, 0));
15951597 nlist_atom.* = atom_index;
15961598 }
15971599}
......@@ -1685,7 +1687,7 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {
16851687pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, macho_file: *MachO) error{OutOfMemory}!void {
16861688 const gpa = macho_file.base.comp.gpa;
16871689 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;
16891691 const off = try ar_symtab.strtab.insert(gpa, self.getNStrx(nlist.n_strx));
16901692 try ar_symtab.entries.append(gpa, .{ .off = off, .file = self.index });
16911693 }
......@@ -2028,30 +2030,30 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
20282030 ) void {
20292031 context.symtab.items[index] = .{
20302032 .n_strx = 0,
2031 .n_type = macho.N_BNSYM,
2033 .n_type = .{ .stab = .bnsym },
20322034 .n_sect = n_sect,
2033 .n_desc = 0,
2035 .n_desc = @bitCast(@as(u16, 0)),
20342036 .n_value = n_value,
20352037 };
20362038 context.symtab.items[index + 1] = .{
20372039 .n_strx = n_strx,
2038 .n_type = macho.N_FUN,
2040 .n_type = .{ .stab = .fun },
20392041 .n_sect = n_sect,
2040 .n_desc = 0,
2042 .n_desc = @bitCast(@as(u16, 0)),
20412043 .n_value = n_value,
20422044 };
20432045 context.symtab.items[index + 2] = .{
20442046 .n_strx = 0,
2045 .n_type = macho.N_FUN,
2047 .n_type = .{ .stab = .fun },
20462048 .n_sect = 0,
2047 .n_desc = 0,
2049 .n_desc = @bitCast(@as(u16, 0)),
20482050 .n_value = size,
20492051 };
20502052 context.symtab.items[index + 3] = .{
20512053 .n_strx = 0,
2052 .n_type = macho.N_ENSYM,
2054 .n_type = .{ .stab = .ensym },
20532055 .n_sect = n_sect,
2054 .n_desc = 0,
2056 .n_desc = @bitCast(@as(u16, 0)),
20552057 .n_value = size,
20562058 };
20572059 }
......@@ -2068,9 +2070,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
20682070 // N_SO comp_dir
20692071 ctx.symtab.items[index] = .{
20702072 .n_strx = n_strx,
2071 .n_type = macho.N_SO,
2073 .n_type = .{ .stab = .so },
20722074 .n_sect = 0,
2073 .n_desc = 0,
2075 .n_desc = @bitCast(@as(u16, 0)),
20742076 .n_value = 0,
20752077 };
20762078 index += 1;
......@@ -2081,9 +2083,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
20812083 // N_SO tu_name
20822084 macho_file.symtab.items[index] = .{
20832085 .n_strx = n_strx,
2084 .n_type = macho.N_SO,
2086 .n_type = .{ .stab = .so },
20852087 .n_sect = 0,
2086 .n_desc = 0,
2088 .n_desc = @bitCast(@as(u16, 0)),
20872089 .n_value = 0,
20882090 };
20892091 index += 1;
......@@ -2094,9 +2096,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
20942096 // N_OSO path
20952097 ctx.symtab.items[index] = .{
20962098 .n_strx = n_strx,
2097 .n_type = macho.N_OSO,
2099 .n_type = .{ .stab = .oso },
20982100 .n_sect = 0,
2099 .n_desc = 1,
2101 .n_desc = @bitCast(@as(u16, 1)),
21002102 .n_value = self.mtime,
21012103 };
21022104 index += 1;
......@@ -2159,18 +2161,18 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
21592161 } else if (sym.visibility == .global) {
21602162 ctx.symtab.items[index] = .{
21612163 .n_strx = sym_n_strx,
2162 .n_type = macho.N_GSYM,
2164 .n_type = .{ .stab = .gsym },
21632165 .n_sect = sym_n_sect,
2164 .n_desc = 0,
2166 .n_desc = @bitCast(@as(u16, 0)),
21652167 .n_value = 0,
21662168 };
21672169 index += 1;
21682170 } else {
21692171 ctx.symtab.items[index] = .{
21702172 .n_strx = sym_n_strx,
2171 .n_type = macho.N_STSYM,
2173 .n_type = .{ .stab = .stsym },
21722174 .n_sect = sym_n_sect,
2173 .n_desc = 0,
2175 .n_desc = @bitCast(@as(u16, 0)),
21742176 .n_value = sym_n_value,
21752177 };
21762178 index += 1;
......@@ -2181,9 +2183,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
21812183 // N_SO
21822184 ctx.symtab.items[index] = .{
21832185 .n_strx = 0,
2184 .n_type = macho.N_SO,
2186 .n_type = .{ .stab = .so },
21852187 .n_sect = 0,
2186 .n_desc = 0,
2188 .n_desc = @bitCast(@as(u16, 0)),
21872189 .n_value = 0,
21882190 };
21892191 } else {
......@@ -2198,9 +2200,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
21982200 // N_SO comp_dir
21992201 ctx.symtab.items[index] = .{
22002202 .n_strx = n_strx,
2201 .n_type = macho.N_SO,
2203 .n_type = .{ .stab = .so },
22022204 .n_sect = 0,
2203 .n_desc = 0,
2205 .n_desc = @bitCast(@as(u16, 0)),
22042206 .n_value = 0,
22052207 };
22062208 index += 1;
......@@ -2211,9 +2213,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
22112213 // N_SO tu_name
22122214 ctx.symtab.items[index] = .{
22132215 .n_strx = n_strx,
2214 .n_type = macho.N_SO,
2216 .n_type = .{ .stab = .so },
22152217 .n_sect = 0,
2216 .n_desc = 0,
2218 .n_desc = @bitCast(@as(u16, 0)),
22172219 .n_value = 0,
22182220 };
22192221 index += 1;
......@@ -2224,9 +2226,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
22242226 // N_OSO path
22252227 ctx.symtab.items[index] = .{
22262228 .n_strx = n_strx,
2227 .n_type = macho.N_OSO,
2229 .n_type = .{ .stab = .so },
22282230 .n_sect = 0,
2229 .n_desc = 1,
2231 .n_desc = @bitCast(@as(u16, 1)),
22302232 .n_value = sf.getOsoModTime(self),
22312233 };
22322234 index += 1;
......@@ -2254,18 +2256,18 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
22542256 } else if (sym.visibility == .global) {
22552257 ctx.symtab.items[index] = .{
22562258 .n_strx = sym_n_strx,
2257 .n_type = macho.N_GSYM,
2259 .n_type = .{ .stab = .gsym },
22582260 .n_sect = sym_n_sect,
2259 .n_desc = 0,
2261 .n_desc = @bitCast(@as(u16, 0)),
22602262 .n_value = 0,
22612263 };
22622264 index += 1;
22632265 } else {
22642266 ctx.symtab.items[index] = .{
22652267 .n_strx = sym_n_strx,
2266 .n_type = macho.N_STSYM,
2268 .n_type = .{ .stab = .stsym },
22672269 .n_sect = sym_n_sect,
2268 .n_desc = 0,
2270 .n_desc = @bitCast(@as(u16, 0)),
22692271 .n_value = sym_n_value,
22702272 };
22712273 index += 1;
......@@ -2276,9 +2278,9 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
22762278 // N_SO
22772279 ctx.symtab.items[index] = .{
22782280 .n_strx = 0,
2279 .n_type = macho.N_SO,
2281 .n_type = .{ .stab = .so },
22802282 .n_sect = 0,
2281 .n_desc = 0,
2283 .n_desc = @bitCast(@as(u16, 0)),
22822284 .n_value = 0,
22832285 };
22842286 index += 1;
src/link/MachO/Symbol.zig+28-14
......@@ -36,7 +36,7 @@ pub fn isLocal(symbol: Symbol) bool {
3636pub fn isSymbolStab(symbol: Symbol, macho_file: *MachO) bool {
3737 const file = symbol.getFile(macho_file) orelse return false;
3838 return switch (file) {
39 .object => symbol.getNlist(macho_file).stab(),
39 .object => symbol.getNlist(macho_file).n_type.bits.is_stab != 0,
4040 else => false,
4141 };
4242}
......@@ -233,35 +233,49 @@ pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void {
233233
234234pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void {
235235 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 } };
237242 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));
239244 out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);
240245
241246 switch (symbol.visibility) {
242 .hidden => out.n_type |= macho.N_PEXT,
247 .hidden => out.n_type.bits.pext = true,
243248 else => {},
244249 }
245250 } else if (symbol.flags.@"export") {
246251 assert(symbol.visibility == .global);
247 out.n_type = macho.N_EXT;
248 out.n_type |= if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
252 out.n_type = .{ .bits = .{
253 .ext = true,
254 .type = if (symbol.flags.abs) .abs else .sect,
255 .pext = false,
256 .is_stab = 0,
257 } };
249258 out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.getOutputSectionIndex(macho_file) + 1);
250259 out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);
251 out.n_desc = 0;
260 out.n_desc = @bitCast(@as(u16, 0));
252261
253262 if (symbol.flags.weak) {
254 out.n_desc |= macho.N_WEAK_DEF;
263 out.n_desc.weak_def_or_ref_to_weak = true;
255264 }
256265 if (symbol.flags.dyn_ref) {
257 out.n_desc |= macho.REFERENCED_DYNAMICALLY;
266 out.n_desc.referenced_dynamically = true;
258267 }
259268 } else {
260269 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 } };
262276 out.n_sect = 0;
263277 out.n_value = 0;
264 out.n_desc = 0;
278 out.n_desc = @bitCast(@as(u16, 0));
265279
266280 // TODO:
267281 // 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
274288 ord
275289 else
276290 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
279293 if (symbol.flags.weak) {
280 out.n_desc |= macho.N_WEAK_DEF;
294 out.n_desc.weak_def_or_ref_to_weak = true;
281295 }
282296
283297 if (symbol.weakRef(macho_file)) {
284 out.n_desc |= macho.N_WEAK_REF;
298 out.n_desc.weak_ref = true;
285299 }
286300 }
287301}
src/link/MachO/Thunk.zig+2-2
......@@ -56,10 +56,10 @@ pub fn writeSymtab(thunk: Thunk, macho_file: *MachO, ctx: anytype) void {
5656 n_strx += @intCast("__thunk".len);
5757 ctx.strtab.items[n_strx] = 0;
5858 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 } };
6060 out_sym.n_sect = @intCast(thunk.out_n_sect + 1);
6161 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));
6363 }
6464}
6565
src/link/MachO/ZigObject.zig+16-16
......@@ -123,9 +123,9 @@ fn newSymbol(self: *ZigObject, allocator: Allocator, name: MachO.String, args: s
123123 self.symtab.set(nlist_idx, .{
124124 .nlist = .{
125125 .n_strx = name.pos,
126 .n_type = args.type,
126 .n_type = @bitCast(args.type),
127127 .n_sect = 0,
128 .n_desc = args.desc,
128 .n_desc = @bitCast(args.desc),
129129 .n_value = 0,
130130 },
131131 .size = 0,
......@@ -206,8 +206,8 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void {
206206 const gpa = macho_file.base.comp.gpa;
207207
208208 for (self.symtab.items(.nlist), self.symtab.items(.atom), self.globals.items, 0..) |nlist, atom_index, *global, i| {
209 if (!nlist.ext()) continue;
210 if (nlist.sect()) {
209 if (!nlist.n_type.bits.ext) continue;
210 if (nlist.n_type.bits.type == .sect) {
211211 const atom = self.getAtom(atom_index).?;
212212 if (!atom.isAlive()) continue;
213213 }
......@@ -221,7 +221,7 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void {
221221 }
222222 global.* = gop.index;
223223
224 if (nlist.undf() and !nlist.tentative()) continue;
224 if (nlist.n_type.bits.type == .undf and !nlist.tentative()) continue;
225225 if (gop.ref.getFile(macho_file) == null) {
226226 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
227227 continue;
......@@ -229,7 +229,7 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) !void {
229229
230230 if (self.asFile().getSymbolRank(.{
231231 .archive = false,
232 .weak = nlist.weakDef(),
232 .weak = nlist.n_desc.weak_def_or_ref_to_weak,
233233 .tentative = nlist.tentative(),
234234 }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) {
235235 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
......@@ -243,12 +243,12 @@ pub fn markLive(self: *ZigObject, macho_file: *MachO) void {
243243
244244 for (0..self.symbols.items.len) |i| {
245245 const nlist = self.symtab.items(.nlist)[i];
246 if (!nlist.ext()) continue;
246 if (!nlist.n_type.bits.ext) continue;
247247
248248 const ref = self.getSymbolRef(@intCast(i), macho_file);
249249 const file = ref.getFile(macho_file) orelse continue;
250250 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);
252252 if (should_keep and file == .object and !file.object.alive) {
253253 file.object.alive = true;
254254 file.object.markLive(macho_file);
......@@ -331,8 +331,8 @@ pub fn claimUnresolved(self: *ZigObject, macho_file: *MachO) void {
331331
332332 for (self.symbols.items, 0..) |*sym, i| {
333333 const nlist = self.symtab.items(.nlist)[i];
334 if (!nlist.ext()) continue;
335 if (!nlist.undf()) continue;
334 if (!nlist.n_type.bits.ext) continue;
335 if (nlist.n_type.bits.type != .undf) continue;
336336
337337 if (self.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;
338338
......@@ -974,7 +974,7 @@ fn updateNavCode(
974974 atom.setAlive(true);
975975 atom.name = sym.name;
976976 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 } };
978978 nlist.n_sect = sect_index + 1;
979979 self.symtab.items(.size)[sym.nlist_idx] = code.len;
980980
......@@ -1115,7 +1115,7 @@ fn createTlvDescriptor(
11151115 atom.name = sym.name;
11161116 nlist.n_strx = sym.name.pos;
11171117 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 } };
11191119 nlist.n_value = 0;
11201120 self.symtab.items(.size)[sym.nlist_idx] = size;
11211121
......@@ -1322,7 +1322,7 @@ pub fn updateExports(
13221322 const global_sym = &self.symbols.items[global_nlist_index];
13231323 global_nlist.n_value = nlist.n_value;
13241324 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 } };
13261326 self.symtab.items(.size)[global_nlist_index] = self.symtab.items(.size)[nlist_idx];
13271327 self.symtab.items(.atom)[global_nlist_index] = atom_index;
13281328 global_sym.atom_ref = .{ .index = atom_index, .file = self.index };
......@@ -1330,7 +1330,7 @@ pub fn updateExports(
13301330 switch (exp.opts.linkage) {
13311331 .internal => {
13321332 // 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;
13341334 global_sym.visibility = .hidden;
13351335 },
13361336 .strong => {
......@@ -1339,7 +1339,7 @@ pub fn updateExports(
13391339 .weak => {
13401340 // Weak linkage is specified as part of n_desc field.
13411341 // 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;
13431343 global_sym.visibility = .global;
13441344 global_sym.flags.weak = true;
13451345 },
......@@ -1394,7 +1394,7 @@ fn updateLazySymbol(
13941394
13951395 const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
13961396 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 } };
13981398 nlist.n_sect = output_section_index + 1;
13991399 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 {
2626
2727 for (aug[1..]) |ch| switch (ch) {
2828 'R' => {
29 const enc = try reader.takeByte();
30 if (enc != DW_EH_PE.pcrel | DW_EH_PE.absptr) {
29 const enc: DW.EH.PE = @bitCast(try reader.takeByte());
30 if (enc != @as(DW.EH.PE, .{ .type = .absptr, .rel = .pcrel })) {
3131 @panic("unexpected pointer encoding"); // TODO error
3232 }
3333 },
3434 'P' => {
35 const enc = try reader.takeByte();
36 if (enc != DW_EH_PE.pcrel | DW_EH_PE.indirect | DW_EH_PE.sdata4) {
35 const enc: DW.EH.PE = @bitCast(try reader.takeByte());
36 if (enc != @as(DW.EH.PE, .{ .type = .sdata4, .rel = .pcrel, .indirect = true })) {
3737 @panic("unexpected personality pointer encoding"); // TODO error
3838 }
3939 _ = try reader.takeInt(u32, .little); // personality pointer
4040 },
4141 'L' => {
42 const enc = try reader.takeByte();
43 switch (enc & DW_EH_PE.type_mask) {
44 DW_EH_PE.sdata4 => cie.lsda_size = .p32,
45 DW_EH_PE.absptr => cie.lsda_size = .p64,
46 else => unreachable, // TODO error
42 const enc: DW.EH.PE = @bitCast(try reader.takeByte());
43 switch (enc.type) {
44 .sdata4 => cie.lsda_size = .p32,
45 .absptr => cie.lsda_size = .p64,
46 else => @panic("unexpected lsda encoding"), // TODO error
4747 }
4848 },
4949 else => @panic("unexpected augmentation string"), // TODO error
......@@ -505,7 +505,7 @@ const Writer = std.Io.Writer;
505505
506506const Allocator = std.mem.Allocator;
507507const Atom = @import("Atom.zig");
508const DW_EH_PE = std.dwarf.EH.PE;
508const DW = std.dwarf;
509509const File = @import("file.zig").File;
510510const MachO = @import("../MachO.zig");
511511const Object = @import("Object.zig");
src/link/MachO/file.zig+8-8
......@@ -192,21 +192,21 @@ pub const File = union(enum) {
192192 assert(file == .object or file == .zig_object);
193193
194194 for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| {
195 if (!nlist.ext()) continue;
196 if (!nlist.undf()) continue;
195 if (!nlist.n_type.bits.ext) continue;
196 if (nlist.n_type.bits.type != .undf) continue;
197197
198198 if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;
199199
200200 const is_import = switch (macho_file.undefined_treatment) {
201201 .@"error" => false,
202 .warn, .suppress => nlist.weakRef(),
202 .warn, .suppress => nlist.n_desc.weak_ref,
203203 .dynamic_lookup => true,
204204 };
205205 if (is_import) {
206206 sym.value = 0;
207207 sym.atom_ref = .{ .index = 0, .file = 0 };
208208 sym.flags.weak = false;
209 sym.flags.weak_ref = nlist.weakRef();
209 sym.flags.weak_ref = nlist.n_desc.weak_ref;
210210 sym.flags.import = is_import;
211211 sym.visibility = .global;
212212
......@@ -223,13 +223,13 @@ pub const File = union(enum) {
223223 assert(file == .object or file == .zig_object);
224224
225225 for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| {
226 if (!nlist.ext()) continue;
227 if (!nlist.undf()) continue;
226 if (!nlist.n_type.bits.ext) continue;
227 if (nlist.n_type.bits.type != .undf) continue;
228228 if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;
229229
230230 sym.value = 0;
231231 sym.atom_ref = .{ .index = 0, .file = 0 };
232 sym.flags.weak_ref = nlist.weakRef();
232 sym.flags.weak_ref = nlist.n_desc.weak_ref;
233233 sym.flags.import = true;
234234 sym.visibility = .global;
235235
......@@ -247,7 +247,7 @@ pub const File = union(enum) {
247247 for (file.getSymbols(), file.getNlists(), 0..) |sym, nlist, i| {
248248 if (sym.visibility != .global) continue;
249249 if (sym.flags.weak) continue;
250 if (nlist.undf()) continue;
250 if (nlist.n_type.bits.type == .undf) continue;
251251 const ref = file.getSymbolRef(@intCast(i), macho_file);
252252 const ref_file = ref.getFile(macho_file) orelse continue;
253253 if (ref_file.getIndex() == file.getIndex()) continue;
src/main.zig+3-3
......@@ -43,7 +43,6 @@ const thread_stack_size = 60 << 20;
4343pub const std_options: std.Options = .{
4444 .wasiCwd = wasi_cwd,
4545 .logFn = log,
46 .enable_segfault_handler = false,
4746
4847 .log_level = switch (builtin.mode) {
4948 .Debug => .debug,
......@@ -53,6 +52,7 @@ pub const std_options: std.Options = .{
5352};
5453
5554pub const panic = crash_report.panic;
55pub const debug = crash_report.debug;
5656
5757var wasi_preopens: fs.wasi.Preopens = undefined;
5858pub fn wasi_cwd() std.os.wasi.fd_t {
......@@ -165,8 +165,6 @@ var debug_allocator: std.heap.DebugAllocator(.{
165165}) = .init;
166166
167167pub fn main() anyerror!void {
168 crash_report.initialize();
169
170168 const gpa, const is_debug = gpa: {
171169 if (build_options.debug_gpa) break :gpa .{ debug_allocator.allocator(), true };
172170 if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false };
......@@ -192,6 +190,8 @@ pub fn main() anyerror!void {
192190
193191 const args = try process.argsAlloc(arena);
194192
193 if (args.len > 0) crash_report.zig_argv0 = args[0];
194
195195 if (tracy.enable_allocation) {
196196 var gpa_tracy = tracy.tracyAllocator(gpa);
197197 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:
512512 if (target.os.tag.isDarwin()) return .async;
513513 if (libunwind) return .async;
514514 if (libtsan) return .async;
515 if (std.debug.Dwarf.abi.supportsUnwinding(target)) return .async;
515 if (std.debug.Dwarf.supportsUnwinding(target)) return .async;
516516 return .none;
517517}
518518
stage1/zig.h+8
......@@ -1510,8 +1510,16 @@ static inline zig_u128 zig_shl_u128(zig_u128 lhs, uint8_t rhs) {
15101510}
15111511
15121512static 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
15131520 zig_i128 sign_mask = lhs < zig_make_i128(0, 0) ? -zig_make_i128(0, 1) : zig_make_i128(0, 0);
15141521 return ((lhs ^ sign_mask) >> rhs) ^ sign_mask;
1522#endif
15151523}
15161524
15171525static 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 @@
11b: *std.Build,
22step: *Step,
3test_index: usize,
43test_filters: []const []const u8,
5optimize_modes: []const OptimizeMode,
6check_exe: *std.Build.Step.Compile,
4targets: []const std.Build.ResolvedTarget,
5convert_exe: *std.Build.Step.Compile,
76
87const Config = struct {
98 name: []const u8,
109 source: []const u8,
11 Debug: ?PerMode = null,
12 ReleaseSmall: ?PerMode = null,
13 ReleaseSafe: ?PerMode = null,
14 ReleaseFast: ?PerMode = null,
15
16 const PerMode = struct {
17 expect: []const u8,
18 exclude_arch: []const std.Target.Cpu.Arch = &.{},
19 exclude_os: []const std.Target.Os.Tag = &.{},
20 error_tracing: ?bool = null,
21 };
10 /// Whether this test case expects to have unwind tables / frame pointers.
11 unwind: enum {
12 /// This case assumes that some unwind strategy, safe or unsafe, is available.
13 any,
14 /// This case assumes that no unwinding strategy is available.
15 none,
16 /// This case assumes that a safe unwind strategy, like DWARF unwinding, is available.
17 safe,
18 /// This case assumes that at most, unsafe FP unwinding is available.
19 no_safe,
20 },
21 /// If `true`, the expected exit code is that of the default panic handler, rather than 0.
22 expect_panic: bool,
23 /// When debug info is not stripped, stdout is expected to **contain** (not equal!) this string.
24 expect: []const u8,
25 /// When debug info *is* stripped, stdout is expected to **contain** (not equal!) this string.
26 expect_strip: []const u8,
2227};
2328
2429pub fn addCase(self: *StackTrace, config: Config) void {
25 self.addCaseInner(config, true);
26 if (shouldTestNonLlvm(&self.b.graph.host.result)) {
27 self.addCaseInner(config, false);
30 for (self.targets) |*target| {
31 addCaseTarget(
32 self,
33 config,
34 target,
35 if (target.query.isNative()) null else t: {
36 break :t target.query.zigTriple(self.b.graph.arena) catch @panic("OOM");
37 },
38 );
2839 }
2940}
30
31fn addCaseInner(self: *StackTrace, config: Config, use_llvm: bool) void {
32 if (config.Debug) |per_mode|
33 self.addExpect(config.name, config.source, .Debug, use_llvm, per_mode);
34
35 if (config.ReleaseSmall) |per_mode|
36 self.addExpect(config.name, config.source, .ReleaseSmall, use_llvm, per_mode);
37
38 if (config.ReleaseFast) |per_mode|
39 self.addExpect(config.name, config.source, .ReleaseFast, use_llvm, per_mode);
40
41 if (config.ReleaseSafe) |per_mode|
42 self.addExpect(config.name, config.source, .ReleaseSafe, use_llvm, per_mode);
43}
44
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(),
41fn addCaseTarget(
42 self: *StackTrace,
43 config: Config,
44 target: *const std.Build.ResolvedTarget,
45 triple: ?[]const u8,
46) void {
47 const both_backends = switch (target.result.cpu.arch) {
48 .x86_64 => switch (target.result.ofmt) {
49 .elf => true,
4950 else => false,
5051 },
5152 else => false,
5253 };
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 }
53117}
54118
55fn addExpect(
119fn addCaseInstance(
56120 self: *StackTrace,
121 target: *const std.Build.ResolvedTarget,
122 triple: ?[]const u8,
57123 name: []const u8,
58124 source: []const u8,
59 optimize_mode: OptimizeMode,
60125 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,
62133) 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
66134 const b = self.b;
67 const annotated_case_name = b.fmt("check {s} ({s} {s})", .{
68 name, @tagName(optimize_mode), if (use_llvm) "llvm" else "selfhosted",
135
136 if (strip_debug) {
137 // To enable this coverage, one of two things needs to happen:
138 // * The compiler needs to gain the ability to strip only debug info (not symbols)
139 // * `std.Build.Step.ObjCopy` needs to be un-regressed
140 return;
141 }
142
143 if (strip_unwind) {
144 // To enable this coverage, `std.Build.Step.ObjCopy` needs to be un-regressed and gain the
145 // ability to remove individual sections. `-fno-unwind-tables` is insufficient because it
146 // does not prevent `.debug_frame` from being emitted. If we could, we would remove the
147 // following sections:
148 // * `.eh_frame`, `.eh_frame_hdr`, `.debug_frame` (Linux)
149 // * `__TEXT,__eh_frame`, `__TEXT,__unwind_info` (macOS)
150 return;
151 }
152
153 const annotated_case_name = b.fmt("check {s} ({s}{s}{s}{s}{s}{s}{s}{s})", .{
154 name,
155 triple orelse "",
156 if (triple != null) " " else "",
157 if (use_llvm) "llvm" else "selfhosted",
158 if (pie == true) " pie" else "",
159 if (link_libc == true) " libc" else "",
160 if (strip_debug) " strip" else "",
161 if (strip_unwind) " no_unwind" else "",
162 if (omit_frame_pointer) " no_fp" else "",
69163 });
70 for (self.test_filters) |test_filter| {
71 if (mem.indexOf(u8, annotated_case_name, test_filter)) |_| break;
72 } else if (self.test_filters.len > 0) return;
164 if (self.test_filters.len > 0) {
165 for (self.test_filters) |test_filter| {
166 if (mem.indexOf(u8, annotated_case_name, test_filter)) |_| break;
167 } else return;
168 }
73169
74170 const write_files = b.addWriteFiles();
75171 const source_zig = write_files.add("source.zig", source);
......@@ -77,27 +173,34 @@ fn addExpect(
77173 .name = "test",
78174 .root_module = b.createModule(.{
79175 .root_source_file = source_zig,
80 .optimize = optimize_mode,
81 .target = b.graph.host,
82 .error_tracing = mode_config.error_tracing,
176 .optimize = .Debug,
177 .target = target.*,
178 .omit_frame_pointer = omit_frame_pointer,
179 .link_libc = link_libc,
180 .unwind_tables = if (strip_unwind) .none else null,
181 // make panics single-threaded so that they don't include a thread ID
182 .single_threaded = expect_panic,
83183 }),
84184 .use_llvm = use_llvm,
85185 });
186 exe.pie = pie;
86187 exe.bundle_ubsan_rt = false;
87188
88189 const run = b.addRunArtifact(exe);
89190 run.removeEnvironmentVariable("CLICOLOR_FORCE");
90191 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 } });
92197 run.expectStdOutEqual("");
93198
94 const check_run = b.addRunArtifact(self.check_exe);
199 const check_run = b.addRunArtifact(self.convert_exe);
95200 check_run.setName(annotated_case_name);
96201 check_run.addFileArg(run.captureStdErr(.{}));
97 check_run.addArgs(&.{
98 @tagName(optimize_mode),
99 });
100 check_run.expectStdOutEqual(mode_config.expect);
202 check_run.expectExitCode(0);
203 check_run.addCheck(.{ .expect_stdout_match = expect_stderr });
101204
102205 self.step.dependOn(&check_run.step);
103206}
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 @@
1const std = @import("std");
2const os = std.os;
3const tests = @import("tests.zig");
4
5pub fn addCases(cases: *tests.StackTracesContext) void {
1pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {
62 cases.addCase(.{
7 .name = "return",
3 .name = "simple panic",
84 .source =
9 \\pub fn main() !void {
10 \\ return error.TheSkyIsFalling;
5 \\pub fn main() void {
6 \\ foo();
117 \\}
12 ,
13 .Debug = .{
14 .expect =
15 \\error: TheSkyIsFalling
16 \\source.zig:2:5: [address] in main ([main_file])
17 \\ return error.TheSkyIsFalling;
18 \\ ^
19 \\
20 ,
21 },
22 .ReleaseSafe = .{
23 .exclude_os = &.{
24 .windows, // TODO
25 .linux, // defeated by aggressive inlining
26 },
27 .expect =
28 \\error: TheSkyIsFalling
29 \\source.zig:2:5: [address] in [function]
30 \\ return error.TheSkyIsFalling;
31 \\ ^
32 \\
33 ,
34 .error_tracing = true,
35 },
36 .ReleaseFast = .{
37 .expect =
38 \\error: TheSkyIsFalling
39 \\
40 ,
41 },
42 .ReleaseSmall = .{
43 .expect =
44 \\error: TheSkyIsFalling
45 \\
46 ,
47 },
48 });
49
50 cases.addCase(.{
51 .name = "try return",
52 .source =
53 \\fn foo() !void {
54 \\ return error.TheSkyIsFalling;
8 \\fn foo() void {
9 \\ @panic("oh no");
5510 \\}
5611 \\
57 \\pub fn main() !void {
58 \\ try foo();
59 \\}
6012 ,
61 .Debug = .{
62 .expect =
63 \\error: TheSkyIsFalling
64 \\source.zig:2:5: [address] in foo ([main_file])
65 \\ return error.TheSkyIsFalling;
66 \\ ^
67 \\source.zig:6:5: [address] in main ([main_file])
68 \\ try foo();
69 \\ ^
70 \\
71 ,
72 },
73 .ReleaseSafe = .{
74 .exclude_os = &.{
75 .windows, // TODO
76 },
77 .expect =
78 \\error: TheSkyIsFalling
79 \\source.zig:2:5: [address] in [function]
80 \\ return error.TheSkyIsFalling;
81 \\ ^
82 \\source.zig:6:5: [address] in [function]
83 \\ try foo();
84 \\ ^
85 \\
86 ,
87 .error_tracing = true,
88 },
89 .ReleaseFast = .{
90 .expect =
91 \\error: TheSkyIsFalling
92 \\
93 ,
94 },
95 .ReleaseSmall = .{
96 .expect =
97 \\error: TheSkyIsFalling
98 \\
99 ,
100 },
101 });
102 cases.addCase(.{
103 .name = "non-error return pops error trace",
104 .source =
105 \\fn bar() !void {
106 \\ return error.UhOh;
107 \\}
13 .unwind = .any,
14 .expect_panic = true,
15 .expect =
16 \\panic: oh no
17 \\source.zig:5:5: [address] in foo
18 \\ @panic("oh no");
19 \\ ^
20 \\source.zig:2:8: [address] in main
21 \\ foo();
22 \\ ^
10823 \\
109 \\fn foo() !void {
110 \\ bar() catch {
111 \\ return; // non-error result: success
112 \\ };
113 \\}
24 ,
25 .expect_strip =
26 \\panic: oh no
27 \\???:?:?: [address] in source.foo
28 \\???:?:?: [address] in source.main
11429 \\
115 \\pub fn main() !void {
116 \\ try foo();
117 \\ return error.UnrelatedError;
118 \\}
11930 ,
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 },
15531 });
15632
15733 cases.addCase(.{
158 .name = "continue in while loop",
34 .name = "simple panic with no unwind strategy",
15935 .source =
160 \\fn foo() !void {
161 \\ return error.UhOh;
36 \\pub fn main() void {
37 \\ foo();
16238 \\}
163 \\
164 \\pub fn main() !void {
165 \\ var i: usize = 0;
166 \\ while (i < 3) : (i += 1) {
167 \\ foo() catch continue;
168 \\ }
169 \\ return error.UnrelatedError;
39 \\fn foo() void {
40 \\ @panic("oh no");
17041 \\}
42 \\
17143 ,
172 .Debug = .{
173 .expect =
174 \\error: UnrelatedError
175 \\source.zig:10:5: [address] in main ([main_file])
176 \\ return error.UnrelatedError;
177 \\ ^
178 \\
179 ,
180 },
181 .ReleaseSafe = .{
182 .exclude_os = &.{
183 .windows, // TODO
184 .linux, // defeated by aggressive inlining
185 },
186 .expect =
187 \\error: UnrelatedError
188 \\source.zig:10:5: [address] in [function]
189 \\ return error.UnrelatedError;
190 \\ ^
191 \\
192 ,
193 .error_tracing = true,
194 },
195 .ReleaseFast = .{
196 .expect =
197 \\error: UnrelatedError
198 \\
199 ,
200 },
201 .ReleaseSmall = .{
202 .expect =
203 \\error: UnrelatedError
204 \\
205 ,
206 },
44 .unwind = .none,
45 .expect_panic = true,
46 .expect = "panic: oh no",
47 .expect_strip = "panic: oh no",
20748 });
20849
20950 cases.addCase(.{
210 .name = "try return + handled catch/if-else",
51 .name = "dump current trace",
21152 .source =
212 \\fn foo() !void {
213 \\ return error.TheSkyIsFalling;
53 \\pub fn main() void {
54 \\ foo(bar());
21455 \\}
215 \\
216 \\pub fn main() !void {
217 \\ foo() catch {}; // should not affect error trace
218 \\ if (foo()) |_| {} else |_| {
219 \\ // should also not affect error trace
220 \\ }
221 \\ try foo();
56 \\fn bar() void {
57 \\ qux(123);
22258 \\}
59 \\fn foo(_: void) void {}
60 \\fn qux(x: u32) void {
61 \\ std.debug.dumpCurrentStackTrace(.{});
62 \\ _ = x;
63 \\}
64 \\const std = @import("std");
65 \\
22366 ,
224 .Debug = .{
225 .expect =
226 \\error: TheSkyIsFalling
227 \\source.zig:2:5: [address] in foo ([main_file])
228 \\ return error.TheSkyIsFalling;
229 \\ ^
230 \\source.zig:10:5: [address] in main ([main_file])
231 \\ try foo();
232 \\ ^
233 \\
234 ,
235 },
236 .ReleaseSafe = .{
237 .exclude_os = &.{
238 .windows, // TODO
239 .linux, // defeated by aggressive inlining
240 },
241 .expect =
242 \\error: TheSkyIsFalling
243 \\source.zig:2:5: [address] in [function]
244 \\ return error.TheSkyIsFalling;
245 \\ ^
246 \\source.zig:10:5: [address] in [function]
247 \\ try foo();
248 \\ ^
249 \\
250 ,
251 .error_tracing = true,
252 },
253 .ReleaseFast = .{
254 .expect =
255 \\error: TheSkyIsFalling
256 \\
257 ,
258 },
259 .ReleaseSmall = .{
260 .expect =
261 \\error: TheSkyIsFalling
262 \\
263 ,
264 },
265 });
266
267 cases.addCase(.{
268 .name = "break from inline loop pops error return trace",
269 .source =
270 \\fn foo() !void { return error.FooBar; }
67 .unwind = .safe,
68 .expect_panic = false,
69 .expect =
70 \\source.zig:9:36: [address] in qux
71 \\ std.debug.dumpCurrentStackTrace(.{});
72 \\ ^
73 \\source.zig:5:8: [address] in bar
74 \\ qux(123);
75 \\ ^
76 \\source.zig:2:12: [address] in main
77 \\ foo(bar());
78 \\ ^
27179 \\
272 \\pub fn main() !void {
273 \\ comptime var i: usize = 0;
274 \\ b: inline while (i < 5) : (i += 1) {
275 \\ foo() catch {
276 \\ break :b; // non-error break, success
277 \\ };
278 \\ }
279 \\ // foo() was successfully handled, should not appear in trace
80 ,
81 .expect_strip =
82 \\???:?:?: [address] in source.qux
83 \\???:?:?: [address] in source.bar
84 \\???:?:?: [address] in source.main
28085 \\
281 \\ return error.BadTime;
282 \\}
28386 ,
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 },
31987 });
32088
32189 cases.addCase(.{
322 .name = "catch and re-throw error",
90 .name = "dump current trace with no unwind strategy",
32391 .source =
324 \\fn foo() !void {
325 \\ return error.TheSkyIsFalling;
92 \\pub fn main() void {
93 \\ foo(bar());
32694 \\}
327 \\
328 \\pub fn main() !void {
329 \\ return foo() catch error.AndMyCarIsOutOfGas;
95 \\fn bar() void {
96 \\ qux(123);
33097 \\}
331 ,
332 .Debug = .{
333 .expect =
334 \\error: AndMyCarIsOutOfGas
335 \\source.zig:2:5: [address] in foo ([main_file])
336 \\ return error.TheSkyIsFalling;
337 \\ ^
338 \\source.zig:6:5: [address] in main ([main_file])
339 \\ return foo() catch error.AndMyCarIsOutOfGas;
340 \\ ^
341 \\
342 ,
343 },
344 .ReleaseSafe = .{
345 .exclude_os = &.{
346 .windows, // TODO
347 .linux, // defeated by aggressive inlining
348 },
349 .expect =
350 \\error: AndMyCarIsOutOfGas
351 \\source.zig:2:5: [address] in [function]
352 \\ return error.TheSkyIsFalling;
353 \\ ^
354 \\source.zig:6:5: [address] in [function]
355 \\ return foo() catch error.AndMyCarIsOutOfGas;
356 \\ ^
357 \\
358 ,
359 .error_tracing = true,
360 },
361 .ReleaseFast = .{
362 .expect =
363 \\error: AndMyCarIsOutOfGas
364 \\
365 ,
366 },
367 .ReleaseSmall = .{
368 .expect =
369 \\error: AndMyCarIsOutOfGas
370 \\
371 ,
372 },
373 });
374
375 cases.addCase(.{
376 .name = "errors stored in var do not contribute to error trace",
377 .source =
378 \\fn foo() !void {
379 \\ return error.TheSkyIsFalling;
98 \\fn foo(_: void) void {}
99 \\fn qux(x: u32) void {
100 \\ std.debug.print("pre\n", .{});
101 \\ std.debug.dumpCurrentStackTrace(.{});
102 \\ std.debug.print("post\n", .{});
103 \\ _ = x;
380104 \\}
105 \\const std = @import("std");
381106 \\
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 \\}
390107 ,
391 .Debug = .{
392 .expect =
393 \\error: SomethingUnrelatedWentWrong
394 \\source.zig:11:5: [address] in main ([main_file])
395 \\ return error.SomethingUnrelatedWentWrong;
396 \\ ^
397 \\
398 ,
399 },
400 .ReleaseSafe = .{
401 .exclude_os = &.{
402 .windows, // TODO
403 .linux, // defeated by aggressive inlining
404 },
405 .expect =
406 \\error: SomethingUnrelatedWentWrong
407 \\source.zig:11:5: [address] in [function]
408 \\ return error.SomethingUnrelatedWentWrong;
409 \\ ^
410 \\
411 ,
412 .error_tracing = true,
413 },
414 .ReleaseFast = .{
415 .expect =
416 \\error: SomethingUnrelatedWentWrong
417 \\
418 ,
419 },
420 .ReleaseSmall = .{
421 .expect =
422 \\error: SomethingUnrelatedWentWrong
423 \\
424 ,
425 },
108 .unwind = .no_safe,
109 .expect_panic = false,
110 .expect = "pre\npost\n",
111 .expect_strip = "pre\npost\n",
426112 });
427113
428114 cases.addCase(.{
429 .name = "error stored in const has trace preserved for duration of block",
115 .name = "dump captured trace",
430116 .source =
431 \\fn foo() !void { return error.TheSkyIsFalling; }
432 \\fn bar() !void { return error.InternalError; }
433 \\fn baz() !void { return error.UnexpectedReality; }
434 \\
435 \\pub fn main() !void {
436 \\ const x = foo();
437 \\ const y = b: {
438 \\ if (true)
439 \\ break :b bar();
440 \\
441 \\ break :b {};
442 \\ };
443 \\ x catch {};
444 \\ y catch {};
445 \\ // foo()/bar() error traces not popped until end of block
446 \\
447 \\ {
448 \\ const z = baz();
449 \\ z catch {};
450 \\ // baz() error trace still alive here
451 \\ }
452 \\ // baz() error trace popped, foo(), bar() still alive
453 \\ return error.StillUnresolved;
117 \\pub fn main() void {
118 \\ var stack_trace_buf: [8]usize = undefined;
119 \\ dumpIt(&captureIt(&stack_trace_buf));
454120 \\}
455 ,
456 .Debug = .{
457 .expect =
458 \\error: StillUnresolved
459 \\source.zig:1:18: [address] in foo ([main_file])
460 \\fn foo() !void { return error.TheSkyIsFalling; }
461 \\ ^
462 \\source.zig:2:18: [address] in bar ([main_file])
463 \\fn bar() !void { return error.InternalError; }
464 \\ ^
465 \\source.zig:23:5: [address] in main ([main_file])
466 \\ return error.StillUnresolved;
467 \\ ^
468 \\
469 ,
470 },
471 .ReleaseSafe = .{
472 .exclude_os = &.{
473 .windows, // TODO
474 .linux, // defeated by aggressive inlining
475 },
476 .expect =
477 \\error: StillUnresolved
478 \\source.zig:1:18: [address] in [function]
479 \\fn foo() !void { return error.TheSkyIsFalling; }
480 \\ ^
481 \\source.zig:2:18: [address] in [function]
482 \\fn bar() !void { return error.InternalError; }
483 \\ ^
484 \\source.zig:23:5: [address] in [function]
485 \\ return error.StillUnresolved;
486 \\ ^
487 \\
488 ,
489 .error_tracing = true,
490 },
491 .ReleaseFast = .{
492 .expect =
493 \\error: StillUnresolved
494 \\
495 ,
496 },
497 .ReleaseSmall = .{
498 .expect =
499 \\error: StillUnresolved
500 \\
501 ,
502 },
503 });
504
505 cases.addCase(.{
506 .name = "error passed to function has its trace preserved for duration of the call",
507 .source =
508 \\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void {
509 \\ actual_error catch |err| {
510 \\ if (err == expected_error) return {};
511 \\ };
512 \\ return error.TestExpectedError;
121 \\fn captureIt(buf: []usize) std.builtin.StackTrace {
122 \\ return captureItInner(buf);
513123 \\}
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");
514131 \\
515 \\fn alwaysErrors() !void { return error.ThisErrorShouldNotAppearInAnyTrace; }
516 \\fn foo() !void { return error.Foo; }
132 ,
133 .unwind = .safe,
134 .expect_panic = false,
135 .expect =
136 \\source.zig:12:46: [address] in captureItInner
137 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
138 \\ ^
139 \\source.zig:6:26: [address] in captureIt
140 \\ return captureItInner(buf);
141 \\ ^
142 \\source.zig:3:22: [address] in main
143 \\ dumpIt(&captureIt(&stack_trace_buf));
144 \\ ^
517145 \\
518 \\pub fn main() !void {
519 \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
520 \\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
521 \\ try expectError(error.Foo, foo());
146 ,
147 .expect_strip =
148 \\???:?:?: [address] in source.captureItInner
149 \\???:?:?: [address] in source.captureIt
150 \\???:?:?: [address] in source.main
522151 \\
523 \\ // Only the error trace for this failing check should appear:
524 \\ try expectError(error.Bar, foo());
525 \\}
526152 ,
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 },
573153 });
574154
575155 cases.addCase(.{
576 .name = "try return from within catch",
156 .name = "dump captured trace with no unwind strategy",
577157 .source =
578 \\fn foo() !void {
579 \\ return error.TheSkyIsFalling;
158 \\pub fn main() void {
159 \\ var stack_trace_buf: [8]usize = undefined;
160 \\ dumpIt(&captureIt(&stack_trace_buf));
580161 \\}
581 \\
582 \\fn bar() !void {
583 \\ return error.AndMyCarIsOutOfGas;
162 \\fn captureIt(buf: []usize) std.builtin.StackTrace {
163 \\ return captureItInner(buf);
584164 \\}
585 \\
586 \\pub fn main() !void {
587 \\ foo() catch { // error trace should include foo()
588 \\ try bar();
589 \\ };
165 \\fn dumpIt(st: *const std.builtin.StackTrace) void {
166 \\ std.debug.dumpStackTrace(st);
590167 \\}
591 ,
592 .Debug = .{
593 .expect =
594 \\error: AndMyCarIsOutOfGas
595 \\source.zig:2:5: [address] in foo ([main_file])
596 \\ return error.TheSkyIsFalling;
597 \\ ^
598 \\source.zig:6:5: [address] in bar ([main_file])
599 \\ return error.AndMyCarIsOutOfGas;
600 \\ ^
601 \\source.zig:11:9: [address] in main ([main_file])
602 \\ try bar();
603 \\ ^
604 \\
605 ,
606 },
607 .ReleaseSafe = .{
608 .exclude_os = &.{
609 .windows, // TODO
610 },
611 .expect =
612 \\error: AndMyCarIsOutOfGas
613 \\source.zig:2:5: [address] in [function]
614 \\ return error.TheSkyIsFalling;
615 \\ ^
616 \\source.zig:6:5: [address] in [function]
617 \\ return error.AndMyCarIsOutOfGas;
618 \\ ^
619 \\source.zig:11:9: [address] in [function]
620 \\ try bar();
621 \\ ^
622 \\
623 ,
624 .error_tracing = true,
625 },
626 .ReleaseFast = .{
627 .expect =
628 \\error: AndMyCarIsOutOfGas
629 \\
630 ,
631 },
632 .ReleaseSmall = .{
633 .expect =
634 \\error: AndMyCarIsOutOfGas
635 \\
636 ,
637 },
638 });
639
640 cases.addCase(.{
641 .name = "try return from within if-else",
642 .source =
643 \\fn foo() !void {
644 \\ return error.TheSkyIsFalling;
645 \\}
646 \\
647 \\fn bar() !void {
648 \\ return error.AndMyCarIsOutOfGas;
168 \\fn captureItInner(buf: []usize) std.builtin.StackTrace {
169 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
649170 \\}
171 \\const std = @import("std");
650172 \\
651 \\pub fn main() !void {
652 \\ if (foo()) |_| {} else |_| { // error trace should include foo()
653 \\ try bar();
654 \\ }
655 \\}
656173 ,
657 .Debug = .{
658 .expect =
659 \\error: AndMyCarIsOutOfGas
660 \\source.zig:2:5: [address] in foo ([main_file])
661 \\ return error.TheSkyIsFalling;
662 \\ ^
663 \\source.zig:6:5: [address] in bar ([main_file])
664 \\ return error.AndMyCarIsOutOfGas;
665 \\ ^
666 \\source.zig:11:9: [address] in main ([main_file])
667 \\ try bar();
668 \\ ^
669 \\
670 ,
671 },
672 .ReleaseSafe = .{
673 .exclude_os = &.{
674 .windows, // TODO
675 },
676 .expect =
677 \\error: AndMyCarIsOutOfGas
678 \\source.zig:2:5: [address] in [function]
679 \\ return error.TheSkyIsFalling;
680 \\ ^
681 \\source.zig:6:5: [address] in [function]
682 \\ return error.AndMyCarIsOutOfGas;
683 \\ ^
684 \\source.zig:11:9: [address] in [function]
685 \\ try bar();
686 \\ ^
687 \\
688 ,
689 .error_tracing = true,
690 },
691 .ReleaseFast = .{
692 .expect =
693 \\error: AndMyCarIsOutOfGas
694 \\
695 ,
696 },
697 .ReleaseSmall = .{
698 .expect =
699 \\error: AndMyCarIsOutOfGas
700 \\
701 ,
702 },
174 .unwind = .no_safe,
175 .expect_panic = false,
176 .expect = "(empty stack trace)\n",
177 .expect_strip = "(empty stack trace)\n",
703178 });
704179
705180 cases.addCase(.{
706 .name = "try try return return",
181 .name = "dump captured trace on thread",
707182 .source =
708 \\fn foo() !void {
709 \\ try bar();
183 \\pub fn main() !void {
184 \\ var stack_trace_buf: [8]usize = undefined;
185 \\ const t = try std.Thread.spawn(.{}, threadMain, .{&stack_trace_buf});
186 \\ t.join();
710187 \\}
711 \\
712 \\fn bar() !void {
713 \\ return make_error();
188 \\fn threadMain(stack_trace_buf: []usize) void {
189 \\ dumpIt(&captureIt(stack_trace_buf));
714190 \\}
715 \\
716 \\fn make_error() !void {
717 \\ return error.TheSkyIsFalling;
191 \\fn captureIt(buf: []usize) std.builtin.StackTrace {
192 \\ return captureItInner(buf);
718193 \\}
719 \\
720 \\pub fn main() !void {
721 \\ try foo();
194 \\fn dumpIt(st: *const std.builtin.StackTrace) void {
195 \\ std.debug.dumpStackTrace(st);
196 \\}
197 \\fn captureItInner(buf: []usize) std.builtin.StackTrace {
198 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
722199 \\}
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 =
781200 \\const std = @import("std");
782201 \\
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 \\}
793202 ,
794 .Debug = .{
795 // std.debug.sys_can_stack_trace
796 .exclude_arch = &.{
797 .loongarch32,
798 .loongarch64,
799 .mips,
800 .mipsel,
801 .mips64,
802 .mips64el,
803 .s390x,
804 },
805 .exclude_os = &.{
806 .freebsd,
807 .openbsd, // integer overflow
808 .windows, // TODO intermittent failures
809 },
810 .expect =
811 \\source.zig:7:8: [address] in foo ([main_file])
812 \\ bar();
813 \\ ^
814 \\source.zig:10:8: [address] in main ([main_file])
815 \\ foo();
816 \\ ^
817 \\
818 ,
819 },
820 });
821 cases.addCase(.{
822 .name = "error union switch with call operand",
823 .source =
824 \\pub fn main() !void {
825 \\ try foo();
826 \\ return error.TheSkyIsFalling;
827 \\}
203 .unwind = .safe,
204 .expect_panic = false,
205 .expect =
206 \\source.zig:16:46: [address] in captureItInner
207 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
208 \\ ^
209 \\source.zig:10:26: [address] in captureIt
210 \\ return captureItInner(buf);
211 \\ ^
212 \\source.zig:7:22: [address] in threadMain
213 \\ dumpIt(&captureIt(stack_trace_buf));
214 \\ ^
828215 \\
829 \\noinline fn failure() error{ Fatal, NonFatal }!void {
830 \\ return error.NonFatal;
831 \\}
216 ,
217 .expect_strip =
218 \\???:?:?: [address] in source.captureItInner
219 \\???:?:?: [address] in source.captureIt
220 \\???:?:?: [address] in source.threadMain
832221 \\
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 \\}
839222 ,
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 },
877223 });
878224}
test/standalone/coff_dwarf/build.zig+7-6
......@@ -1,9 +1,10 @@
11const std = @import("std");
2const builtin = @import("builtin");
32
43/// This tests the path where DWARF information is embedded in a COFF binary
54pub fn build(b: *std.Build) void {
6 switch (builtin.cpu.arch) {
5 const host = b.graph.host;
6
7 switch (host.result.cpu.arch) {
78 .aarch64,
89 .x86,
910 .x86_64,
......@@ -15,10 +16,10 @@ pub fn build(b: *std.Build) void {
1516 b.default_step = test_step;
1617
1718 const optimize: std.builtin.OptimizeMode = .Debug;
18 const target = if (builtin.os.tag == .windows)
19 b.standardTargetOptions(.{})
20 else
21 b.resolveTargetQuery(.{ .os_tag = .windows });
19 const target = switch (host.result.os.tag) {
20 .windows => host,
21 else => b.resolveTargetQuery(.{ .os_tag = .windows }),
22 };
2223
2324 const exe = b.addExecutable(.{
2425 .name = "main",
test/standalone/coff_dwarf/main.zig+23-16
......@@ -1,27 +1,34 @@
11const std = @import("std");
2const assert = std.debug.assert;
3const testing = std.testing;
2const fatal = std.process.fatal;
43
54extern fn add(a: u32, b: u32, addr: *usize) u32;
65
7pub fn main() !void {
8 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
9 defer assert(gpa.deinit() == .ok);
10 const allocator = gpa.allocator();
6pub fn main() void {
7 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;
8 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);
9 const gpa = debug_alloc_inst.allocator();
1110
12 var debug_info = try std.debug.SelfInfo.open(allocator);
13 defer debug_info.deinit();
11 var di: std.debug.SelfInfo = .init;
12 defer di.deinit(gpa);
1413
1514 var add_addr: usize = undefined;
1615 _ = add(1, 2, &add_addr);
1716
18 const module = try debug_info.getModuleForAddress(add_addr);
19 const symbol = try module.getSymbolAtAddress(allocator, add_addr);
20 defer if (symbol.source_location) |sl| allocator.free(sl.file_name);
17 const symbol = di.getSymbol(gpa, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err});
18 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
2119
22 try testing.expectEqualStrings("add", symbol.name);
23 try testing.expect(symbol.source_location != null);
24 try testing.expectEqualStrings("shared_lib.c", std.fs.path.basename(symbol.source_location.?.file_name));
25 try testing.expectEqual(@as(u64, 3), symbol.source_location.?.line);
26 try testing.expectEqual(@as(u64, 0), symbol.source_location.?.column);
20 if (symbol.name == null) fatal("failed to resolve symbol name", .{});
21 if (symbol.compile_unit_name == null) fatal("failed to resolve compile unit", .{});
22 if (symbol.source_location == null) fatal("failed to resolve source location", .{});
23
24 if (!std.mem.eql(u8, symbol.name.?, "add")) {
25 fatal("incorrect symbol name '{s}'", .{symbol.name.?});
26 }
27 const sl = &symbol.source_location.?;
28 if (!std.mem.eql(u8, std.fs.path.basename(sl.file_name), "shared_lib.c")) {
29 fatal("incorrect file name '{s}'", .{sl.file_name});
30 }
31 if (sl.line != 3 or sl.column != 0) {
32 fatal("incorrect line/column :{d}:{d}", .{ sl.line, sl.column });
33 }
2734}
test/standalone/stack_iterator/unwind.zig+31-29
......@@ -1,27 +1,19 @@
11const std = @import("std");
22const builtin = @import("builtin");
3const debug = std.debug;
4const testing = std.testing;
3const fatal = std.process.fatal;
54
6noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void {
5noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
76 expected[0] = @returnAddress();
8
9 var context: debug.ThreadContext = undefined;
10 testing.expect(debug.getContext(&context)) catch @panic("failed to getContext");
11
12 const debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo");
13 var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context, @frameAddress()) catch @panic("failed to initWithContext");
14 defer it.deinit();
15
16 for (unwound) |*addr| {
17 if (it.next()) |return_address| addr.* = return_address;
18 }
7 return std.debug.captureCurrentStackTrace(.{
8 .first_address = @returnAddress(),
9 .allow_unsafe_unwind = true,
10 }, addr_buf);
1911}
2012
21noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {
13noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
2214 // Exercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored
2315 if (builtin.target.ofmt != .c) {
24 switch (builtin.cpu.arch) {
16 switch (builtin.target.cpu.arch) {
2517 .x86 => {
2618 if (builtin.omit_frame_pointer) {
2719 asm volatile (
......@@ -67,10 +59,10 @@ noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {
6759 }
6860
6961 expected[1] = @returnAddress();
70 frame3(expected, unwound);
62 return frame3(expected, addr_buf);
7163}
7264
73noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void {
65noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
7466 expected[2] = @returnAddress();
7567
7668 // 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 {
7870 var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined;
7971 _ = std.mem.doNotOptimizeAway(&pad);
8072
81 frame2(expected, unwound);
73 return frame2(expected, addr_buf);
8274}
8375
84noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void {
76noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
8577 expected[3] = @returnAddress();
86 frame1(expected, unwound);
78 return frame1(expected, addr_buf);
8779}
8880
89pub fn main() !void {
90 // Disabled until the DWARF unwinder bugs on .aarch64 are solved
91 if (builtin.omit_frame_pointer and comptime builtin.target.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) return;
92
93 if (!std.debug.have_ucontext or !std.debug.have_getcontext) return;
81pub fn main() void {
82 if (std.debug.cpu_context.Native == noreturn and builtin.omit_frame_pointer) {
83 // Stack unwinding is impossible.
84 return;
85 }
9486
9587 var expected: [4]usize = undefined;
96 var unwound: [4]usize = undefined;
97 frame0(&expected, &unwound);
98 try testing.expectEqual(expected, unwound);
88 var addr_buf: [4]usize = undefined;
89 const trace = frame0(&expected, &addr_buf);
90 // There may be *more* than 4 frames (due to the caller of `main`); that's okay.
91 if (trace.index < 4) {
92 fatal("expected at least 4 frames, got '{d}':\n{f}", .{ trace.index, &trace });
93 }
94 if (!std.mem.eql(usize, trace.instruction_addresses, &expected)) {
95 const expected_trace: std.builtin.StackTrace = .{
96 .index = 4,
97 .instruction_addresses = &expected,
98 };
99 fatal("expected trace:\n{f}\nactual trace:\n{f}", .{ &expected_trace, &trace });
100 }
99101}
test/standalone/stack_iterator/unwind_freestanding.zig+35-33
......@@ -1,26 +1,21 @@
1/// Test StackIterator on 'freestanding' target. Based on unwind.zig.
1//! Test StackIterator on 'freestanding' target. Based on unwind.zig.
2
23const 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 {
76 expected[0] = @returnAddress();
8
9 var it = debug.StackIterator.init(@returnAddress(), @frameAddress());
10 defer it.deinit();
11
12 // Save StackIterator's frame addresses into `unwound`:
13 for (unwound) |*addr| {
14 if (it.next()) |return_address| addr.* = return_address;
15 }
7 return std.debug.captureCurrentStackTrace(.{
8 .first_address = @returnAddress(),
9 .allow_unsafe_unwind = true,
10 }, addr_buf);
1611}
1712
18noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {
13noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
1914 expected[1] = @returnAddress();
20 frame3(expected, unwound);
15 return frame3(expected, addr_buf);
2116}
2217
23noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void {
18noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
2419 expected[2] = @returnAddress();
2520
2621 // 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 {
2823 var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined;
2924 _ = std.mem.doNotOptimizeAway(&pad);
3025
31 frame2(expected, unwound);
26 return frame2(expected, addr_buf);
3227}
3328
34noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void {
29noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
3530 expected[3] = @returnAddress();
36 frame1(expected, unwound);
31 return frame1(expected, addr_buf);
3732}
3833
39// No-OS entrypoint
34/// No-OS entrypoint
4035export fn _start() callconv(.withStackAlign(.c, 1)) noreturn {
4136 var expected: [4]usize = undefined;
42 var unwound: [4]usize = undefined;
43 frame0(&expected, &unwound);
37 var addr_buf: [4]usize = undefined;
38 const trace = frame0(&expected, &addr_buf);
4439
45 // Verify result (no std.testing in freestanding)
46 var missed: c_int = 0;
47 for (expected, unwound) |expectFA, actualFA| {
48 if (expectFA != actualFA) {
49 missed += 1;
50 }
51 }
40 // Since we can't print from this freestanding test, we'll just use the exit
41 // code to communicate error conditions.
42
43 // Unlike `unwind.zig`, we can expect *exactly* 4 frames, as we are the
44 // actual entry point and the frame pointer will be 0 on entry.
45 if (trace.index != 4) exit(1);
46 if (trace.instruction_addresses[0] != expected[0]) exit(2);
47 if (trace.instruction_addresses[1] != expected[1]) exit(3);
48 if (trace.instruction_addresses[2] != expected[2]) exit(4);
49 if (trace.instruction_addresses[3] != expected[3]) exit(5);
50
51 exit(0);
52}
5253
53 // Need to compile with the target OS as "freestanding" or "other" to
54 // exercise the StackIterator code, but when run as a regression test
55 // need to actually exit. So assume we're running on x86_64-linux ...
54fn exit(code: u8) noreturn {
55 // We are intentionally compiling with the target OS being "freestanding" to
56 // exercise std.debug, but we still need to exit the process somehow; so do
57 // the appropriate x86_64-linux syscall.
5658 asm volatile (
5759 \\movl $60, %%eax
5860 \\syscall
5961 :
60 : [missed] "{edi}" (missed),
62 : [code] "{edi}" (code),
6163 : .{ .edi = true, .eax = true });
6264
63 while (true) {} // unreached
65 unreachable;
6466}
test/standalone/windows_bat_args/fuzz.zig+40-35
......@@ -3,14 +3,14 @@ const builtin = @import("builtin");
33const Allocator = std.mem.Allocator;
44
55pub fn main() anyerror!void {
6 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
7 defer if (gpa.deinit() == .leak) @panic("found memory leaks");
8 const allocator = gpa.allocator();
6 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;
7 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);
8 const gpa = debug_alloc_inst.allocator();
99
10 var it = try std.process.argsWithAllocator(allocator);
10 var it = try std.process.argsWithAllocator(gpa);
1111 defer it.deinit();
1212 _ = 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
1515 const iterations: u64 = iterations: {
1616 const arg = it.next() orelse "0";
......@@ -42,58 +42,63 @@ pub fn main() anyerror!void {
4242 try tmp.dir.setAsCwd();
4343 defer tmp.parent_dir.setAsCwd() catch {};
4444
45 var buf = try std.array_list.Managed(u8).initCapacity(allocator, 128);
46 defer buf.deinit();
47 try buf.appendSlice("@echo off\n");
48 try buf.append('"');
49 try buf.appendSlice(child_exe_path);
50 try buf.append('"');
45 // `child_exe_path_orig` might be relative; make it relative to our new cwd.
46 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });
47 defer gpa.free(child_exe_path);
48
49 var buf: std.ArrayList(u8) = .empty;
50 defer buf.deinit(gpa);
51 try buf.print(gpa,
52 \\@echo off
53 \\"{s}"
54 , .{child_exe_path});
55 // Trailing newline intentionally omitted above so we can add args.
5156 const preamble_len = buf.items.len;
5257
53 try buf.appendSlice(" %*");
58 try buf.appendSlice(gpa, " %*");
5459 try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items });
5560 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");
5863 try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items });
5964 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\"");
6267 try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items });
6368 buf.shrinkRetainingCapacity(preamble_len);
6469
6570 var i: u64 = 0;
6671 while (iterations == 0 or i < iterations) {
67 const rand_arg = try randomArg(allocator, rand);
68 defer allocator.free(rand_arg);
72 const rand_arg = try randomArg(gpa, rand);
73 defer gpa.free(rand_arg);
6974
70 try testExec(allocator, &.{rand_arg}, null);
75 try testExec(gpa, &.{rand_arg}, null);
7176
7277 i += 1;
7378 }
7479}
7580
76fn testExec(allocator: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {
77 try testExecBat(allocator, "args1.bat", args, env);
78 try testExecBat(allocator, "args2.bat", args, env);
79 try testExecBat(allocator, "args3.bat", args, env);
81fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {
82 try testExecBat(gpa, "args1.bat", args, env);
83 try testExecBat(gpa, "args2.bat", args, env);
84 try testExecBat(gpa, "args3.bat", args, env);
8085}
8186
82fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {
83 var argv = try std.array_list.Managed([]const u8).initCapacity(allocator, 1 + args.len);
84 defer argv.deinit();
85 argv.appendAssumeCapacity(bat);
86 argv.appendSliceAssumeCapacity(args);
87fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {
88 const argv = try gpa.alloc([]const u8, 1 + args.len);
89 defer gpa.free(argv);
90 argv[0] = bat;
91 @memcpy(argv[1..], args);
8792
8893 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
8994
9095 const result = try std.process.Child.run(.{
91 .allocator = allocator,
96 .allocator = gpa,
9297 .env_map = env,
93 .argv = argv.items,
98 .argv = argv,
9499 });
95 defer allocator.free(result.stdout);
96 defer allocator.free(result.stderr);
100 defer gpa.free(result.stdout);
101 defer gpa.free(result.stderr);
97102
98103 try std.testing.expectEqualStrings("", result.stderr);
99104 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
109114 }
110115}
111116
112fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {
117fn randomArg(gpa: Allocator, rand: std.Random) ![]const u8 {
113118 const Choice = enum {
114119 backslash,
115120 quote,
......@@ -121,8 +126,8 @@ fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {
121126 };
122127
123128 const choices = rand.uintAtMostBiased(u16, 256);
124 var buf = try std.array_list.Managed(u8).initCapacity(allocator, choices);
125 errdefer buf.deinit();
129 var buf: std.ArrayList(u8) = try .initCapacity(gpa, choices);
130 errdefer buf.deinit(gpa);
126131
127132 var last_codepoint: u21 = 0;
128133 for (0..choices) |_| {
......@@ -149,12 +154,12 @@ fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {
149154 continue;
150155 }
151156 }
152 try buf.ensureUnusedCapacity(4);
157 try buf.ensureUnusedCapacity(gpa, 4);
153158 const unused_slice = buf.unusedCapacitySlice();
154159 const len = std.unicode.wtf8Encode(codepoint, unused_slice) catch unreachable;
155160 buf.items.len += len;
156161 last_codepoint = codepoint;
157162 }
158163
159 return buf.toOwnedSlice();
164 return buf.toOwnedSlice(gpa);
160165}
test/standalone/windows_bat_args/test.zig+77-72
......@@ -1,14 +1,14 @@
11const std = @import("std");
22
33pub fn main() anyerror!void {
4 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
5 defer if (gpa.deinit() == .leak) @panic("found memory leaks");
6 const allocator = gpa.allocator();
4 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;
5 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);
6 const gpa = debug_alloc_inst.allocator();
77
8 var it = try std.process.argsWithAllocator(allocator);
8 var it = try std.process.argsWithAllocator(gpa);
99 defer it.deinit();
1010 _ = 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
1313 var tmp = std.testing.tmpDir(.{});
1414 defer tmp.cleanup();
......@@ -16,62 +16,67 @@ pub fn main() anyerror!void {
1616 try tmp.dir.setAsCwd();
1717 defer tmp.parent_dir.setAsCwd() catch {};
1818
19 var buf = try std.array_list.Managed(u8).initCapacity(allocator, 128);
20 defer buf.deinit();
21 try buf.appendSlice("@echo off\n");
22 try buf.append('"');
23 try buf.appendSlice(child_exe_path);
24 try buf.append('"');
19 // `child_exe_path_orig` might be relative; make it relative to our new cwd.
20 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });
21 defer gpa.free(child_exe_path);
22
23 var buf: std.ArrayList(u8) = .empty;
24 defer buf.deinit(gpa);
25 try buf.print(gpa,
26 \\@echo off
27 \\"{s}"
28 , .{child_exe_path});
29 // Trailing newline intentionally omitted above so we can add args.
2530 const preamble_len = buf.items.len;
2631
27 try buf.appendSlice(" %*");
32 try buf.appendSlice(gpa, " %*");
2833 try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items });
2934 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");
3237 try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items });
3338 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\"");
3641 try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items });
3742 buf.shrinkRetainingCapacity(preamble_len);
3843
3944 // Test cases are from https://github.com/rust-lang/rust/blob/master/tests/ui/std/windows-bat-args.rs
40 try testExecError(error.InvalidBatchScriptArg, allocator, &.{"\x00"});
41 try testExecError(error.InvalidBatchScriptArg, allocator, &.{"\n"});
42 try testExecError(error.InvalidBatchScriptArg, allocator, &.{"\r"});
43 try testExec(allocator, &.{ "a", "b" }, null);
44 try testExec(allocator, &.{ "c is for cat", "d is for dog" }, null);
45 try testExec(allocator, &.{ "\"", " \"" }, null);
46 try testExec(allocator, &.{ "\\", "\\" }, null);
47 try testExec(allocator, &.{">file.txt"}, null);
48 try testExec(allocator, &.{"whoami.exe"}, null);
49 try testExec(allocator, &.{"&a.exe"}, null);
50 try testExec(allocator, &.{"&echo hello "}, null);
51 try testExec(allocator, &.{ "&echo hello", "&whoami", ">file.txt" }, null);
52 try testExec(allocator, &.{"!TMP!"}, null);
53 try testExec(allocator, &.{"key=value"}, null);
54 try testExec(allocator, &.{"\"key=value\""}, null);
55 try testExec(allocator, &.{"key = value"}, null);
56 try testExec(allocator, &.{"key=[\"value\"]"}, null);
57 try testExec(allocator, &.{ "", "a=b" }, null);
58 try testExec(allocator, &.{"key=\"foo bar\""}, null);
59 try testExec(allocator, &.{"key=[\"my_value]"}, null);
60 try testExec(allocator, &.{"key=[\"my_value\",\"other-value\"]"}, null);
61 try testExec(allocator, &.{"key\\=value"}, null);
62 try testExec(allocator, &.{"key=\"&whoami\""}, null);
63 try testExec(allocator, &.{"key=\"value\"=5"}, null);
64 try testExec(allocator, &.{"key=[\">file.txt\"]"}, null);
65 try testExec(allocator, &.{"%hello"}, null);
66 try testExec(allocator, &.{"%PATH%"}, null);
67 try testExec(allocator, &.{"%%cd:~,%"}, null);
68 try testExec(allocator, &.{"%PATH%PATH%"}, null);
69 try testExec(allocator, &.{"\">file.txt"}, null);
70 try testExec(allocator, &.{"abc\"&echo hello"}, null);
71 try testExec(allocator, &.{"123\">file.txt"}, null);
72 try testExec(allocator, &.{"\"&echo hello&whoami.exe"}, null);
73 try testExec(allocator, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null);
74 try testExec(allocator, &.{"&whoami.exe"}, null);
45 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\x00"});
46 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\n"});
47 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\r"});
48 try testExec(gpa, &.{ "a", "b" }, null);
49 try testExec(gpa, &.{ "c is for cat", "d is for dog" }, null);
50 try testExec(gpa, &.{ "\"", " \"" }, null);
51 try testExec(gpa, &.{ "\\", "\\" }, null);
52 try testExec(gpa, &.{">file.txt"}, null);
53 try testExec(gpa, &.{"whoami.exe"}, null);
54 try testExec(gpa, &.{"&a.exe"}, null);
55 try testExec(gpa, &.{"&echo hello "}, null);
56 try testExec(gpa, &.{ "&echo hello", "&whoami", ">file.txt" }, null);
57 try testExec(gpa, &.{"!TMP!"}, null);
58 try testExec(gpa, &.{"key=value"}, null);
59 try testExec(gpa, &.{"\"key=value\""}, null);
60 try testExec(gpa, &.{"key = value"}, null);
61 try testExec(gpa, &.{"key=[\"value\"]"}, null);
62 try testExec(gpa, &.{ "", "a=b" }, null);
63 try testExec(gpa, &.{"key=\"foo bar\""}, null);
64 try testExec(gpa, &.{"key=[\"my_value]"}, null);
65 try testExec(gpa, &.{"key=[\"my_value\",\"other-value\"]"}, null);
66 try testExec(gpa, &.{"key\\=value"}, null);
67 try testExec(gpa, &.{"key=\"&whoami\""}, null);
68 try testExec(gpa, &.{"key=\"value\"=5"}, null);
69 try testExec(gpa, &.{"key=[\">file.txt\"]"}, null);
70 try testExec(gpa, &.{"%hello"}, null);
71 try testExec(gpa, &.{"%PATH%"}, null);
72 try testExec(gpa, &.{"%%cd:~,%"}, null);
73 try testExec(gpa, &.{"%PATH%PATH%"}, null);
74 try testExec(gpa, &.{"\">file.txt"}, null);
75 try testExec(gpa, &.{"abc\"&echo hello"}, null);
76 try testExec(gpa, &.{"123\">file.txt"}, null);
77 try testExec(gpa, &.{"\"&echo hello&whoami.exe"}, null);
78 try testExec(gpa, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null);
79 try testExec(gpa, &.{"&whoami.exe"}, null);
7580
7681 // Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution.
7782 // In many Windows APIs (including CreateProcess), trailing space and . characters are stripped
......@@ -89,17 +94,17 @@ pub fn main() anyerror!void {
8994 // > "args1.bat .. "
9095 // '"args1.bat .. "' is not recognized as an internal or external command,
9196 // 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));
9398 const absolute_with_trailing = blk: {
94 const absolute_path = try std.fs.realpathAlloc(allocator, "args1.bat");
95 defer allocator.free(absolute_path);
96 break :blk try std.mem.concat(allocator, u8, &.{ absolute_path, " .. " });
99 const absolute_path = try std.fs.realpathAlloc(gpa, "args1.bat");
100 defer gpa.free(absolute_path);
101 break :blk try std.mem.concat(gpa, u8, &.{ absolute_path, " .. " });
97102 };
98 defer allocator.free(absolute_with_trailing);
99 try std.testing.expectError(error.FileNotFound, testExecBat(allocator, absolute_with_trailing, &.{"abc"}, null));
103 defer gpa.free(absolute_with_trailing);
104 try std.testing.expectError(error.FileNotFound, testExecBat(gpa, absolute_with_trailing, &.{"abc"}, null));
100105
101106 var env = env: {
102 var env = try std.process.getEnvMap(allocator);
107 var env = try std.process.getEnvMap(gpa);
103108 errdefer env.deinit();
104109 // No escaping
105110 try env.put("FOO", "123");
......@@ -110,37 +115,37 @@ pub fn main() anyerror!void {
110115 break :env env;
111116 };
112117 defer env.deinit();
113 try testExec(allocator, &.{"%FOO%"}, &env);
118 try testExec(gpa, &.{"%FOO%"}, &env);
114119
115120 // Ensure that none of the `>file.txt`s have caused file.txt to be created
116121 try std.testing.expectError(error.FileNotFound, tmp.dir.access("file.txt", .{}));
117122}
118123
119fn testExecError(err: anyerror, allocator: std.mem.Allocator, args: []const []const u8) !void {
120 return std.testing.expectError(err, testExec(allocator, args, null));
124fn testExecError(err: anyerror, gpa: std.mem.Allocator, args: []const []const u8) !void {
125 return std.testing.expectError(err, testExec(gpa, args, null));
121126}
122127
123fn testExec(allocator: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {
124 try testExecBat(allocator, "args1.bat", args, env);
125 try testExecBat(allocator, "args2.bat", args, env);
126 try testExecBat(allocator, "args3.bat", args, env);
128fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {
129 try testExecBat(gpa, "args1.bat", args, env);
130 try testExecBat(gpa, "args2.bat", args, env);
131 try testExecBat(gpa, "args3.bat", args, env);
127132}
128133
129fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {
130 var argv = try std.array_list.Managed([]const u8).initCapacity(allocator, 1 + args.len);
131 defer argv.deinit();
132 argv.appendAssumeCapacity(bat);
133 argv.appendSliceAssumeCapacity(args);
134fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {
135 const argv = try gpa.alloc([]const u8, 1 + args.len);
136 defer gpa.free(argv);
137 argv[0] = bat;
138 @memcpy(argv[1..], args);
134139
135140 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
136141
137142 const result = try std.process.Child.run(.{
138 .allocator = allocator,
143 .allocator = gpa,
139144 .env_map = env,
140 .argv = argv.items,
145 .argv = argv,
141146 });
142 defer allocator.free(result.stdout);
143 defer allocator.free(result.stderr);
147 defer gpa.free(result.stdout);
148 defer gpa.free(result.stderr);
144149
145150 try std.testing.expectEqualStrings("", result.stderr);
146151 var it = std.mem.splitScalar(u8, result.stdout, '\x00');
test/tests.zig+72-7
......@@ -6,11 +6,13 @@ const OptimizeMode = std.builtin.OptimizeMode;
66const Step = std.Build.Step;
77
88// Cases
9const error_traces = @import("error_traces.zig");
910const stack_traces = @import("stack_traces.zig");
1011const llvm_ir = @import("llvm_ir.zig");
1112const libc = @import("libc.zig");
1213
1314// Implementations
15pub const ErrorTracesContext = @import("src/ErrorTrace.zig");
1416pub const StackTracesContext = @import("src/StackTrace.zig");
1517pub const DebuggerContext = @import("src/Debugger.zig");
1618pub const LlvmIrContext = @import("src/LlvmIr.zig");
......@@ -1857,28 +1859,61 @@ const c_abi_targets = blk: {
18571859 };
18581860};
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
18601895pub fn addStackTraceTests(
18611896 b: *std.Build,
18621897 test_filters: []const []const u8,
1863 optimize_modes: []const OptimizeMode,
1898 skip_non_native: bool,
18641899) *Step {
1865 const check_exe = b.addExecutable(.{
1866 .name = "check-stack-trace",
1900 const convert_exe = b.addExecutable(.{
1901 .name = "convert-stack-trace",
18671902 .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"),
18691904 .target = b.graph.host,
18701905 .optimize = .Debug,
18711906 }),
18721907 });
18731908
18741909 const cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
1910
18751911 cases.* = .{
18761912 .b = b,
18771913 .step = b.step("test-stack-traces", "Run the stack trace tests"),
1878 .test_index = 0,
18791914 .test_filters = test_filters,
1880 .optimize_modes = optimize_modes,
1881 .check_exe = check_exe,
1915 .targets = nativeAndCompatible32bit(b, skip_non_native),
1916 .convert_exe = convert_exe,
18821917 };
18831918
18841919 stack_traces.addCases(cases);
......@@ -1886,6 +1921,36 @@ pub fn addStackTraceTests(
18861921 return cases.step;
18871922}
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
18891954fn compilerHasPackageManager(b: *std.Build) bool {
18901955 // We can only use dependencies if the compiler was built with support for package management.
18911956 // (zig2 doesn't support it, but we still need to construct a build graph to build stage3.)