authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-07-21 17:37:22+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-07-21 17:37:22+02:00
log61d5b7c957e63239f1ebbdbfb94105d53f2dbaa4
treea49da9a773faff32301383f9bffaed99852c26f6
parentc43ee5bb22298eefc3fae919807f5da8f7be70f1
parentb1d86db7b45c57b2a9d48655738bce8d77327438
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #15823 from kcbanner/dwarf_unwind

Add DWARF unwinding, and an external debug info loader for ELF

29 files changed, 5603 insertions(+), 515 deletions(-)

lib/std/c.zig+7
......@@ -413,6 +413,13 @@ pub extern "c" fn timer_delete(timerid: c.timer_t) c_int;
413413pub extern "c" fn timer_settime(timerid: c.timer_t, flags: c_int, new_value: *const c.itimerspec, old_value: *c.itimerspec) c_int;
414414pub extern "c" fn timer_gettime(timerid: c.timer_t, flags: c_int, curr_value: *c.itimerspec) c_int;
415415
416pub usingnamespace if (builtin.os.tag == .linux and builtin.target.isMusl()) struct {
417 // musl does not implement getcontext
418 pub const getcontext = std.os.linux.getcontext;
419} else struct {
420 pub extern "c" fn getcontext(ucp: *std.os.ucontext_t) c_int;
421};
422
416423pub const max_align_t = if (builtin.abi == .msvc)
417424 f64
418425else if (builtin.target.isDarwin())
lib/std/c/darwin.zig+2-4
......@@ -148,12 +148,10 @@ pub const ucontext_t = extern struct {
148148 link: ?*ucontext_t,
149149 mcsize: u64,
150150 mcontext: *mcontext_t,
151 __mcontext_data: mcontext_t,
151152};
152153
153pub const mcontext_t = extern struct {
154 es: arch_bits.exception_state,
155 ss: arch_bits.thread_state,
156};
154pub const mcontext_t = arch_bits.mcontext_t;
157155
158156extern "c" fn __error() *c_int;
159157pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32;
lib/std/c/darwin/aarch64.zig+13
......@@ -1,5 +1,12 @@
11// See C headers in
22// lib/libc/include/aarch64-macos.12-gnu/mach/arm/_structs.h
3// lib/libc/include/aarch64-macos.13-none/arm/_mcontext.h
4
5pub const mcontext_t = extern struct {
6 es: exception_state,
7 ss: thread_state,
8 ns: neon_state,
9};
310
411pub const exception_state = extern struct {
512 far: u64, // Virtual Fault Address
......@@ -17,6 +24,12 @@ pub const thread_state = extern struct {
1724 __pad: u32,
1825};
1926
27pub const neon_state = extern struct {
28 q: [32]u128,
29 fpsr: u32,
30 fpcr: u32,
31};
32
2033pub const EXC_TYPES_COUNT = 14;
2134pub const EXC_MASK_MACHINE = 0;
2235
lib/std/c/darwin/x86_64.zig+29
......@@ -1,5 +1,11 @@
11const c = @import("../darwin.zig");
22
3pub const mcontext_t = extern struct {
4 es: exception_state,
5 ss: thread_state,
6 fs: float_state,
7};
8
39pub const exception_state = extern struct {
410 trapno: u16,
511 cpu: u16,
......@@ -31,6 +37,29 @@ pub const thread_state = extern struct {
3137 gs: u64,
3238};
3339
40const stmm_reg = [16]u8;
41const xmm_reg = [16]u8;
42pub const float_state = extern struct {
43 reserved: [2]c_int,
44 fcw: u16,
45 fsw: u16,
46 ftw: u8,
47 rsrv1: u8,
48 fop: u16,
49 ip: u32,
50 cs: u16,
51 rsrv2: u16,
52 dp: u32,
53 ds: u16,
54 rsrv3: u16,
55 mxcsr: u32,
56 mxcsrmask: u32,
57 stmm: [8]stmm_reg,
58 xmm: [16]xmm_reg,
59 rsrv4: [96]u8,
60 reserved1: c_int,
61};
62
3463pub const THREAD_STATE = 4;
3564pub const THREAD_STATE_COUNT: c.mach_msg_type_number_t = @sizeOf(thread_state) / @sizeOf(c_int);
3665
lib/std/coff.zig+8-5
......@@ -1214,6 +1214,11 @@ pub const Coff = struct {
12141214 return Strtab{ .buffer = self.data[offset..][0..size] };
12151215 }
12161216
1217 pub fn strtabRequired(self: *const Coff) bool {
1218 for (self.getSectionHeaders()) |*sect_hdr| if (sect_hdr.getName() == null) return true;
1219 return false;
1220 }
1221
12171222 pub fn getSectionHeaders(self: *const Coff) []align(1) const SectionHeader {
12181223 const coff_header = self.getCoffHeader();
12191224 const offset = self.coff_header_offset + @sizeOf(CoffHeader) + coff_header.size_of_optional_header;
......@@ -1248,14 +1253,12 @@ pub const Coff = struct {
12481253 return null;
12491254 }
12501255
1251 pub fn getSectionData(self: *const Coff, comptime name: []const u8) ![]const u8 {
1252 const sec = self.getSectionByName(name) orelse return error.MissingCoffSection;
1256 pub fn getSectionData(self: *const Coff, sec: *align(1) const SectionHeader) []const u8 {
12531257 return self.data[sec.pointer_to_raw_data..][0..sec.virtual_size];
12541258 }
12551259
1256 // Return an owned slice full of the section data
1257 pub fn getSectionDataAlloc(self: *const Coff, comptime name: []const u8, allocator: mem.Allocator) ![]u8 {
1258 const section_data = try self.getSectionData(name);
1260 pub fn getSectionDataAlloc(self: *const Coff, sec: *align(1) const SectionHeader, allocator: mem.Allocator) ![]u8 {
1261 const section_data = self.getSectionData(sec);
12591262 return allocator.dupe(u8, section_data);
12601263 }
12611264};
lib/std/debug.zig+764-331
......@@ -133,10 +133,80 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
133133 }
134134}
135135
136pub const have_ucontext = @hasDecl(os.system, "ucontext_t") and
137 (builtin.os.tag != .linux or switch (builtin.cpu.arch) {
138 .mips, .mipsel, .mips64, .mips64el, .riscv64 => false,
139 else => true,
140});
141
142/// Platform-specific thread state. This contains register state, and on some platforms
143/// information about the stack. This is not safe to trivially copy, because some platforms
144/// use internal pointers within this structure. To make a copy, use `copyContext`.
145pub const ThreadContext = blk: {
146 if (native_os == .windows) {
147 break :blk std.os.windows.CONTEXT;
148 } else if (have_ucontext) {
149 break :blk os.ucontext_t;
150 } else {
151 break :blk void;
152 }
153};
154
155/// Copies one context to another, updating any internal pointers
156pub fn copyContext(source: *const ThreadContext, dest: *ThreadContext) void {
157 if (!have_ucontext) return {};
158 dest.* = source.*;
159 relocateContext(dest);
160}
161
162/// Updates any internal pointers in the context to reflect its current location
163pub fn relocateContext(context: *ThreadContext) void {
164 return switch (native_os) {
165 .macos => {
166 context.mcontext = &context.__mcontext_data;
167 },
168 else => {},
169 };
170}
171
172pub const have_getcontext = @hasDecl(os.system, "getcontext") and
173 (builtin.os.tag != .linux or switch (builtin.cpu.arch) {
174 .x86,
175 .x86_64,
176 => true,
177 else => builtin.link_libc and !builtin.target.isMusl(),
178});
179
180/// Capture the current context. The register values in the context will reflect the
181/// state after the platform `getcontext` function returns.
182///
183/// It is valid to call this if the platform doesn't have context capturing support,
184/// in that case false will be returned.
185pub inline fn getContext(context: *ThreadContext) bool {
186 if (native_os == .windows) {
187 context.* = std.mem.zeroes(windows.CONTEXT);
188 windows.ntdll.RtlCaptureContext(context);
189 return true;
190 }
191
192 const result = have_getcontext and os.system.getcontext(context) == 0;
193 if (native_os == .macos) {
194 assert(context.mcsize == @sizeOf(std.c.mcontext_t));
195
196 // On aarch64-macos, the system getcontext doesn't write anything into the pc
197 // register slot, it only writes lr. This makes the context consistent with
198 // other aarch64 getcontext implementations which write the current lr
199 // (where getcontext will return to) into both the lr and pc slot of the context.
200 if (native_arch == .aarch64) context.mcontext.ss.pc = context.mcontext.ss.lr;
201 }
202
203 return result;
204}
205
136206/// Tries to print the stack trace starting from the supplied base pointer to stderr,
137207/// unbuffered, and ignores any error returned.
138208/// TODO multithreaded awareness
139pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
209pub fn dumpStackTraceFromBase(context: *const ThreadContext) void {
140210 nosuspend {
141211 if (comptime builtin.target.isWasm()) {
142212 if (native_os == .wasi) {
......@@ -156,13 +226,25 @@ pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
156226 };
157227 const tty_config = io.tty.detectConfig(io.getStdErr());
158228 if (native_os == .windows) {
159 writeCurrentStackTraceWindows(stderr, debug_info, tty_config, ip) catch return;
229 // On x86_64 and aarch64, the stack will be unwound using RtlVirtualUnwind using the context
230 // provided by the exception handler. On x86, RtlVirtualUnwind doesn't exist. Instead, a new backtrace
231 // will be captured and frames prior to the exception will be filtered.
232 // The caveat is that RtlCaptureStackBackTrace does not include the KiUserExceptionDispatcher frame,
233 // which is where the IP in `context` points to, so it can't be used as start_addr.
234 // Instead, start_addr is recovered from the stack.
235 const start_addr = if (builtin.cpu.arch == .x86) @as(*const usize, @ptrFromInt(context.getRegs().bp + 4)).* else null;
236 writeStackTraceWindows(stderr, debug_info, tty_config, context, start_addr) catch return;
160237 return;
161238 }
162239
163 printSourceAtAddress(debug_info, stderr, ip, tty_config) catch return;
164 var it = StackIterator.init(null, bp);
240 var it = StackIterator.initWithContext(null, debug_info, context) catch return;
241 defer it.deinit();
242 printSourceAtAddress(debug_info, stderr, it.unwind_state.?.dwarf_context.pc, tty_config) catch return;
243
165244 while (it.next()) |return_address| {
245 if (it.getLastError()) |unwind_error|
246 printUnwindError(debug_info, stderr, unwind_error.address, unwind_error.err, tty_config) catch {};
247
166248 // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,
167249 // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid
168250 // an overflow. We do not need to signal `StackIterator` as it will correctly detect this
......@@ -184,12 +266,12 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT
184266 if (native_os == .windows) {
185267 const addrs = stack_trace.instruction_addresses;
186268 const first_addr = first_address orelse {
187 stack_trace.index = walkStackWindows(addrs[0..]);
269 stack_trace.index = walkStackWindows(addrs[0..], null);
188270 return;
189271 };
190272 var addr_buf_stack: [32]usize = undefined;
191273 const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs;
192 const n = walkStackWindows(addr_buf[0..]);
274 const n = walkStackWindows(addr_buf[0..], null);
193275 const first_index = for (addr_buf[0..n], 0..) |addr, i| {
194276 if (addr == first_addr) {
195277 break i;
......@@ -206,7 +288,11 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT
206288 }
207289 stack_trace.index = slice.len;
208290 } else {
291 // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required).
292 // A new path for loading DebugInfo needs to be created which will only attempt to parse in-memory sections, because
293 // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding.
209294 var it = StackIterator.init(first_address, null);
295 defer it.deinit();
210296 for (stack_trace.instruction_addresses, 0..) |*addr, i| {
211297 addr.* = it.next() orelse {
212298 stack_trace.index = i;
......@@ -399,12 +485,27 @@ pub fn writeStackTrace(
399485 }
400486}
401487
488pub const UnwindError = if (have_ucontext)
489 @typeInfo(@typeInfo(@TypeOf(StackIterator.next_unwind)).Fn.return_type.?).ErrorUnion.error_set
490else
491 void;
492
402493pub const StackIterator = struct {
403494 // Skip every frame before this address is found.
404495 first_address: ?usize,
405496 // Last known value of the frame pointer register.
406497 fp: usize,
407498
499 // When DebugInfo and a register context is available, this iterator can unwind
500 // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer),
501 // using DWARF and MachO unwind info.
502 unwind_state: if (have_ucontext) ?struct {
503 debug_info: *DebugInfo,
504 dwarf_context: DW.UnwindContext,
505 last_error: ?UnwindError = null,
506 failed: bool = false,
507 } else void = if (have_ucontext) null else {},
508
408509 pub fn init(first_address: ?usize, fp: ?usize) StackIterator {
409510 if (native_arch == .sparc64) {
410511 // Flush all the register windows on stack.
......@@ -419,6 +520,44 @@ pub const StackIterator = struct {
419520 };
420521 }
421522
523 pub fn initWithContext(first_address: ?usize, debug_info: *DebugInfo, context: *const os.ucontext_t) !StackIterator {
524 // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that
525 // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder.
526 if (comptime builtin.target.isDarwin() and native_arch == .aarch64) {
527 return init(first_address, context.mcontext.ss.fp);
528 } else {
529 var iterator = init(first_address, null);
530 iterator.unwind_state = .{
531 .debug_info = debug_info,
532 .dwarf_context = try DW.UnwindContext.init(debug_info.allocator, context, &isValidMemory),
533 };
534
535 return iterator;
536 }
537 }
538
539 pub fn deinit(self: *StackIterator) void {
540 if (have_ucontext and self.unwind_state != null) self.unwind_state.?.dwarf_context.deinit();
541 }
542
543 pub fn getLastError(self: *StackIterator) ?struct {
544 err: UnwindError,
545 address: usize,
546 } {
547 if (!have_ucontext) return null;
548 if (self.unwind_state) |*unwind_state| {
549 if (unwind_state.last_error) |err| {
550 unwind_state.last_error = null;
551 return .{
552 .err = err,
553 .address = unwind_state.dwarf_context.pc,
554 };
555 }
556 }
557
558 return null;
559 }
560
422561 // Offset of the saved BP wrt the frame pointer.
423562 const fp_offset = if (native_arch.isRISCV())
424563 // On RISC-V the frame pointer points to the top of the saved register
......@@ -461,6 +600,7 @@ pub const StackIterator = struct {
461600 if (native_os == .freestanding) return true;
462601
463602 const aligned_address = address & ~@as(usize, @intCast((mem.page_size - 1)));
603 if (aligned_address == 0) return false;
464604 const aligned_memory = @as([*]align(mem.page_size) u8, @ptrFromInt(aligned_address))[0..mem.page_size];
465605
466606 if (native_os != .windows) {
......@@ -500,7 +640,49 @@ pub const StackIterator = struct {
500640 }
501641 }
502642
643 fn next_unwind(self: *StackIterator) !usize {
644 const unwind_state = &self.unwind_state.?;
645 const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc);
646 switch (native_os) {
647 .macos, .ios, .watchos, .tvos => {
648 // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding
649 // via DWARF before attempting to use the compact unwind info will produce incorrect results.
650 if (module.unwind_info) |unwind_info| {
651 if (DW.unwindFrameMachO(&unwind_state.dwarf_context, unwind_info, module.eh_frame, module.base_address)) |return_address| {
652 return return_address;
653 } else |err| {
654 if (err != error.RequiresDWARFUnwind) return err;
655 }
656 } else return error.MissingUnwindInfo;
657 },
658 else => {},
659 }
660
661 if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| {
662 return di.unwindFrame(&unwind_state.dwarf_context, null);
663 } else return error.MissingDebugInfo;
664 }
665
503666 fn next_internal(self: *StackIterator) ?usize {
667 if (have_ucontext) {
668 if (self.unwind_state) |*unwind_state| {
669 if (!unwind_state.failed) {
670 if (unwind_state.dwarf_context.pc == 0) return null;
671 if (self.next_unwind()) |return_address| {
672 self.fp = unwind_state.dwarf_context.getFp() catch 0;
673 return return_address;
674 } else |err| {
675 unwind_state.last_error = err;
676 unwind_state.failed = true;
677
678 // Fall back to fp-based unwinding on the first failure.
679 // We can't attempt it again for other modules higher in the
680 // stack because the full register state won't have been unwound.
681 }
682 }
683 }
684 }
685
504686 const fp = if (comptime native_arch.isSPARC())
505687 // On SPARC the offset is positive. (!)
506688 math.add(usize, self.fp, fp_offset) catch return null
......@@ -537,11 +719,21 @@ pub fn writeCurrentStackTrace(
537719 tty_config: io.tty.Config,
538720 start_addr: ?usize,
539721) !void {
722 var context: ThreadContext = undefined;
723 const has_context = getContext(&context);
540724 if (native_os == .windows) {
541 return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr);
725 return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr);
542726 }
543 var it = StackIterator.init(start_addr, null);
727
728 var it = (if (has_context) blk: {
729 break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null;
730 } else null) orelse StackIterator.init(start_addr, null);
731 defer it.deinit();
732
544733 while (it.next()) |return_address| {
734 if (it.getLastError()) |unwind_error|
735 try printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config);
736
545737 // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,
546738 // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid
547739 // an overflow. We do not need to signal `StackIterator` as it will correctly detect this
......@@ -552,7 +744,7 @@ pub fn writeCurrentStackTrace(
552744 }
553745}
554746
555pub noinline fn walkStackWindows(addresses: []usize) usize {
747pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize {
556748 if (builtin.cpu.arch == .x86) {
557749 // RtlVirtualUnwind doesn't exist on x86
558750 return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null);
......@@ -560,8 +752,13 @@ pub noinline fn walkStackWindows(addresses: []usize) usize {
560752
561753 const tib = @as(*const windows.NT_TIB, @ptrCast(&windows.teb().Reserved1));
562754
563 var context: windows.CONTEXT = std.mem.zeroes(windows.CONTEXT);
564 windows.ntdll.RtlCaptureContext(&context);
755 var context: windows.CONTEXT = undefined;
756 if (existing_context) |context_ptr| {
757 context = context_ptr.*;
758 } else {
759 context = std.mem.zeroes(windows.CONTEXT);
760 windows.ntdll.RtlCaptureContext(&context);
761 }
565762
566763 var i: usize = 0;
567764 var image_base: usize = undefined;
......@@ -603,14 +800,15 @@ pub noinline fn walkStackWindows(addresses: []usize) usize {
603800 return i;
604801}
605802
606pub fn writeCurrentStackTraceWindows(
803pub fn writeStackTraceWindows(
607804 out_stream: anytype,
608805 debug_info: *DebugInfo,
609806 tty_config: io.tty.Config,
807 context: *const windows.CONTEXT,
610808 start_addr: ?usize,
611809) !void {
612810 var addr_buf: [1024]usize = undefined;
613 const n = walkStackWindows(addr_buf[0..]);
811 const n = walkStackWindows(addr_buf[0..], context);
614812 const addrs = addr_buf[0..n];
615813 var start_i: usize = if (start_addr) |saddr| blk: {
616814 for (addrs, 0..) |addr, i| {
......@@ -681,6 +879,13 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz
681879 );
682880}
683881
882pub fn printUnwindError(debug_info: *DebugInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void {
883 const module_name = debug_info.getModuleNameForAddress(address) orelse "???";
884 try tty_config.setColor(out_stream, .dim);
885 try out_stream.print("Unwind information for `{s}:0x{x}` was not available ({}), trace may be incomplete\n\n", .{ module_name, address, err });
886 try tty_config.setColor(out_stream, .reset);
887}
888
684889pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
685890 const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {
686891 error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config),
......@@ -779,12 +984,8 @@ pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!DebugI
779984 }
780985}
781986
782fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDebugInfo {
987fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebugInfo {
783988 nosuspend {
784 const coff_obj = try allocator.create(coff.Coff);
785 defer allocator.destroy(coff_obj);
786 coff_obj.* = try coff.Coff.init(coff_bytes);
787
788989 var di = ModuleDebugInfo{
789990 .base_address = undefined,
790991 .coff_image_base = coff_obj.getImageBase(),
......@@ -792,62 +993,35 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDe
792993 .debug_data = undefined,
793994 };
794995
795 if (coff_obj.getSectionByName(".debug_info")) |sec| {
996 if (coff_obj.getSectionByName(".debug_info")) |_| {
796997 // This coff file has embedded DWARF debug info
797 _ = sec;
798
799 const debug_info = coff_obj.getSectionDataAlloc(".debug_info", allocator) catch return error.MissingDebugInfo;
800 errdefer allocator.free(debug_info);
801 const debug_abbrev = coff_obj.getSectionDataAlloc(".debug_abbrev", allocator) catch return error.MissingDebugInfo;
802 errdefer allocator.free(debug_abbrev);
803 const debug_str = coff_obj.getSectionDataAlloc(".debug_str", allocator) catch return error.MissingDebugInfo;
804 errdefer allocator.free(debug_str);
805 const debug_line = coff_obj.getSectionDataAlloc(".debug_line", allocator) catch return error.MissingDebugInfo;
806 errdefer allocator.free(debug_line);
807
808 const debug_str_offsets = coff_obj.getSectionDataAlloc(".debug_str_offsets", allocator) catch null;
809 const debug_line_str = coff_obj.getSectionDataAlloc(".debug_line_str", allocator) catch null;
810 const debug_ranges = coff_obj.getSectionDataAlloc(".debug_ranges", allocator) catch null;
811 const debug_loclists = coff_obj.getSectionDataAlloc(".debug_loclists", allocator) catch null;
812 const debug_rnglists = coff_obj.getSectionDataAlloc(".debug_rnglists", allocator) catch null;
813 const debug_addr = coff_obj.getSectionDataAlloc(".debug_addr", allocator) catch null;
814 const debug_names = coff_obj.getSectionDataAlloc(".debug_names", allocator) catch null;
815 const debug_frame = coff_obj.getSectionDataAlloc(".debug_frame", allocator) catch null;
998 var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
999 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1000
1001 inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| {
1002 sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
1003 break :blk .{
1004 .data = try coff_obj.getSectionDataAlloc(section_header, allocator),
1005 .virtual_address = section_header.virtual_address,
1006 .owned = true,
1007 };
1008 } else null;
1009 }
8161010
8171011 var dwarf = DW.DwarfInfo{
8181012 .endian = native_endian,
819 .debug_info = debug_info,
820 .debug_abbrev = debug_abbrev,
821 .debug_str = debug_str,
822 .debug_str_offsets = debug_str_offsets,
823 .debug_line = debug_line,
824 .debug_line_str = debug_line_str,
825 .debug_ranges = debug_ranges,
826 .debug_loclists = debug_loclists,
827 .debug_rnglists = debug_rnglists,
828 .debug_addr = debug_addr,
829 .debug_names = debug_names,
830 .debug_frame = debug_frame,
831 };
832
833 DW.openDwarfDebugInfo(&dwarf, allocator) catch |err| {
834 if (debug_str_offsets) |d| allocator.free(d);
835 if (debug_line_str) |d| allocator.free(d);
836 if (debug_ranges) |d| allocator.free(d);
837 if (debug_loclists) |d| allocator.free(d);
838 if (debug_rnglists) |d| allocator.free(d);
839 if (debug_addr) |d| allocator.free(d);
840 if (debug_names) |d| allocator.free(d);
841 if (debug_frame) |d| allocator.free(d);
842 return err;
1013 .sections = sections,
1014 .is_macho = false,
8431015 };
8441016
1017 try DW.openDwarfDebugInfo(&dwarf, allocator);
8451018 di.debug_data = PdbOrDwarf{ .dwarf = dwarf };
8461019 return di;
8471020 }
8481021
8491022 // Only used by pdb path
8501023 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
1024 errdefer allocator.free(di.coff_section_headers);
8511025
8521026 var path_buf: [windows.MAX_PATH]u8 = undefined;
8531027 const len = try coff_obj.getPdbPath(path_buf[0..]);
......@@ -877,13 +1051,35 @@ fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8
8771051 return ptr[start..end];
8781052}
8791053
880/// This takes ownership of elf_file: users of this function should not close
881/// it themselves, even on error.
882/// TODO it's weird to take ownership even on error, rework this code.
883pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugInfo {
1054/// Reads debug info from an ELF file, or the current binary if none in specified.
1055/// If the required sections aren't present but a reference to external debug info is,
1056/// then this this function will recurse to attempt to load the debug sections from
1057/// an external file.
1058pub fn readElfDebugInfo(
1059 allocator: mem.Allocator,
1060 elf_filename: ?[]const u8,
1061 build_id: ?[]const u8,
1062 expected_crc: ?u32,
1063 parent_sections: *DW.DwarfInfo.SectionArray,
1064 parent_mapped_mem: ?[]align(mem.page_size) const u8,
1065) !ModuleDebugInfo {
8841066 nosuspend {
1067
1068 // TODO https://github.com/ziglang/zig/issues/5525
1069 const elf_file = (if (elf_filename) |filename| blk: {
1070 break :blk if (fs.path.isAbsolute(filename))
1071 fs.openFileAbsolute(filename, .{ .intended_io_mode = .blocking })
1072 else
1073 fs.cwd().openFile(filename, .{ .intended_io_mode = .blocking });
1074 } else fs.openSelfExe(.{ .intended_io_mode = .blocking })) catch |err| switch (err) {
1075 error.FileNotFound => return error.MissingDebugInfo,
1076 else => return err,
1077 };
1078
8851079 const mapped_mem = try mapWholeFile(elf_file);
886 const hdr = @as(*const elf.Ehdr, @ptrCast(&mapped_mem[0]));
1080 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32SmallWithPoly(.IEEE).hash(mapped_mem)) return error.InvalidDebugInfo;
1081
1082 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
8871083 if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
8881084 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
8891085
......@@ -896,73 +1092,152 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn
8961092
8971093 const shoff = hdr.e_shoff;
8981094 const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
899 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(
900 &mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow],
901 ));
902 const header_strings = mapped_mem[str_shdr.sh_offset .. str_shdr.sh_offset + str_shdr.sh_size];
1095 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow]));
1096 const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
9031097 const shdrs = @as(
9041098 [*]const elf.Shdr,
9051099 @ptrCast(@alignCast(&mapped_mem[shoff])),
9061100 )[0..hdr.e_shnum];
9071101
908 var opt_debug_info: ?[]const u8 = null;
909 var opt_debug_abbrev: ?[]const u8 = null;
910 var opt_debug_str: ?[]const u8 = null;
911 var opt_debug_str_offsets: ?[]const u8 = null;
912 var opt_debug_line: ?[]const u8 = null;
913 var opt_debug_line_str: ?[]const u8 = null;
914 var opt_debug_ranges: ?[]const u8 = null;
915 var opt_debug_loclists: ?[]const u8 = null;
916 var opt_debug_rnglists: ?[]const u8 = null;
917 var opt_debug_addr: ?[]const u8 = null;
918 var opt_debug_names: ?[]const u8 = null;
919 var opt_debug_frame: ?[]const u8 = null;
1102 var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
9201103
921 for (shdrs) |*shdr| {
922 if (shdr.sh_type == elf.SHT_NULL) continue;
1104 // Combine section list. This takes ownership over any owned sections from the parent scope.
1105 for (parent_sections, &sections) |*parent, *section| {
1106 if (parent.*) |*p| {
1107 section.* = p.*;
1108 p.owned = false;
1109 }
1110 }
1111 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1112
1113 var separate_debug_filename: ?[]const u8 = null;
1114 var separate_debug_crc: ?u32 = null;
9231115
1116 for (shdrs) |*shdr| {
1117 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
9241118 const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
925 if (mem.eql(u8, name, ".debug_info")) {
926 opt_debug_info = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
927 } else if (mem.eql(u8, name, ".debug_abbrev")) {
928 opt_debug_abbrev = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
929 } else if (mem.eql(u8, name, ".debug_str")) {
930 opt_debug_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
931 } else if (mem.eql(u8, name, ".debug_str_offsets")) {
932 opt_debug_str_offsets = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
933 } else if (mem.eql(u8, name, ".debug_line")) {
934 opt_debug_line = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
935 } else if (mem.eql(u8, name, ".debug_line_str")) {
936 opt_debug_line_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
937 } else if (mem.eql(u8, name, ".debug_ranges")) {
938 opt_debug_ranges = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
939 } else if (mem.eql(u8, name, ".debug_loclists")) {
940 opt_debug_loclists = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
941 } else if (mem.eql(u8, name, ".debug_rnglists")) {
942 opt_debug_rnglists = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
943 } else if (mem.eql(u8, name, ".debug_addr")) {
944 opt_debug_addr = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
945 } else if (mem.eql(u8, name, ".debug_names")) {
946 opt_debug_names = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
947 } else if (mem.eql(u8, name, ".debug_frame")) {
948 opt_debug_frame = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1119
1120 if (mem.eql(u8, name, ".gnu_debuglink")) {
1121 const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1122 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
1123 const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr);
1124 const crc_bytes = gnu_debuglink[crc_offset .. crc_offset + 4];
1125 separate_debug_crc = mem.readIntSliceNative(u32, crc_bytes);
1126 separate_debug_filename = debug_filename;
1127 continue;
1128 }
1129
1130 var section_index: ?usize = null;
1131 inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| {
1132 if (mem.eql(u8, "." ++ section.name, name)) section_index = i;
1133 }
1134 if (section_index == null) continue;
1135 if (sections[section_index.?] != null) continue;
1136
1137 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1138 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
1139 var section_stream = io.fixedBufferStream(section_bytes);
1140 var section_reader = section_stream.reader();
1141 const chdr = section_reader.readStruct(elf.Chdr) catch continue;
1142 if (chdr.ch_type != .ZLIB) continue;
1143
1144 var zlib_stream = std.compress.zlib.decompressStream(allocator, section_stream.reader()) catch continue;
1145 defer zlib_stream.deinit();
1146
1147 var decompressed_section = try allocator.alloc(u8, chdr.ch_size);
1148 errdefer allocator.free(decompressed_section);
1149
1150 const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
1151 assert(read == decompressed_section.len);
1152
1153 break :blk .{
1154 .data = decompressed_section,
1155 .virtual_address = shdr.sh_addr,
1156 .owned = true,
1157 };
1158 } else .{
1159 .data = section_bytes,
1160 .virtual_address = shdr.sh_addr,
1161 .owned = false,
1162 };
1163 }
1164
1165 const missing_debug_info =
1166 sections[@intFromEnum(DW.DwarfSection.debug_info)] == null or
1167 sections[@intFromEnum(DW.DwarfSection.debug_abbrev)] == null or
1168 sections[@intFromEnum(DW.DwarfSection.debug_str)] == null or
1169 sections[@intFromEnum(DW.DwarfSection.debug_line)] == null;
1170
1171 // Attempt to load debug info from an external file
1172 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
1173 if (missing_debug_info) {
1174
1175 // Only allow one level of debug info nesting
1176 if (parent_mapped_mem) |_| {
1177 return error.MissingDebugInfo;
1178 }
1179
1180 const global_debug_directories = [_][]const u8{
1181 "/usr/lib/debug",
1182 };
1183
1184 // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
1185 if (build_id) |id| blk: {
1186 if (id.len < 3) break :blk;
1187
1188 // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
1189 const extension = ".debug";
1190 var id_prefix_buf: [2]u8 = undefined;
1191 var filename_buf: [38 + extension.len]u8 = undefined;
1192
1193 _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable;
1194 const filename = std.fmt.bufPrint(
1195 &filename_buf,
1196 "{s}" ++ extension,
1197 .{std.fmt.fmtSliceHexLower(id[1..])},
1198 ) catch break :blk;
1199
1200 for (global_debug_directories) |global_directory| {
1201 const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename });
1202 defer allocator.free(path);
1203
1204 return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
1205 }
1206 }
1207
1208 // use the path from .gnu_debuglink, in the same search order as gdb
1209 if (separate_debug_filename) |separate_filename| blk: {
1210 if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo;
1211
1212 // <cwd>/<gnu_debuglink>
1213 if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1214
1215 // <cwd>/.debug/<gnu_debuglink>
1216 {
1217 const path = try fs.path.join(allocator, &.{ ".debug", separate_filename });
1218 defer allocator.free(path);
1219
1220 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1221 }
1222
1223 var cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
1224 const cwd_path = fs.cwd().realpath("", &cwd_buf) catch break :blk;
1225
1226 // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
1227 for (global_debug_directories) |global_directory| {
1228 const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename });
1229 defer allocator.free(path);
1230 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1231 }
9491232 }
1233
1234 return error.MissingDebugInfo;
9501235 }
9511236
9521237 var di = DW.DwarfInfo{
9531238 .endian = endian,
954 .debug_info = opt_debug_info orelse return error.MissingDebugInfo,
955 .debug_abbrev = opt_debug_abbrev orelse return error.MissingDebugInfo,
956 .debug_str = opt_debug_str orelse return error.MissingDebugInfo,
957 .debug_str_offsets = opt_debug_str_offsets,
958 .debug_line = opt_debug_line orelse return error.MissingDebugInfo,
959 .debug_line_str = opt_debug_line_str,
960 .debug_ranges = opt_debug_ranges,
961 .debug_loclists = opt_debug_loclists,
962 .debug_rnglists = opt_debug_rnglists,
963 .debug_addr = opt_debug_addr,
964 .debug_names = opt_debug_names,
965 .debug_frame = opt_debug_frame,
1239 .sections = sections,
1240 .is_macho = false,
9661241 };
9671242
9681243 try DW.openDwarfDebugInfo(&di, allocator);
......@@ -970,7 +1245,8 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn
9701245 return ModuleDebugInfo{
9711246 .base_address = undefined,
9721247 .dwarf = di,
973 .mapped_memory = mapped_mem,
1248 .mapped_memory = parent_mapped_mem orelse mapped_mem,
1249 .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
9741250 };
9751251 }
9761252}
......@@ -1094,6 +1370,7 @@ fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugIn
10941370
10951371 return ModuleDebugInfo{
10961372 .base_address = undefined,
1373 .vmaddr_slide = undefined,
10971374 .mapped_memory = mapped_mem,
10981375 .ofiles = ModuleDebugInfo.OFileTable.init(allocator),
10991376 .symbols = symbols,
......@@ -1180,6 +1457,21 @@ pub const WindowsModuleInfo = struct {
11801457 base_address: usize,
11811458 size: u32,
11821459 name: []const u8,
1460 handle: windows.HMODULE,
1461
1462 // Set when the image file needed to be mapped from disk
1463 mapped_file: ?struct {
1464 file: File,
1465 section_handle: windows.HANDLE,
1466 section_view: []const u8,
1467
1468 pub fn deinit(self: @This()) void {
1469 const process_handle = windows.kernel32.GetCurrentProcess();
1470 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS);
1471 windows.CloseHandle(self.section_handle);
1472 self.file.close();
1473 }
1474 } = null,
11831475};
11841476
11851477pub const DebugInfo = struct {
......@@ -1195,6 +1487,8 @@ pub const DebugInfo = struct {
11951487 };
11961488
11971489 if (native_os == .windows) {
1490 errdefer debug_info.modules.deinit(allocator);
1491
11981492 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
11991493 if (handle == windows.INVALID_HANDLE_VALUE) {
12001494 switch (windows.kernel32.GetLastError()) {
......@@ -1212,9 +1506,16 @@ pub const DebugInfo = struct {
12121506 var module_valid = true;
12131507 while (module_valid) {
12141508 const module_info = try debug_info.modules.addOne(allocator);
1215 module_info.base_address = @intFromPtr(module_entry.modBaseAddr);
1216 module_info.size = module_entry.modBaseSize;
1217 module_info.name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{};
1509 const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{};
1510 errdefer allocator.free(name);
1511
1512 module_info.* = .{
1513 .base_address = @intFromPtr(module_entry.modBaseAddr),
1514 .size = module_entry.modBaseSize,
1515 .name = name,
1516 .handle = module_entry.hModule,
1517 };
1518
12181519 module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1;
12191520 }
12201521 }
......@@ -1233,6 +1534,7 @@ pub const DebugInfo = struct {
12331534 if (native_os == .windows) {
12341535 for (self.modules.items) |module| {
12351536 self.allocator.free(module.name);
1537 if (module.mapped_file) |mapped_file| mapped_file.deinit();
12361538 }
12371539 self.modules.deinit(self.allocator);
12381540 }
......@@ -1252,9 +1554,12 @@ pub const DebugInfo = struct {
12521554 }
12531555 }
12541556
1557 // Returns the module name for a given address.
1558 // This can be called when getModuleForAddress fails, so implementations should provide
1559 // a path that doesn't rely on any side-effects of a prior successful module lookup.
12551560 pub fn getModuleNameForAddress(self: *DebugInfo, address: usize) ?[]const u8 {
12561561 if (comptime builtin.target.isDarwin()) {
1257 return null;
1562 return self.lookupModuleNameDyld(address);
12581563 } else if (native_os == .windows) {
12591564 return self.lookupModuleNameWin32(address);
12601565 } else if (native_os == .haiku) {
......@@ -1262,7 +1567,7 @@ pub const DebugInfo = struct {
12621567 } else if (comptime builtin.target.isWasm()) {
12631568 return null;
12641569 } else {
1265 return null;
1570 return self.lookupModuleNameDl(address);
12661571 }
12671572 }
12681573
......@@ -1271,11 +1576,10 @@ pub const DebugInfo = struct {
12711576
12721577 var i: u32 = 0;
12731578 while (i < image_count) : (i += 1) {
1274 const base_address = std.c._dyld_get_image_vmaddr_slide(i);
1275
1276 if (address < base_address) continue;
1277
12781579 const header = std.c._dyld_get_image_header(i) orelse continue;
1580 const base_address = @intFromPtr(header);
1581 if (address < base_address) continue;
1582 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
12791583
12801584 var it = macho.LoadCommandIterator{
12811585 .ncmds = header.ncmds,
......@@ -1284,18 +1588,29 @@ pub const DebugInfo = struct {
12841588 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
12851589 )[0..header.sizeofcmds]),
12861590 };
1591
1592 var unwind_info: ?[]const u8 = null;
1593 var eh_frame: ?[]const u8 = null;
12871594 while (it.next()) |cmd| switch (cmd.cmd()) {
12881595 .SEGMENT_64 => {
12891596 const segment_cmd = cmd.cast(macho.segment_command_64).?;
1290 const rebased_address = address - base_address;
1291 const seg_start = segment_cmd.vmaddr;
1292 const seg_end = seg_start + segment_cmd.vmsize;
1597 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
12931598
1294 if (rebased_address >= seg_start and rebased_address < seg_end) {
1599 const seg_start = segment_cmd.vmaddr + vmaddr_slide;
1600 const seg_end = seg_start + segment_cmd.vmsize;
1601 if (address >= seg_start and address < seg_end) {
12951602 if (self.address_map.get(base_address)) |obj_di| {
12961603 return obj_di;
12971604 }
12981605
1606 for (cmd.getSections()) |sect| {
1607 if (mem.eql(u8, "__unwind_info", sect.sectName())) {
1608 unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
1609 } else if (mem.eql(u8, "__eh_frame", sect.sectName())) {
1610 eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
1611 }
1612 }
1613
12991614 const obj_di = try self.allocator.create(ModuleDebugInfo);
13001615 errdefer self.allocator.destroy(obj_di);
13011616
......@@ -1308,6 +1623,9 @@ pub const DebugInfo = struct {
13081623 };
13091624 obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
13101625 obj_di.base_address = base_address;
1626 obj_di.vmaddr_slide = vmaddr_slide;
1627 obj_di.unwind_info = unwind_info;
1628 obj_di.eh_frame = eh_frame;
13111629
13121630 try self.address_map.putNoClobber(base_address, obj_di);
13131631
......@@ -1321,18 +1639,124 @@ pub const DebugInfo = struct {
13211639 return error.MissingDebugInfo;
13221640 }
13231641
1642 fn lookupModuleNameDyld(self: *DebugInfo, address: usize) ?[]const u8 {
1643 _ = self;
1644 const image_count = std.c._dyld_image_count();
1645
1646 var i: u32 = 0;
1647 while (i < image_count) : (i += 1) {
1648 const header = std.c._dyld_get_image_header(i) orelse continue;
1649 const base_address = @intFromPtr(header);
1650 if (address < base_address) continue;
1651 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
1652
1653 var it = macho.LoadCommandIterator{
1654 .ncmds = header.ncmds,
1655 .buffer = @alignCast(@as(
1656 [*]u8,
1657 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
1658 )[0..header.sizeofcmds]),
1659 };
1660
1661 while (it.next()) |cmd| switch (cmd.cmd()) {
1662 .SEGMENT_64 => {
1663 const segment_cmd = cmd.cast(macho.segment_command_64).?;
1664 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
1665
1666 const original_address = address - vmaddr_slide;
1667 const seg_start = segment_cmd.vmaddr;
1668 const seg_end = seg_start + segment_cmd.vmsize;
1669 if (original_address >= seg_start and original_address < seg_end) {
1670 return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0));
1671 }
1672 },
1673 else => {},
1674 };
1675 }
1676
1677 return null;
1678 }
1679
13241680 fn lookupModuleWin32(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
1325 for (self.modules.items) |module| {
1681 for (self.modules.items) |*module| {
13261682 if (address >= module.base_address and address < module.base_address + module.size) {
13271683 if (self.address_map.get(module.base_address)) |obj_di| {
13281684 return obj_di;
13291685 }
13301686
1331 const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size];
13321687 const obj_di = try self.allocator.create(ModuleDebugInfo);
13331688 errdefer self.allocator.destroy(obj_di);
13341689
1335 obj_di.* = try readCoffDebugInfo(self.allocator, mapped_module);
1690 const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size];
1691 var coff_obj = try coff.Coff.init(mapped_module);
1692
1693 // The string table is not mapped into memory by the loader, so if a section name is in the
1694 // string table then we have to map the full image file from disk. This can happen when
1695 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
1696 if (coff_obj.strtabRequired()) {
1697 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
1698 // openFileAbsoluteW requires the prefix to be present
1699 mem.copy(u16, name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' });
1700
1701 const process_handle = windows.kernel32.GetCurrentProcess();
1702 const len = windows.kernel32.K32GetModuleFileNameExW(
1703 process_handle,
1704 module.handle,
1705 @ptrCast(&name_buffer[4]),
1706 windows.PATH_MAX_WIDE,
1707 );
1708
1709 if (len == 0) return error.MissingDebugInfo;
1710 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
1711 error.FileNotFound => return error.MissingDebugInfo,
1712 else => return err,
1713 };
1714 errdefer coff_file.close();
1715
1716 var section_handle: windows.HANDLE = undefined;
1717 const create_section_rc = windows.ntdll.NtCreateSection(
1718 &section_handle,
1719 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
1720 null,
1721 null,
1722 windows.PAGE_READONLY,
1723 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
1724 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
1725 windows.SEC_COMMIT,
1726 coff_file.handle,
1727 );
1728 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
1729 errdefer windows.CloseHandle(section_handle);
1730
1731 var coff_len: usize = 0;
1732 var base_ptr: usize = 0;
1733 const map_section_rc = windows.ntdll.NtMapViewOfSection(
1734 section_handle,
1735 process_handle,
1736 @ptrCast(&base_ptr),
1737 null,
1738 0,
1739 null,
1740 &coff_len,
1741 .ViewUnmap,
1742 0,
1743 windows.PAGE_READONLY,
1744 );
1745 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
1746 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS);
1747
1748 const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len];
1749 coff_obj = try coff.Coff.init(section_view);
1750
1751 module.mapped_file = .{
1752 .file = coff_file,
1753 .section_handle = section_handle,
1754 .section_view = section_view,
1755 };
1756 }
1757 errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit();
1758
1759 obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj);
13361760 obj_di.base_address = module.base_address;
13371761
13381762 try self.address_map.putNoClobber(module.base_address, obj_di);
......@@ -1352,6 +1776,44 @@ pub const DebugInfo = struct {
13521776 return null;
13531777 }
13541778
1779 fn lookupModuleNameDl(self: *DebugInfo, address: usize) ?[]const u8 {
1780 _ = self;
1781
1782 var ctx: struct {
1783 // Input
1784 address: usize,
1785 // Output
1786 name: []const u8 = "",
1787 } = .{ .address = address };
1788 const CtxTy = @TypeOf(ctx);
1789
1790 if (os.dl_iterate_phdr(&ctx, error{Found}, struct {
1791 fn callback(info: *os.dl_phdr_info, size: usize, context: *CtxTy) !void {
1792 _ = size;
1793 if (context.address < info.dlpi_addr) return;
1794 const phdrs = info.dlpi_phdr[0..info.dlpi_phnum];
1795 for (phdrs) |*phdr| {
1796 if (phdr.p_type != elf.PT_LOAD) continue;
1797
1798 const seg_start = info.dlpi_addr +% phdr.p_vaddr;
1799 const seg_end = seg_start + phdr.p_memsz;
1800 if (context.address >= seg_start and context.address < seg_end) {
1801 context.name = mem.sliceTo(info.dlpi_name, 0) orelse "";
1802 break;
1803 }
1804 } else return;
1805
1806 return error.Found;
1807 }
1808 }.callback)) {
1809 return null;
1810 } else |err| switch (err) {
1811 error.Found => return fs.path.basename(ctx.name),
1812 }
1813
1814 return null;
1815 }
1816
13551817 fn lookupModuleDl(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
13561818 var ctx: struct {
13571819 // Input
......@@ -1359,6 +1821,8 @@ pub const DebugInfo = struct {
13591821 // Output
13601822 base_address: usize = undefined,
13611823 name: []const u8 = undefined,
1824 build_id: ?[]const u8 = null,
1825 gnu_eh_frame: ?[]const u8 = null,
13621826 } = .{ .address = address };
13631827 const CtxTy = @TypeOf(ctx);
13641828
......@@ -1373,18 +1837,40 @@ pub const DebugInfo = struct {
13731837 for (phdrs) |*phdr| {
13741838 if (phdr.p_type != elf.PT_LOAD) continue;
13751839
1376 const seg_start = info.dlpi_addr + phdr.p_vaddr;
1840 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
1841 const seg_start = info.dlpi_addr +% phdr.p_vaddr;
13771842 const seg_end = seg_start + phdr.p_memsz;
1378
13791843 if (context.address >= seg_start and context.address < seg_end) {
13801844 // Android libc uses NULL instead of an empty string to mark the
13811845 // main program
13821846 context.name = mem.sliceTo(info.dlpi_name, 0) orelse "";
13831847 context.base_address = info.dlpi_addr;
1384 // Stop the iteration
1385 return error.Found;
1848 break;
1849 }
1850 } else return;
1851
1852 for (info.dlpi_phdr[0..info.dlpi_phnum]) |phdr| {
1853 switch (phdr.p_type) {
1854 elf.PT_NOTE => {
1855 // Look for .note.gnu.build-id
1856 const note_bytes = @as([*]const u8, @ptrFromInt(info.dlpi_addr + phdr.p_vaddr))[0..phdr.p_memsz];
1857 const name_size = mem.readIntSliceNative(u32, note_bytes[0..4]);
1858 if (name_size != 4) continue;
1859 const desc_size = mem.readIntSliceNative(u32, note_bytes[4..8]);
1860 const note_type = mem.readIntSliceNative(u32, note_bytes[8..12]);
1861 if (note_type != elf.NT_GNU_BUILD_ID) continue;
1862 if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
1863 context.build_id = note_bytes[16..][0..desc_size];
1864 },
1865 elf.PT_GNU_EH_FRAME => {
1866 context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.dlpi_addr + phdr.p_vaddr))[0..phdr.p_memsz];
1867 },
1868 else => {},
13861869 }
13871870 }
1871
1872 // Stop the iteration
1873 return error.Found;
13881874 }
13891875 }.callback)) {
13901876 return error.MissingDebugInfo;
......@@ -1399,20 +1885,24 @@ pub const DebugInfo = struct {
13991885 const obj_di = try self.allocator.create(ModuleDebugInfo);
14001886 errdefer self.allocator.destroy(obj_di);
14011887
1402 // TODO https://github.com/ziglang/zig/issues/5525
1403 const copy = if (ctx.name.len > 0)
1404 fs.cwd().openFile(ctx.name, .{ .intended_io_mode = .blocking })
1405 else
1406 fs.openSelfExe(.{ .intended_io_mode = .blocking });
1407
1408 const elf_file = copy catch |err| switch (err) {
1409 error.FileNotFound => return error.MissingDebugInfo,
1410 else => return err,
1411 };
1888 var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
1889 if (ctx.gnu_eh_frame) |eh_frame_hdr| {
1890 // This is a special case - pointer offsets inside .eh_frame_hdr
1891 // are encoded relative to its base address, so we must use the
1892 // version that is already memory mapped, and not the one that
1893 // will be mapped separately from the ELF file.
1894 sections[@intFromEnum(DW.DwarfSection.eh_frame_hdr)] = .{
1895 .data = eh_frame_hdr,
1896 .owned = false,
1897 };
1898 }
14121899
1413 obj_di.* = try readElfDebugInfo(self.allocator, elf_file);
1900 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
14141901 obj_di.base_address = ctx.base_address;
14151902
1903 // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding
1904 obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {};
1905
14161906 try self.address_map.putNoClobber(ctx.base_address, obj_di);
14171907
14181908 return obj_di;
......@@ -1434,11 +1924,16 @@ pub const DebugInfo = struct {
14341924pub const ModuleDebugInfo = switch (native_os) {
14351925 .macos, .ios, .watchos, .tvos => struct {
14361926 base_address: usize,
1927 vmaddr_slide: usize,
14371928 mapped_memory: []align(mem.page_size) const u8,
14381929 symbols: []const MachoSymbol,
14391930 strings: [:0]const u8,
14401931 ofiles: OFileTable,
14411932
1933 // Backed by the in-memory sections mapped by the loader
1934 unwind_info: ?[]const u8 = null,
1935 eh_frame: ?[]const u8 = null,
1936
14421937 const OFileTable = std.StringHashMap(OFileInfo);
14431938 const OFileInfo = struct {
14441939 di: DW.DwarfInfo,
......@@ -1457,7 +1952,7 @@ pub const ModuleDebugInfo = switch (native_os) {
14571952 os.munmap(self.mapped_memory);
14581953 }
14591954
1460 fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !OFileInfo {
1955 fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !*OFileInfo {
14611956 const o_file = try fs.cwd().openFile(o_file_path, .{ .intended_io_mode = .blocking });
14621957 const mapped_mem = try mapWholeFile(o_file);
14631958
......@@ -1500,95 +1995,40 @@ pub const ModuleDebugInfo = switch (native_os) {
15001995 addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);
15011996 }
15021997
1503 var opt_debug_line: ?macho.section_64 = null;
1504 var opt_debug_info: ?macho.section_64 = null;
1505 var opt_debug_abbrev: ?macho.section_64 = null;
1506 var opt_debug_str: ?macho.section_64 = null;
1507 var opt_debug_str_offsets: ?macho.section_64 = null;
1508 var opt_debug_line_str: ?macho.section_64 = null;
1509 var opt_debug_ranges: ?macho.section_64 = null;
1510 var opt_debug_loclists: ?macho.section_64 = null;
1511 var opt_debug_rnglists: ?macho.section_64 = null;
1512 var opt_debug_addr: ?macho.section_64 = null;
1513 var opt_debug_names: ?macho.section_64 = null;
1514 var opt_debug_frame: ?macho.section_64 = null;
1998 var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
1999 if (self.eh_frame) |eh_frame| sections[@intFromEnum(DW.DwarfSection.eh_frame)] = .{
2000 .data = eh_frame,
2001 .owned = false,
2002 };
15152003
15162004 for (segcmd.?.getSections()) |sect| {
1517 const name = sect.sectName();
1518 if (mem.eql(u8, name, "__debug_line")) {
1519 opt_debug_line = sect;
1520 } else if (mem.eql(u8, name, "__debug_info")) {
1521 opt_debug_info = sect;
1522 } else if (mem.eql(u8, name, "__debug_abbrev")) {
1523 opt_debug_abbrev = sect;
1524 } else if (mem.eql(u8, name, "__debug_str")) {
1525 opt_debug_str = sect;
1526 } else if (mem.eql(u8, name, "__debug_str_offsets")) {
1527 opt_debug_str_offsets = sect;
1528 } else if (mem.eql(u8, name, "__debug_line_str")) {
1529 opt_debug_line_str = sect;
1530 } else if (mem.eql(u8, name, "__debug_ranges")) {
1531 opt_debug_ranges = sect;
1532 } else if (mem.eql(u8, name, "__debug_loclists")) {
1533 opt_debug_loclists = sect;
1534 } else if (mem.eql(u8, name, "__debug_rnglists")) {
1535 opt_debug_rnglists = sect;
1536 } else if (mem.eql(u8, name, "__debug_addr")) {
1537 opt_debug_addr = sect;
1538 } else if (mem.eql(u8, name, "__debug_names")) {
1539 opt_debug_names = sect;
1540 } else if (mem.eql(u8, name, "__debug_frame")) {
1541 opt_debug_frame = sect;
2005 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
2006
2007 var section_index: ?usize = null;
2008 inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| {
2009 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i;
15422010 }
2011 if (section_index == null) continue;
2012
2013 const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size);
2014 sections[section_index.?] = .{
2015 .data = section_bytes,
2016 .virtual_address = sect.addr,
2017 .owned = false,
2018 };
15432019 }
15442020
1545 const debug_line = opt_debug_line orelse
1546 return error.MissingDebugInfo;
1547 const debug_info = opt_debug_info orelse
1548 return error.MissingDebugInfo;
1549 const debug_str = opt_debug_str orelse
1550 return error.MissingDebugInfo;
1551 const debug_abbrev = opt_debug_abbrev orelse
1552 return error.MissingDebugInfo;
2021 const missing_debug_info =
2022 sections[@intFromEnum(DW.DwarfSection.debug_info)] == null or
2023 sections[@intFromEnum(DW.DwarfSection.debug_abbrev)] == null or
2024 sections[@intFromEnum(DW.DwarfSection.debug_str)] == null or
2025 sections[@intFromEnum(DW.DwarfSection.debug_line)] == null;
2026 if (missing_debug_info) return error.MissingDebugInfo;
15532027
15542028 var di = DW.DwarfInfo{
15552029 .endian = .Little,
1556 .debug_info = try chopSlice(mapped_mem, debug_info.offset, debug_info.size),
1557 .debug_abbrev = try chopSlice(mapped_mem, debug_abbrev.offset, debug_abbrev.size),
1558 .debug_str = try chopSlice(mapped_mem, debug_str.offset, debug_str.size),
1559 .debug_str_offsets = if (opt_debug_str_offsets) |debug_str_offsets|
1560 try chopSlice(mapped_mem, debug_str_offsets.offset, debug_str_offsets.size)
1561 else
1562 null,
1563 .debug_line = try chopSlice(mapped_mem, debug_line.offset, debug_line.size),
1564 .debug_line_str = if (opt_debug_line_str) |debug_line_str|
1565 try chopSlice(mapped_mem, debug_line_str.offset, debug_line_str.size)
1566 else
1567 null,
1568 .debug_ranges = if (opt_debug_ranges) |debug_ranges|
1569 try chopSlice(mapped_mem, debug_ranges.offset, debug_ranges.size)
1570 else
1571 null,
1572 .debug_loclists = if (opt_debug_loclists) |debug_loclists|
1573 try chopSlice(mapped_mem, debug_loclists.offset, debug_loclists.size)
1574 else
1575 null,
1576 .debug_rnglists = if (opt_debug_rnglists) |debug_rnglists|
1577 try chopSlice(mapped_mem, debug_rnglists.offset, debug_rnglists.size)
1578 else
1579 null,
1580 .debug_addr = if (opt_debug_addr) |debug_addr|
1581 try chopSlice(mapped_mem, debug_addr.offset, debug_addr.size)
1582 else
1583 null,
1584 .debug_names = if (opt_debug_names) |debug_names|
1585 try chopSlice(mapped_mem, debug_names.offset, debug_names.size)
1586 else
1587 null,
1588 .debug_frame = if (opt_debug_frame) |debug_frame|
1589 try chopSlice(mapped_mem, debug_frame.offset, debug_frame.size)
1590 else
1591 null,
2030 .sections = sections,
2031 .is_macho = true,
15922032 };
15932033
15942034 try DW.openDwarfDebugInfo(&di, allocator);
......@@ -1598,52 +2038,38 @@ pub const ModuleDebugInfo = switch (native_os) {
15982038 };
15992039
16002040 // Add the debug info to the cache
1601 try self.ofiles.putNoClobber(o_file_path, info);
2041 const result = try self.ofiles.getOrPut(o_file_path);
2042 assert(!result.found_existing);
2043 result.value_ptr.* = info;
16022044
1603 return info;
2045 return result.value_ptr;
16042046 }
16052047
16062048 pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
16072049 nosuspend {
1608 // Translate the VA into an address into this object
1609 const relocated_address = address - self.base_address;
1610
1611 // Find the .o file where this symbol is defined
1612 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse
1613 return SymbolInfo{};
1614 const addr_off = relocated_address - symbol.addr;
2050 const result = try self.getOFileInfoForAddress(allocator, address);
2051 if (result.symbol == null) return .{};
16152052
16162053 // Take the symbol name from the N_FUN STAB entry, we're going to
16172054 // use it if we fail to find the DWARF infos
1618 const stab_symbol = mem.sliceTo(self.strings[symbol.strx..], 0);
1619 const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
1620
1621 // Check if its debug infos are already in the cache
1622 var o_file_info = self.ofiles.get(o_file_path) orelse
1623 (self.loadOFile(allocator, o_file_path) catch |err| switch (err) {
1624 error.FileNotFound,
1625 error.MissingDebugInfo,
1626 error.InvalidDebugInfo,
1627 => {
1628 return SymbolInfo{ .symbol_name = stab_symbol };
1629 },
1630 else => return err,
1631 });
1632 const o_file_di = &o_file_info.di;
2055 const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0);
2056 if (result.o_file_info == null) return .{ .symbol_name = stab_symbol };
16332057
16342058 // Translate again the address, this time into an address inside the
16352059 // .o file
1636 const relocated_address_o = o_file_info.addr_table.get(stab_symbol) orelse return SymbolInfo{
2060 const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{
16372061 .symbol_name = "???",
16382062 };
16392063
2064 const addr_off = result.relocated_address - result.symbol.?.addr;
2065 const o_file_di = &result.o_file_info.?.di;
16402066 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
16412067 return SymbolInfo{
16422068 .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
16432069 .compile_unit_name = compile_unit.die.getAttrString(
16442070 o_file_di,
16452071 DW.AT.name,
1646 o_file_di.debug_str,
2072 o_file_di.section(.debug_str),
16472073 compile_unit.*,
16482074 ) catch |err| switch (err) {
16492075 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
......@@ -1663,39 +2089,61 @@ pub const ModuleDebugInfo = switch (native_os) {
16632089 },
16642090 else => return err,
16652091 }
2092 }
2093 }
2094
2095 pub fn getOFileInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !struct {
2096 relocated_address: usize,
2097 symbol: ?*const MachoSymbol = null,
2098 o_file_info: ?*OFileInfo = null,
2099 } {
2100 nosuspend {
2101 // Translate the VA into an address into this object
2102 const relocated_address = address - self.vmaddr_slide;
2103
2104 // Find the .o file where this symbol is defined
2105 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{
2106 .relocated_address = relocated_address,
2107 };
2108
2109 // Check if its debug infos are already in the cache
2110 const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
2111 var o_file_info = self.ofiles.getPtr(o_file_path) orelse
2112 (self.loadOFile(allocator, o_file_path) catch |err| switch (err) {
2113 error.FileNotFound,
2114 error.MissingDebugInfo,
2115 error.InvalidDebugInfo,
2116 => return .{
2117 .relocated_address = relocated_address,
2118 .symbol = symbol,
2119 },
2120 else => return err,
2121 });
16662122
1667 unreachable;
2123 return .{
2124 .relocated_address = relocated_address,
2125 .symbol = symbol,
2126 .o_file_info = o_file_info,
2127 };
16682128 }
16692129 }
2130
2131 pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo {
2132 return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;
2133 }
16702134 },
16712135 .uefi, .windows => struct {
16722136 base_address: usize,
16732137 debug_data: PdbOrDwarf,
16742138 coff_image_base: u64,
2139 /// Only used if debug_data is .pdb
16752140 coff_section_headers: []coff.SectionHeader,
16762141
16772142 fn deinit(self: *@This(), allocator: mem.Allocator) void {
1678 switch (self.debug_data) {
1679 .dwarf => |*dwarf| {
1680 allocator.free(dwarf.debug_info);
1681 allocator.free(dwarf.debug_abbrev);
1682 allocator.free(dwarf.debug_str);
1683 allocator.free(dwarf.debug_line);
1684 if (dwarf.debug_str_offsets) |d| allocator.free(d);
1685 if (dwarf.debug_line_str) |d| allocator.free(d);
1686 if (dwarf.debug_ranges) |d| allocator.free(d);
1687 if (dwarf.debug_loclists) |d| allocator.free(d);
1688 if (dwarf.debug_rnglists) |d| allocator.free(d);
1689 if (dwarf.debug_addr) |d| allocator.free(d);
1690 if (dwarf.debug_names) |d| allocator.free(d);
1691 if (dwarf.debug_frame) |d| allocator.free(d);
1692 },
1693 .pdb => {
1694 allocator.free(self.coff_section_headers);
1695 },
1696 }
1697
16982143 self.debug_data.deinit(allocator);
2144 if (self.debug_data == .pdb) {
2145 allocator.free(self.coff_section_headers);
2146 }
16992147 }
17002148
17012149 pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
......@@ -1747,15 +2195,27 @@ pub const ModuleDebugInfo = switch (native_os) {
17472195 .line_info = opt_line_info,
17482196 };
17492197 }
2198
2199 pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo {
2200 _ = allocator;
2201 _ = address;
2202
2203 return switch (self.debug_data) {
2204 .dwarf => |*dwarf| dwarf,
2205 else => null,
2206 };
2207 }
17502208 },
17512209 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris => struct {
17522210 base_address: usize,
17532211 dwarf: DW.DwarfInfo,
17542212 mapped_memory: []align(mem.page_size) const u8,
2213 external_mapped_memory: ?[]align(mem.page_size) const u8,
17552214
17562215 fn deinit(self: *@This(), allocator: mem.Allocator) void {
17572216 self.dwarf.deinit(allocator);
17582217 os.munmap(self.mapped_memory);
2218 if (self.external_mapped_memory) |m| os.munmap(m);
17592219 }
17602220
17612221 pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
......@@ -1763,6 +2223,12 @@ pub const ModuleDebugInfo = switch (native_os) {
17632223 const relocated_address = address - self.base_address;
17642224 return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf);
17652225 }
2226
2227 pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo {
2228 _ = allocator;
2229 _ = address;
2230 return &self.dwarf;
2231 }
17662232 },
17672233 .wasi => struct {
17682234 fn deinit(self: *@This(), allocator: mem.Allocator) void {
......@@ -1776,6 +2242,13 @@ pub const ModuleDebugInfo = switch (native_os) {
17762242 _ = address;
17772243 return SymbolInfo{};
17782244 }
2245
2246 pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo {
2247 _ = self;
2248 _ = allocator;
2249 _ = address;
2250 return null;
2251 }
17792252 },
17802253 else => DW.DwarfInfo,
17812254};
......@@ -1784,7 +2257,7 @@ fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *DW.DwarfInfo)
17842257 if (nosuspend di.findCompileUnit(address)) |compile_unit| {
17852258 return SymbolInfo{
17862259 .symbol_name = nosuspend di.getSymbolName(address) orelse "???",
1787 .compile_unit_name = compile_unit.die.getAttrString(di, DW.AT.name, di.debug_str, compile_unit.*) catch |err| switch (err) {
2260 .compile_unit_name = compile_unit.die.getAttrString(di, DW.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) {
17882261 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
17892262 },
17902263 .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) {
......@@ -1932,52 +2405,13 @@ fn dumpSegfaultInfoPosix(sig: i32, addr: usize, ctx_ptr: ?*const anyopaque) void
19322405 } catch os.abort();
19332406
19342407 switch (native_arch) {
1935 .x86 => {
1936 const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
1937 const ip = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EIP]));
1938 const bp = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EBP]));
1939 dumpStackTraceFromBase(bp, ip);
1940 },
1941 .x86_64 => {
1942 const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
1943 const ip = switch (native_os) {
1944 .linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RIP])),
1945 .freebsd => @as(usize, @intCast(ctx.mcontext.rip)),
1946 .openbsd => @as(usize, @intCast(ctx.sc_rip)),
1947 .macos => @as(usize, @intCast(ctx.mcontext.ss.rip)),
1948 else => unreachable,
1949 };
1950 const bp = switch (native_os) {
1951 .linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RBP])),
1952 .openbsd => @as(usize, @intCast(ctx.sc_rbp)),
1953 .freebsd => @as(usize, @intCast(ctx.mcontext.rbp)),
1954 .macos => @as(usize, @intCast(ctx.mcontext.ss.rbp)),
1955 else => unreachable,
1956 };
1957 dumpStackTraceFromBase(bp, ip);
1958 },
1959 .arm => {
2408 .x86,
2409 .x86_64,
2410 .arm,
2411 .aarch64,
2412 => {
19602413 const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
1961 const ip = @as(usize, @intCast(ctx.mcontext.arm_pc));
1962 const bp = @as(usize, @intCast(ctx.mcontext.arm_fp));
1963 dumpStackTraceFromBase(bp, ip);
1964 },
1965 .aarch64 => {
1966 const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
1967 const ip = switch (native_os) {
1968 .macos => @as(usize, @intCast(ctx.mcontext.ss.pc)),
1969 .netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.PC])),
1970 .freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.elr)),
1971 else => @as(usize, @intCast(ctx.mcontext.pc)),
1972 };
1973 // x29 is the ABI-designated frame pointer
1974 const bp = switch (native_os) {
1975 .macos => @as(usize, @intCast(ctx.mcontext.ss.fp)),
1976 .netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.FP])),
1977 .freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.x[os.REG.FP])),
1978 else => @as(usize, @intCast(ctx.mcontext.regs[29])),
1979 };
1980 dumpStackTraceFromBase(bp, ip);
2414 dumpStackTraceFromBase(ctx);
19812415 },
19822416 else => {},
19832417 }
......@@ -2036,16 +2470,15 @@ fn handleSegfaultWindowsExtra(
20362470}
20372471
20382472fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void {
2039 const regs = info.ContextRecord.getRegs();
20402473 const stderr = io.getStdErr().writer();
20412474 _ = switch (msg) {
20422475 0 => stderr.print("{s}\n", .{label.?}),
20432476 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}),
2044 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{regs.ip}),
2477 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{info.ContextRecord.getRegs().ip}),
20452478 else => unreachable,
20462479 } catch os.abort();
20472480
2048 dumpStackTraceFromBase(regs.bp, regs.ip);
2481 dumpStackTraceFromBase(info.ContextRecord);
20492482}
20502483
20512484pub fn dumpStackPointerAddr(prefix: []const u8) void {
lib/std/dwarf.zig+1462-116
......@@ -3,9 +3,11 @@ const std = @import("std.zig");
33const debug = std.debug;
44const fs = std.fs;
55const io = std.io;
6const os = std.os;
67const mem = std.mem;
78const math = std.math;
89const leb = @import("leb128.zig");
10const assert = std.debug.assert;
911
1012pub const TAG = @import("dwarf/TAG.zig");
1113pub const AT = @import("dwarf/AT.zig");
......@@ -13,6 +15,10 @@ pub const OP = @import("dwarf/OP.zig");
1315pub const LANG = @import("dwarf/LANG.zig");
1416pub const FORM = @import("dwarf/FORM.zig");
1517pub const ATE = @import("dwarf/ATE.zig");
18pub const EH = @import("dwarf/EH.zig");
19pub const abi = @import("dwarf/abi.zig");
20pub const call_frame = @import("dwarf/call_frame.zig");
21pub const expressions = @import("dwarf/expressions.zig");
1622
1723pub const LLE = struct {
1824 pub const end_of_list = 0x00;
......@@ -140,11 +146,11 @@ pub const CC = enum(u8) {
140146 pass_by_reference = 0x4,
141147 pass_by_value = 0x5,
142148
143 lo_user = 0x40,
144 hi_user = 0xff,
145
146149 GNU_renesas_sh = 0x40,
147150 GNU_borland_fastcall_i386 = 0x41,
151
152 pub const lo_user = 0x40;
153 pub const hi_user = 0xff;
148154};
149155
150156pub const Format = enum { @"32", @"64" };
......@@ -157,15 +163,9 @@ const PcRange = struct {
157163const Func = struct {
158164 pc_range: ?PcRange,
159165 name: ?[]const u8,
160
161 fn deinit(func: *Func, allocator: mem.Allocator) void {
162 if (func.name) |name| {
163 allocator.free(name);
164 }
165 }
166166};
167167
168const CompileUnit = struct {
168pub const CompileUnit = struct {
169169 version: u16,
170170 is_64: bool,
171171 die: *Die,
......@@ -175,6 +175,7 @@ const CompileUnit = struct {
175175 addr_base: usize,
176176 rnglists_base: usize,
177177 loclists_base: usize,
178 frame_base: ?*const FormValue,
178179};
179180
180181const AbbrevTable = std.ArrayList(AbbrevTableEntry);
......@@ -210,7 +211,7 @@ const AbbrevAttr = struct {
210211 payload: i64,
211212};
212213
213const FormValue = union(enum) {
214pub const FormValue = union(enum) {
214215 Address: u64,
215216 AddrOffset: usize,
216217 Block: []u8,
......@@ -292,7 +293,7 @@ const Die = struct {
292293
293294 fn getAttrAddr(
294295 self: *const Die,
295 di: *DwarfInfo,
296 di: *const DwarfInfo,
296297 id: u64,
297298 compile_unit: CompileUnit,
298299 ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 {
......@@ -337,7 +338,7 @@ const Die = struct {
337338 FormValue.String => |value| return value,
338339 FormValue.StrPtr => |offset| return di.getString(offset),
339340 FormValue.StrOffset => |index| {
340 const debug_str_offsets = di.debug_str_offsets orelse return badDwarf();
341 const debug_str_offsets = di.section(.debug_str_offsets) orelse return badDwarf();
341342 if (compile_unit.str_offsets_base == 0) return badDwarf();
342343 if (compile_unit.is_64) {
343344 const byte_offset = compile_unit.str_offsets_base + 8 * index;
......@@ -642,27 +643,75 @@ fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*con
642643 return null;
643644}
644645
646pub const DwarfSection = enum {
647 debug_info,
648 debug_abbrev,
649 debug_str,
650 debug_str_offsets,
651 debug_line,
652 debug_line_str,
653 debug_ranges,
654 debug_loclists,
655 debug_rnglists,
656 debug_addr,
657 debug_names,
658 debug_frame,
659 eh_frame,
660 eh_frame_hdr,
661};
662
645663pub const DwarfInfo = struct {
664 pub const Section = struct {
665 data: []const u8,
666 // Module-relative virtual address.
667 // Only set if the section data was loaded from disk.
668 virtual_address: ?usize = null,
669 // If `data` is owned by this DwarfInfo.
670 owned: bool,
671
672 // For sections that are not memory mapped by the loader, this is an offset
673 // from `data.ptr` to where the section would have been mapped. Otherwise,
674 // `data` is directly backed by the section and the offset is zero.
675 pub fn virtualOffset(self: Section, base_address: usize) i64 {
676 return if (self.virtual_address) |va|
677 @as(i64, @intCast(base_address + va)) -
678 @as(i64, @intCast(@intFromPtr(self.data.ptr)))
679 else
680 0;
681 }
682 };
683
684 const num_sections = std.enums.directEnumArrayLen(DwarfSection, 0);
685 pub const SectionArray = [num_sections]?Section;
686 pub const null_section_array = [_]?Section{null} ** num_sections;
687
646688 endian: std.builtin.Endian,
647 // No memory is owned by the DwarfInfo
648 debug_info: []const u8,
649 debug_abbrev: []const u8,
650 debug_str: []const u8,
651 debug_str_offsets: ?[]const u8,
652 debug_line: []const u8,
653 debug_line_str: ?[]const u8,
654 debug_ranges: ?[]const u8,
655 debug_loclists: ?[]const u8,
656 debug_rnglists: ?[]const u8,
657 debug_addr: ?[]const u8,
658 debug_names: ?[]const u8,
659 debug_frame: ?[]const u8,
689 sections: SectionArray = null_section_array,
690 is_macho: bool,
691
660692 // Filled later by the initializer
661693 abbrev_table_list: std.ArrayListUnmanaged(AbbrevTableHeader) = .{},
662694 compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .{},
663695 func_list: std.ArrayListUnmanaged(Func) = .{},
664696
697 eh_frame_hdr: ?ExceptionFrameHeader = null,
698 // These lookup tables are only used if `eh_frame_hdr` is null
699 cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .{},
700 // Sorted by start_pc
701 fde_list: std.ArrayListUnmanaged(FrameDescriptionEntry) = .{},
702
703 pub fn section(di: DwarfInfo, dwarf_section: DwarfSection) ?[]const u8 {
704 return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;
705 }
706
707 pub fn sectionVirtualOffset(di: DwarfInfo, dwarf_section: DwarfSection, base_address: usize) ?i64 {
708 return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null;
709 }
710
665711 pub fn deinit(di: *DwarfInfo, allocator: mem.Allocator) void {
712 for (di.sections) |opt_section| {
713 if (opt_section) |s| if (s.owned) allocator.free(s.data);
714 }
666715 for (di.abbrev_table_list.items) |*abbrev| {
667716 abbrev.deinit();
668717 }
......@@ -672,10 +721,9 @@ pub const DwarfInfo = struct {
672721 allocator.destroy(cu.die);
673722 }
674723 di.compile_unit_list.deinit(allocator);
675 for (di.func_list.items) |*func| {
676 func.deinit(allocator);
677 }
678724 di.func_list.deinit(allocator);
725 di.cie_map.deinit(allocator);
726 di.fde_list.deinit(allocator);
679727 }
680728
681729 pub fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 {
......@@ -691,7 +739,7 @@ pub const DwarfInfo = struct {
691739 }
692740
693741 fn scanAllFunctions(di: *DwarfInfo, allocator: mem.Allocator) !void {
694 var stream = io.fixedBufferStream(di.debug_info);
742 var stream = io.fixedBufferStream(di.section(.debug_info).?);
695743 const in = stream.reader();
696744 const seekable = &stream.seekableStream();
697745 var this_unit_offset: u64 = 0;
......@@ -755,6 +803,7 @@ pub const DwarfInfo = struct {
755803 .addr_base = if (die_obj.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0,
756804 .rnglists_base = if (die_obj.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0,
757805 .loclists_base = if (die_obj.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0,
806 .frame_base = die_obj.getAttr(AT.frame_base),
758807 };
759808 },
760809 TAG.subprogram, TAG.inlined_subroutine, TAG.subroutine, TAG.entry_point => {
......@@ -764,8 +813,7 @@ pub const DwarfInfo = struct {
764813 // Prevent endless loops
765814 while (depth > 0) : (depth -= 1) {
766815 if (this_die_obj.getAttr(AT.name)) |_| {
767 const name = try this_die_obj.getAttrString(di, AT.name, di.debug_str, compile_unit);
768 break :x try allocator.dupe(u8, name);
816 break :x try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit);
769817 } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| {
770818 // Follow the DIE it points to and repeat
771819 const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin);
......@@ -796,34 +844,58 @@ pub const DwarfInfo = struct {
796844 break :x null;
797845 };
798846
799 const pc_range = x: {
800 if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| {
801 if (die_obj.getAttr(AT.high_pc)) |high_pc_value| {
802 const pc_end = switch (high_pc_value.*) {
803 FormValue.Address => |value| value,
804 FormValue.Const => |value| b: {
805 const offset = try value.asUnsignedLe();
806 break :b (low_pc + offset);
807 },
808 else => return badDwarf(),
809 };
810 break :x PcRange{
847 var range_added = if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| blk: {
848 if (die_obj.getAttr(AT.high_pc)) |high_pc_value| {
849 const pc_end = switch (high_pc_value.*) {
850 FormValue.Address => |value| value,
851 FormValue.Const => |value| b: {
852 const offset = try value.asUnsignedLe();
853 break :b (low_pc + offset);
854 },
855 else => return badDwarf(),
856 };
857
858 try di.func_list.append(allocator, Func{
859 .name = fn_name,
860 .pc_range = .{
811861 .start = low_pc,
812862 .end = pc_end,
813 };
814 } else {
815 break :x null;
816 }
817 } else |err| {
818 if (err != error.MissingDebugInfo) return err;
819 break :x null;
863 },
864 });
865
866 break :blk true;
820867 }
868
869 break :blk false;
870 } else |err| blk: {
871 if (err != error.MissingDebugInfo) return err;
872 break :blk false;
821873 };
822874
823 try di.func_list.append(allocator, Func{
824 .name = fn_name,
825 .pc_range = pc_range,
826 });
875 if (die_obj.getAttr(AT.ranges)) |ranges_value| blk: {
876 var iter = DebugRangeIterator.init(ranges_value, di, &compile_unit) catch |err| {
877 if (err != error.MissingDebugInfo) return err;
878 break :blk;
879 };
880
881 while (try iter.next()) |range| {
882 range_added = true;
883 try di.func_list.append(allocator, Func{
884 .name = fn_name,
885 .pc_range = .{
886 .start = range.start_addr,
887 .end = range.end_addr,
888 },
889 });
890 }
891 }
892
893 if (fn_name != null and !range_added) {
894 try di.func_list.append(allocator, Func{
895 .name = fn_name,
896 .pc_range = null,
897 });
898 }
827899 },
828900 else => {},
829901 }
......@@ -836,7 +908,7 @@ pub const DwarfInfo = struct {
836908 }
837909
838910 fn scanAllCompileUnits(di: *DwarfInfo, allocator: mem.Allocator) !void {
839 var stream = io.fixedBufferStream(di.debug_info);
911 var stream = io.fixedBufferStream(di.section(.debug_info).?);
840912 const in = &stream.reader();
841913 const seekable = &stream.seekableStream();
842914 var this_unit_offset: u64 = 0;
......@@ -892,6 +964,7 @@ pub const DwarfInfo = struct {
892964 .addr_base = if (compile_unit_die.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0,
893965 .rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0,
894966 .loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0,
967 .frame_base = compile_unit_die.getAttr(AT.frame_base),
895968 };
896969
897970 compile_unit.pc_range = x: {
......@@ -924,17 +997,18 @@ pub const DwarfInfo = struct {
924997 }
925998 }
926999
927 pub fn findCompileUnit(di: *DwarfInfo, target_address: u64) !*const CompileUnit {
928 for (di.compile_unit_list.items) |*compile_unit| {
929 if (compile_unit.pc_range) |range| {
930 if (target_address >= range.start and target_address < range.end) return compile_unit;
931 }
1000 const DebugRangeIterator = struct {
1001 base_address: u64,
1002 section_type: DwarfSection,
1003 di: *const DwarfInfo,
1004 compile_unit: *const CompileUnit,
1005 stream: io.FixedBufferStream([]const u8),
9321006
933 const opt_debug_ranges = if (compile_unit.version >= 5) di.debug_rnglists else di.debug_ranges;
934 const debug_ranges = opt_debug_ranges orelse continue;
1007 pub fn init(ranges_value: *const FormValue, di: *const DwarfInfo, compile_unit: *const CompileUnit) !@This() {
1008 const section_type = if (compile_unit.version >= 5) DwarfSection.debug_rnglists else DwarfSection.debug_ranges;
1009 const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo;
9351010
936 const ranges_val = compile_unit.die.getAttr(AT.ranges) orelse continue;
937 const ranges_offset = switch (ranges_val.*) {
1011 const ranges_offset = switch (ranges_value.*) {
9381012 .SecOffset => |off| off,
9391013 .Const => |c| try c.asUnsignedLe(),
9401014 .RangeListOffset => |idx| off: {
......@@ -954,8 +1028,7 @@ pub const DwarfInfo = struct {
9541028 };
9551029
9561030 var stream = io.fixedBufferStream(debug_ranges);
957 const in = &stream.reader();
958 const seekable = &stream.seekableStream();
1031 try stream.seekTo(ranges_offset);
9591032
9601033 // All the addresses in the list are relative to the value
9611034 // specified by DW_AT.low_pc or to some other value encoded
......@@ -966,86 +1039,122 @@ pub const DwarfInfo = struct {
9661039 else => return err,
9671040 };
9681041
969 try seekable.seekTo(ranges_offset);
1042 return .{
1043 .base_address = base_address,
1044 .section_type = section_type,
1045 .di = di,
1046 .compile_unit = compile_unit,
1047 .stream = stream,
1048 };
1049 }
9701050
971 if (compile_unit.version >= 5) {
972 while (true) {
1051 // Returns the next range in the list, or null if the end was reached.
1052 pub fn next(self: *@This()) !?struct { start_addr: u64, end_addr: u64 } {
1053 const in = self.stream.reader();
1054 switch (self.section_type) {
1055 .debug_rnglists => {
9731056 const kind = try in.readByte();
9741057 switch (kind) {
975 RLE.end_of_list => break,
1058 RLE.end_of_list => return null,
9761059 RLE.base_addressx => {
9771060 const index = try leb.readULEB128(usize, in);
978 base_address = try di.readDebugAddr(compile_unit.*, index);
1061 self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index);
1062 return try self.next();
9791063 },
9801064 RLE.startx_endx => {
9811065 const start_index = try leb.readULEB128(usize, in);
982 const start_addr = try di.readDebugAddr(compile_unit.*, start_index);
1066 const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index);
9831067
9841068 const end_index = try leb.readULEB128(usize, in);
985 const end_addr = try di.readDebugAddr(compile_unit.*, end_index);
1069 const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index);
9861070
987 if (target_address >= start_addr and target_address < end_addr) {
988 return compile_unit;
989 }
1071 return .{
1072 .start_addr = start_addr,
1073 .end_addr = end_addr,
1074 };
9901075 },
9911076 RLE.startx_length => {
9921077 const start_index = try leb.readULEB128(usize, in);
993 const start_addr = try di.readDebugAddr(compile_unit.*, start_index);
1078 const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index);
9941079
9951080 const len = try leb.readULEB128(usize, in);
9961081 const end_addr = start_addr + len;
9971082
998 if (target_address >= start_addr and target_address < end_addr) {
999 return compile_unit;
1000 }
1083 return .{
1084 .start_addr = start_addr,
1085 .end_addr = end_addr,
1086 };
10011087 },
10021088 RLE.offset_pair => {
10031089 const start_addr = try leb.readULEB128(usize, in);
10041090 const end_addr = try leb.readULEB128(usize, in);
1091
10051092 // This is the only kind that uses the base address
1006 if (target_address >= base_address + start_addr and target_address < base_address + end_addr) {
1007 return compile_unit;
1008 }
1093 return .{
1094 .start_addr = self.base_address + start_addr,
1095 .end_addr = self.base_address + end_addr,
1096 };
10091097 },
10101098 RLE.base_address => {
1011 base_address = try in.readInt(usize, di.endian);
1099 self.base_address = try in.readInt(usize, self.di.endian);
1100 return try self.next();
10121101 },
10131102 RLE.start_end => {
1014 const start_addr = try in.readInt(usize, di.endian);
1015 const end_addr = try in.readInt(usize, di.endian);
1016 if (target_address >= start_addr and target_address < end_addr) {
1017 return compile_unit;
1018 }
1103 const start_addr = try in.readInt(usize, self.di.endian);
1104 const end_addr = try in.readInt(usize, self.di.endian);
1105
1106 return .{
1107 .start_addr = start_addr,
1108 .end_addr = end_addr,
1109 };
10191110 },
10201111 RLE.start_length => {
1021 const start_addr = try in.readInt(usize, di.endian);
1112 const start_addr = try in.readInt(usize, self.di.endian);
10221113 const len = try leb.readULEB128(usize, in);
10231114 const end_addr = start_addr + len;
1024 if (target_address >= start_addr and target_address < end_addr) {
1025 return compile_unit;
1026 }
1115
1116 return .{
1117 .start_addr = start_addr,
1118 .end_addr = end_addr,
1119 };
10271120 },
10281121 else => return badDwarf(),
10291122 }
1030 }
1031 } else {
1032 while (true) {
1033 const begin_addr = try in.readInt(usize, di.endian);
1034 const end_addr = try in.readInt(usize, di.endian);
1035 if (begin_addr == 0 and end_addr == 0) {
1036 break;
1037 }
1123 },
1124 .debug_ranges => {
1125 const start_addr = try in.readInt(usize, self.di.endian);
1126 const end_addr = try in.readInt(usize, self.di.endian);
1127 if (start_addr == 0 and end_addr == 0) return null;
1128
10381129 // This entry selects a new value for the base address
1039 if (begin_addr == math.maxInt(usize)) {
1040 base_address = end_addr;
1041 continue;
1130 if (start_addr == math.maxInt(usize)) {
1131 self.base_address = end_addr;
1132 return try self.next();
10421133 }
1043 if (target_address >= base_address + begin_addr and target_address < base_address + end_addr) {
1044 return compile_unit;
1045 }
1046 }
1134
1135 return .{
1136 .start_addr = self.base_address + start_addr,
1137 .end_addr = self.base_address + end_addr,
1138 };
1139 },
1140 else => unreachable,
10471141 }
10481142 }
1143 };
1144
1145 pub fn findCompileUnit(di: *const DwarfInfo, target_address: u64) !*const CompileUnit {
1146 for (di.compile_unit_list.items) |*compile_unit| {
1147 if (compile_unit.pc_range) |range| {
1148 if (target_address >= range.start and target_address < range.end) return compile_unit;
1149 }
1150
1151 const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue;
1152 var iter = DebugRangeIterator.init(ranges_value, di, compile_unit) catch continue;
1153 while (try iter.next()) |range| {
1154 if (target_address >= range.start_addr and target_address < range.end_addr) return compile_unit;
1155 }
1156 }
1157
10491158 return missingDwarf();
10501159 }
10511160
......@@ -1065,7 +1174,7 @@ pub const DwarfInfo = struct {
10651174 }
10661175
10671176 fn parseAbbrevTable(di: *DwarfInfo, allocator: mem.Allocator, offset: u64) !AbbrevTable {
1068 var stream = io.fixedBufferStream(di.debug_abbrev);
1177 var stream = io.fixedBufferStream(di.section(.debug_abbrev).?);
10691178 const in = &stream.reader();
10701179 const seekable = &stream.seekableStream();
10711180
......@@ -1146,11 +1255,11 @@ pub const DwarfInfo = struct {
11461255 compile_unit: CompileUnit,
11471256 target_address: u64,
11481257 ) !debug.LineInfo {
1149 var stream = io.fixedBufferStream(di.debug_line);
1258 var stream = io.fixedBufferStream(di.section(.debug_line).?);
11501259 const in = &stream.reader();
11511260 const seekable = &stream.seekableStream();
11521261
1153 const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.debug_line_str, compile_unit);
1262 const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit);
11541263 const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
11551264
11561265 try seekable.seekTo(line_info_offset);
......@@ -1416,15 +1525,15 @@ pub const DwarfInfo = struct {
14161525 }
14171526
14181527 fn getString(di: DwarfInfo, offset: u64) ![]const u8 {
1419 return getStringGeneric(di.debug_str, offset);
1528 return getStringGeneric(di.section(.debug_str), offset);
14201529 }
14211530
14221531 fn getLineString(di: DwarfInfo, offset: u64) ![]const u8 {
1423 return getStringGeneric(di.debug_line_str, offset);
1532 return getStringGeneric(di.section(.debug_line_str), offset);
14241533 }
14251534
14261535 fn readDebugAddr(di: DwarfInfo, compile_unit: CompileUnit, index: u64) !u64 {
1427 const debug_addr = di.debug_addr orelse return badDwarf();
1536 const debug_addr = di.section(.debug_addr) orelse return badDwarf();
14281537
14291538 // addr_base points to the first item after the header, however we
14301539 // need to read the header to know the size of each item. Empirically,
......@@ -1448,10 +1557,689 @@ pub const DwarfInfo = struct {
14481557 else => badDwarf(),
14491558 };
14501559 }
1560
1561 /// If .eh_frame_hdr is present, then only the header needs to be parsed.
1562 ///
1563 /// Otherwise, .eh_frame and .debug_frame are scanned and a sorted list
1564 /// of FDEs is built for binary searching during unwinding.
1565 pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator, base_address: usize) !void {
1566 if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {
1567 var stream = io.fixedBufferStream(eh_frame_hdr);
1568 const reader = stream.reader();
1569
1570 const version = try reader.readByte();
1571 if (version != 1) break :blk;
1572
1573 const eh_frame_ptr_enc = try reader.readByte();
1574 if (eh_frame_ptr_enc == EH.PE.omit) break :blk;
1575 const fde_count_enc = try reader.readByte();
1576 if (fde_count_enc == EH.PE.omit) break :blk;
1577 const table_enc = try reader.readByte();
1578 if (table_enc == EH.PE.omit) break :blk;
1579
1580 const eh_frame_ptr = std.math.cast(usize, try readEhPointer(reader, eh_frame_ptr_enc, @sizeOf(usize), .{
1581 .pc_rel_base = @intFromPtr(&eh_frame_hdr[stream.pos]),
1582 .follow_indirect = true,
1583 }, builtin.cpu.arch.endian()) orelse return badDwarf()) orelse return badDwarf();
1584
1585 const fde_count = std.math.cast(usize, try readEhPointer(reader, fde_count_enc, @sizeOf(usize), .{
1586 .pc_rel_base = @intFromPtr(&eh_frame_hdr[stream.pos]),
1587 .follow_indirect = true,
1588 }, builtin.cpu.arch.endian()) orelse return badDwarf()) orelse return badDwarf();
1589
1590 const entry_size = try ExceptionFrameHeader.entrySize(table_enc);
1591 const entries_len = fde_count * entry_size;
1592 if (entries_len > eh_frame_hdr.len - stream.pos) return badDwarf();
1593
1594 di.eh_frame_hdr = .{
1595 .eh_frame_ptr = eh_frame_ptr,
1596 .table_enc = table_enc,
1597 .fde_count = fde_count,
1598 .entries = eh_frame_hdr[stream.pos..][0..entries_len],
1599 };
1600
1601 // No need to scan .eh_frame, we have a binary search table already
1602 return;
1603 }
1604
1605 const frame_sections = [2]DwarfSection{ .eh_frame, .debug_frame };
1606 for (frame_sections) |frame_section| {
1607 if (di.section(frame_section)) |section_data| {
1608 var stream = io.fixedBufferStream(section_data);
1609 while (stream.pos < stream.buffer.len) {
1610 const entry_header = try EntryHeader.read(&stream, frame_section, di.endian);
1611 switch (entry_header.type) {
1612 .cie => {
1613 const cie = try CommonInformationEntry.parse(
1614 entry_header.entry_bytes,
1615 di.sectionVirtualOffset(frame_section, base_address).?,
1616 true,
1617 entry_header.is_64,
1618 frame_section,
1619 entry_header.length_offset,
1620 @sizeOf(usize),
1621 di.endian,
1622 );
1623 try di.cie_map.put(allocator, entry_header.length_offset, cie);
1624 },
1625 .fde => |cie_offset| {
1626 const cie = di.cie_map.get(cie_offset) orelse return badDwarf();
1627 const fde = try FrameDescriptionEntry.parse(
1628 entry_header.entry_bytes,
1629 di.sectionVirtualOffset(frame_section, base_address).?,
1630 true,
1631 cie,
1632 @sizeOf(usize),
1633 di.endian,
1634 );
1635 try di.fde_list.append(allocator, fde);
1636 },
1637 .terminator => break,
1638 }
1639 }
1640
1641 std.mem.sort(FrameDescriptionEntry, di.fde_list.items, {}, struct {
1642 fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool {
1643 _ = ctx;
1644 return a.pc_begin < b.pc_begin;
1645 }
1646 }.lessThan);
1647 }
1648 }
1649 }
1650
1651 /// Unwind a stack frame using DWARF unwinding info, updating the register context.
1652 ///
1653 /// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE.
1654 /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE.
1655 ///
1656 /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
1657 /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
1658 pub fn unwindFrame(di: *const DwarfInfo, context: *UnwindContext, explicit_fde_offset: ?usize) !usize {
1659 if (!comptime abi.isSupportedArch(builtin.target.cpu.arch)) return error.UnsupportedCpuArchitecture;
1660 if (context.pc == 0) return 0;
1661
1662 // Find the FDE and CIE
1663 var cie: CommonInformationEntry = undefined;
1664 var fde: FrameDescriptionEntry = undefined;
1665
1666 if (explicit_fde_offset) |fde_offset| {
1667 const dwarf_section: DwarfSection = .eh_frame;
1668 const frame_section = di.section(dwarf_section) orelse return error.MissingFDE;
1669 if (fde_offset >= frame_section.len) return error.MissingFDE;
1670
1671 var stream = io.fixedBufferStream(frame_section);
1672 try stream.seekTo(fde_offset);
1673
1674 const fde_entry_header = try EntryHeader.read(&stream, dwarf_section, di.endian);
1675 if (fde_entry_header.type != .fde) return error.MissingFDE;
1676
1677 const cie_offset = fde_entry_header.type.fde;
1678 try stream.seekTo(cie_offset);
1679
1680 const cie_entry_header = try EntryHeader.read(&stream, dwarf_section, builtin.cpu.arch.endian());
1681 if (cie_entry_header.type != .cie) return badDwarf();
1682
1683 cie = try CommonInformationEntry.parse(
1684 cie_entry_header.entry_bytes,
1685 0,
1686 true,
1687 cie_entry_header.is_64,
1688 dwarf_section,
1689 cie_entry_header.length_offset,
1690 @sizeOf(usize),
1691 builtin.cpu.arch.endian(),
1692 );
1693
1694 fde = try FrameDescriptionEntry.parse(
1695 fde_entry_header.entry_bytes,
1696 0,
1697 true,
1698 cie,
1699 @sizeOf(usize),
1700 builtin.cpu.arch.endian(),
1701 );
1702 } else if (di.eh_frame_hdr) |header| {
1703 const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null;
1704 try header.findEntry(
1705 context.isValidMemory,
1706 eh_frame_len,
1707 @intFromPtr(di.section(.eh_frame_hdr).?.ptr),
1708 context.pc,
1709 &cie,
1710 &fde,
1711 );
1712 } else {
1713 const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct {
1714 pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order {
1715 if (pc < mid_item.pc_begin) return .lt;
1716
1717 const range_end = mid_item.pc_begin + mid_item.pc_range;
1718 if (pc < range_end) return .eq;
1719
1720 return .gt;
1721 }
1722 }.compareFn);
1723
1724 fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE;
1725 cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;
1726 }
1727
1728 var expression_context = .{
1729 .is_64 = cie.is_64,
1730 .isValidMemory = context.isValidMemory,
1731 .compile_unit = di.findCompileUnit(fde.pc_begin) catch null,
1732 .thread_context = context.thread_context,
1733 .reg_context = context.reg_context,
1734 .cfa = context.cfa,
1735 };
1736
1737 context.vm.reset();
1738 context.reg_context.eh_frame = cie.version != 4;
1739 context.reg_context.is_macho = di.is_macho;
1740
1741 const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde);
1742 context.cfa = switch (row.cfa.rule) {
1743 .val_offset => |offset| blk: {
1744 const register = row.cfa.register orelse return error.InvalidCFARule;
1745 const value = mem.readIntSliceNative(usize, try abi.regBytes(context.thread_context, register, context.reg_context));
1746 break :blk try call_frame.applyOffset(value, offset);
1747 },
1748 .expression => |expression| blk: {
1749 context.stack_machine.reset();
1750 const value = try context.stack_machine.run(
1751 expression,
1752 context.allocator,
1753 expression_context,
1754 context.cfa,
1755 );
1756
1757 if (value) |v| {
1758 if (v != .generic) return error.InvalidExpressionValue;
1759 break :blk v.generic;
1760 } else return error.NoExpressionValue;
1761 },
1762 else => return error.InvalidCFARule,
1763 };
1764
1765 if (!context.isValidMemory(context.cfa.?)) return error.InvalidCFA;
1766 expression_context.cfa = context.cfa;
1767
1768 // Buffering the modifications is done because copying the thread context is not portable,
1769 // some implementations (ie. darwin) use internal pointers to the mcontext.
1770 var arena = std.heap.ArenaAllocator.init(context.allocator);
1771 defer arena.deinit();
1772 const update_allocator = arena.allocator();
1773
1774 const RegisterUpdate = struct {
1775 // Backed by thread_context
1776 dest: []u8,
1777 // Backed by arena
1778 src: []const u8,
1779 prev: ?*@This(),
1780 };
1781
1782 var update_tail: ?*RegisterUpdate = null;
1783 var has_return_address = true;
1784 for (context.vm.rowColumns(row)) |column| {
1785 if (column.register) |register| {
1786 if (register == cie.return_address_register) {
1787 has_return_address = column.rule != .undefined;
1788 }
1789
1790 const dest = try abi.regBytes(context.thread_context, register, context.reg_context);
1791 const src = try update_allocator.alloc(u8, dest.len);
1792
1793 const prev = update_tail;
1794 update_tail = try update_allocator.create(RegisterUpdate);
1795 update_tail.?.* = .{
1796 .dest = dest,
1797 .src = src,
1798 .prev = prev,
1799 };
1800
1801 try column.resolveValue(
1802 context,
1803 expression_context,
1804 src,
1805 );
1806 }
1807 }
1808
1809 // On all implemented architectures, the CFA is defined as being the previous frame's SP
1810 (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?;
1811
1812 while (update_tail) |tail| {
1813 @memcpy(tail.dest, tail.src);
1814 update_tail = tail.prev;
1815 }
1816
1817 if (has_return_address) {
1818 context.pc = abi.stripInstructionPtrAuthCode(mem.readIntSliceNative(usize, try abi.regBytes(
1819 context.thread_context,
1820 cie.return_address_register,
1821 context.reg_context,
1822 )));
1823 } else {
1824 context.pc = 0;
1825 }
1826
1827 (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), context.reg_context)).* = context.pc;
1828
1829 // The call instruction will have pushed the address of the instruction that follows the call as the return address.
1830 // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in
1831 // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up
1832 // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this,
1833 // we subtract one so that the next lookup is guaranteed to land inside the
1834 //
1835 // The exception to this rule is signal frames, where we return execution would be returned to the instruction
1836 // that triggered the handler.
1837 const return_address = context.pc;
1838 if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1;
1839
1840 return return_address;
1841 }
1842};
1843
1844/// Returns the DWARF register number for an x86_64 register number found in compact unwind info
1845fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {
1846 return switch (unwind_reg_number) {
1847 1 => 3, // RBX
1848 2 => 12, // R12
1849 3 => 13, // R13
1850 4 => 14, // R14
1851 5 => 15, // R15
1852 6 => 6, // RBP
1853 else => error.InvalidUnwindRegisterNumber,
1854 };
1855}
1856
1857const macho = std.macho;
1858
1859/// Unwind a frame using MachO compact unwind info (from __unwind_info).
1860/// If the compact encoding can't encode a way to unwind a frame, it will
1861/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
1862pub fn unwindFrameMachO(context: *UnwindContext, unwind_info: []const u8, eh_frame: ?[]const u8, module_base_address: usize) !usize {
1863 const header = mem.bytesAsValue(
1864 macho.unwind_info_section_header,
1865 unwind_info[0..@sizeOf(macho.unwind_info_section_header)],
1866 );
1867 const indices = mem.bytesAsSlice(
1868 macho.unwind_info_section_header_index_entry,
1869 unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)],
1870 );
1871 if (indices.len == 0) return error.MissingUnwindInfo;
1872
1873 const mapped_pc = context.pc - module_base_address;
1874 const second_level_index = blk: {
1875 var left: usize = 0;
1876 var len: usize = indices.len;
1877
1878 while (len > 1) {
1879 const mid = left + len / 2;
1880 const offset = indices[mid].functionOffset;
1881 if (mapped_pc < offset) {
1882 len /= 2;
1883 } else {
1884 left = mid;
1885 if (mapped_pc == offset) break;
1886 len -= len / 2;
1887 }
1888 }
1889
1890 // Last index is a sentinel containing the highest address as its functionOffset
1891 if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo;
1892 break :blk &indices[left];
1893 };
1894
1895 const common_encodings = mem.bytesAsSlice(
1896 macho.compact_unwind_encoding_t,
1897 unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)],
1898 );
1899
1900 const start_offset = second_level_index.secondLevelPagesSectionOffset;
1901 const kind = mem.bytesAsValue(
1902 macho.UNWIND_SECOND_LEVEL,
1903 unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)],
1904 );
1905
1906 const entry: struct {
1907 function_offset: usize,
1908 raw_encoding: u32,
1909 } = switch (kind.*) {
1910 .REGULAR => blk: {
1911 const page_header = mem.bytesAsValue(
1912 macho.unwind_info_regular_second_level_page_header,
1913 unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)],
1914 );
1915
1916 const entries = mem.bytesAsSlice(
1917 macho.unwind_info_regular_second_level_entry,
1918 unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)],
1919 );
1920 if (entries.len == 0) return error.InvalidUnwindInfo;
1921
1922 var left: usize = 0;
1923 var len: usize = entries.len;
1924 while (len > 1) {
1925 const mid = left + len / 2;
1926 const offset = entries[mid].functionOffset;
1927 if (mapped_pc < offset) {
1928 len /= 2;
1929 } else {
1930 left = mid;
1931 if (mapped_pc == offset) break;
1932 len -= len / 2;
1933 }
1934 }
1935
1936 break :blk .{
1937 .function_offset = entries[left].functionOffset,
1938 .raw_encoding = entries[left].encoding,
1939 };
1940 },
1941 .COMPRESSED => blk: {
1942 const page_header = mem.bytesAsValue(
1943 macho.unwind_info_compressed_second_level_page_header,
1944 unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)],
1945 );
1946
1947 const entries = mem.bytesAsSlice(
1948 macho.UnwindInfoCompressedEntry,
1949 unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)],
1950 );
1951 if (entries.len == 0) return error.InvalidUnwindInfo;
1952
1953 var left: usize = 0;
1954 var len: usize = entries.len;
1955 while (len > 1) {
1956 const mid = left + len / 2;
1957 const offset = second_level_index.functionOffset + entries[mid].funcOffset;
1958 if (mapped_pc < offset) {
1959 len /= 2;
1960 } else {
1961 left = mid;
1962 if (mapped_pc == offset) break;
1963 len -= len / 2;
1964 }
1965 }
1966
1967 const entry = entries[left];
1968 const function_offset = second_level_index.functionOffset + entry.funcOffset;
1969 if (entry.encodingIndex < header.commonEncodingsArrayCount) {
1970 if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo;
1971 break :blk .{
1972 .function_offset = function_offset,
1973 .raw_encoding = common_encodings[entry.encodingIndex],
1974 };
1975 } else {
1976 const local_index = try std.math.sub(
1977 u8,
1978 entry.encodingIndex,
1979 std.math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo,
1980 );
1981 const local_encodings = mem.bytesAsSlice(
1982 macho.compact_unwind_encoding_t,
1983 unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)],
1984 );
1985 if (local_index >= local_encodings.len) return error.InvalidUnwindInfo;
1986 break :blk .{
1987 .function_offset = function_offset,
1988 .raw_encoding = local_encodings[local_index],
1989 };
1990 }
1991 },
1992 else => return error.InvalidUnwindInfo,
1993 };
1994
1995 if (entry.raw_encoding == 0) return error.NoUnwindInfo;
1996 const reg_context = abi.RegisterContext{
1997 .eh_frame = false,
1998 .is_macho = true,
1999 };
2000
2001 const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
2002 const new_ip = switch (builtin.cpu.arch) {
2003 .x86_64 => switch (encoding.mode.x86_64) {
2004 .OLD => return error.UnimplementedUnwindEncoding,
2005 .RBP_FRAME => blk: {
2006 const regs: [5]u3 = .{
2007 encoding.value.x86_64.frame.reg0,
2008 encoding.value.x86_64.frame.reg1,
2009 encoding.value.x86_64.frame.reg2,
2010 encoding.value.x86_64.frame.reg3,
2011 encoding.value.x86_64.frame.reg4,
2012 };
2013
2014 const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize);
2015 var max_reg: usize = 0;
2016 inline for (regs, 0..) |reg, i| {
2017 if (reg > 0) max_reg = i;
2018 }
2019
2020 const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*;
2021 const new_sp = fp + 2 * @sizeOf(usize);
2022
2023 // Verify the stack range we're about to read register values from
2024 if (!context.isValidMemory(new_sp) or !context.isValidMemory(fp - frame_offset + max_reg * @sizeOf(usize))) return error.InvalidUnwindInfo;
2025
2026 const ip_ptr = fp + @sizeOf(usize);
2027 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
2028 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
2029
2030 (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp;
2031 (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
2032 (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
2033
2034 for (regs, 0..) |reg, i| {
2035 if (reg == 0) continue;
2036 const addr = fp - frame_offset + i * @sizeOf(usize);
2037 const reg_number = try compactUnwindToDwarfRegNumber(reg);
2038 (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
2039 }
2040
2041 break :blk new_ip;
2042 },
2043 .STACK_IMMD,
2044 .STACK_IND,
2045 => blk: {
2046 const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*;
2047 const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD)
2048 @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize)
2049 else stack_size: {
2050 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
2051 const sub_offset_addr =
2052 module_base_address +
2053 entry.function_offset +
2054 encoding.value.x86_64.frameless.stack.indirect.sub_offset;
2055 if (!context.isValidMemory(sub_offset_addr)) return error.InvalidUnwindInfo;
2056
2057 // `sub_offset_addr` points to the offset of the literal within the instruction
2058 const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
2059 break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust);
2060 };
2061
2062 // Decode the Lehmer-coded sequence of registers.
2063 // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
2064
2065 // Decode the variable-based permutation number into its digits. Each digit represents
2066 // an index into the list of register numbers that weren't yet used in the sequence at
2067 // the time the digit was added.
2068 const reg_count = encoding.value.x86_64.frameless.stack_reg_count;
2069 const ip_ptr = if (reg_count > 0) reg_blk: {
2070 var digits: [6]u3 = undefined;
2071 var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation;
2072 var base: usize = 2;
2073 for (0..reg_count) |i| {
2074 const div = accumulator / base;
2075 digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
2076 accumulator = div;
2077 base += 1;
2078 }
2079
2080 const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 };
2081 var registers: [reg_numbers.len]u3 = undefined;
2082 var used_indices = [_]bool{false} ** reg_numbers.len;
2083 for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
2084 var unused_count: u8 = 0;
2085 const unused_index = for (used_indices, 0..) |used, index| {
2086 if (!used) {
2087 if (target_unused_index == unused_count) break index;
2088 unused_count += 1;
2089 }
2090 } else unreachable;
2091
2092 registers[i] = reg_numbers[unused_index];
2093 used_indices[unused_index] = true;
2094 }
2095
2096 var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
2097 if (!context.isValidMemory(reg_addr)) return error.InvalidUnwindInfo;
2098 for (0..reg_count) |i| {
2099 const reg_number = try compactUnwindToDwarfRegNumber(registers[i]);
2100 (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
2101 reg_addr += @sizeOf(usize);
2102 }
2103
2104 break :reg_blk reg_addr;
2105 } else sp + stack_size - @sizeOf(usize);
2106
2107 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
2108 const new_sp = ip_ptr + @sizeOf(usize);
2109 if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo;
2110
2111 (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
2112 (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
2113
2114 break :blk new_ip;
2115 },
2116 .DWARF => {
2117 return unwindFrameMachODwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));
2118 },
2119 },
2120 .aarch64 => switch (encoding.mode.arm64) {
2121 .OLD => return error.UnimplementedUnwindEncoding,
2122 .FRAMELESS => blk: {
2123 const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*;
2124 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
2125 const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*;
2126 if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo;
2127 (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
2128 break :blk new_ip;
2129 },
2130 .DWARF => {
2131 return unwindFrameMachODwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));
2132 },
2133 .FRAME => blk: {
2134 const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*;
2135 const new_sp = fp + 16;
2136 const ip_ptr = fp + @sizeOf(usize);
2137
2138 const num_restored_pairs: usize =
2139 @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) +
2140 @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs)));
2141 const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize);
2142
2143 if (!context.isValidMemory(new_sp) or !context.isValidMemory(min_reg_addr)) return error.InvalidUnwindInfo;
2144
2145 var reg_addr = fp - @sizeOf(usize);
2146 inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| {
2147 if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) {
2148 (try abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
2149 reg_addr += @sizeOf(usize);
2150 (try abi.regValueNative(usize, context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
2151 reg_addr += @sizeOf(usize);
2152 }
2153 }
2154
2155 inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| {
2156 if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) {
2157 // Only the lower half of the 128-bit V registers are restored during unwinding
2158 @memcpy(
2159 try abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context),
2160 mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
2161 );
2162 reg_addr += @sizeOf(usize);
2163 @memcpy(
2164 try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context),
2165 mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
2166 );
2167 reg_addr += @sizeOf(usize);
2168 }
2169 }
2170
2171 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
2172 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
2173
2174 (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp;
2175 (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
2176
2177 break :blk new_ip;
2178 },
2179 },
2180 else => return error.UnimplementedArch,
2181 };
2182
2183 context.pc = abi.stripInstructionPtrAuthCode(new_ip);
2184 if (context.pc > 0) context.pc -= 1;
2185 return new_ip;
2186}
2187
2188fn unwindFrameMachODwarf(context: *UnwindContext, eh_frame: []const u8, fde_offset: usize) !usize {
2189 var di = DwarfInfo{
2190 .endian = builtin.cpu.arch.endian(),
2191 .is_macho = true,
2192 };
2193 defer di.deinit(context.allocator);
2194
2195 di.sections[@intFromEnum(DwarfSection.eh_frame)] = .{
2196 .data = eh_frame,
2197 .owned = false,
2198 };
2199
2200 return di.unwindFrame(context, fde_offset);
2201}
2202
2203pub const UnwindContext = struct {
2204 allocator: mem.Allocator,
2205 cfa: ?usize,
2206 pc: usize,
2207 thread_context: *debug.ThreadContext,
2208 reg_context: abi.RegisterContext,
2209 isValidMemory: *const fn (address: usize) bool,
2210 vm: call_frame.VirtualMachine = .{},
2211 stack_machine: expressions.StackMachine(.{ .call_frame_context = true }) = .{},
2212
2213 pub fn init(allocator: mem.Allocator, thread_context: *const debug.ThreadContext, isValidMemory: *const fn (address: usize) bool) !UnwindContext {
2214 const pc = abi.stripInstructionPtrAuthCode((try abi.regValueNative(usize, thread_context, abi.ipRegNum(), null)).*);
2215
2216 const context_copy = try allocator.create(debug.ThreadContext);
2217 debug.copyContext(thread_context, context_copy);
2218
2219 return .{
2220 .allocator = allocator,
2221 .cfa = null,
2222 .pc = pc,
2223 .thread_context = context_copy,
2224 .reg_context = undefined,
2225 .isValidMemory = isValidMemory,
2226 };
2227 }
2228
2229 pub fn deinit(self: *UnwindContext) void {
2230 self.vm.deinit(self.allocator);
2231 self.stack_machine.deinit(self.allocator);
2232 self.allocator.destroy(self.thread_context);
2233 }
2234
2235 pub fn getFp(self: *const UnwindContext) !usize {
2236 return (try abi.regValueNative(usize, self.thread_context, abi.fpRegNum(self.reg_context), self.reg_context)).*;
2237 }
14512238};
14522239
14532240/// Initialize DWARF info. The caller has the responsibility to initialize most
1454/// the DwarfInfo fields before calling.
2241/// the DwarfInfo fields before calling. `binary_mem` is the raw bytes of the
2242/// main binary file (not the secondary debug info file).
14552243pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: mem.Allocator) !void {
14562244 try di.scanAllFunctions(allocator);
14572245 try di.scanAllCompileUnits(allocator);
......@@ -1477,3 +2265,561 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
14772265 const last = mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return badDwarf();
14782266 return str[casted_offset..last :0];
14792267}
2268
2269const EhPointerContext = struct {
2270 // The address of the pointer field itself
2271 pc_rel_base: u64,
2272
2273 // Whether or not to follow indirect pointers. This should only be
2274 // used when decoding pointers at runtime using the current process's
2275 // debug info
2276 follow_indirect: bool,
2277
2278 // These relative addressing modes are only used in specific cases, and
2279 // might not be available / required in all parsing contexts
2280 data_rel_base: ?u64 = null,
2281 text_rel_base: ?u64 = null,
2282 function_rel_base: ?u64 = null,
2283};
2284
2285fn readEhPointer(reader: anytype, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext, endian: std.builtin.Endian) !?u64 {
2286 if (enc == EH.PE.omit) return null;
2287
2288 const value: union(enum) {
2289 signed: i64,
2290 unsigned: u64,
2291 } = switch (enc & EH.PE.type_mask) {
2292 EH.PE.absptr => .{
2293 .unsigned = switch (addr_size_bytes) {
2294 2 => try reader.readInt(u16, endian),
2295 4 => try reader.readInt(u32, endian),
2296 8 => try reader.readInt(u64, endian),
2297 else => return error.InvalidAddrSize,
2298 },
2299 },
2300 EH.PE.uleb128 => .{ .unsigned = try leb.readULEB128(u64, reader) },
2301 EH.PE.udata2 => .{ .unsigned = try reader.readInt(u16, endian) },
2302 EH.PE.udata4 => .{ .unsigned = try reader.readInt(u32, endian) },
2303 EH.PE.udata8 => .{ .unsigned = try reader.readInt(u64, endian) },
2304 EH.PE.sleb128 => .{ .signed = try leb.readILEB128(i64, reader) },
2305 EH.PE.sdata2 => .{ .signed = try reader.readInt(i16, endian) },
2306 EH.PE.sdata4 => .{ .signed = try reader.readInt(i32, endian) },
2307 EH.PE.sdata8 => .{ .signed = try reader.readInt(i64, endian) },
2308 else => return badDwarf(),
2309 };
2310
2311 var base = switch (enc & EH.PE.rel_mask) {
2312 EH.PE.pcrel => ctx.pc_rel_base,
2313 EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified,
2314 EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified,
2315 EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified,
2316 else => null,
2317 };
2318
2319 const ptr: u64 = if (base) |b| switch (value) {
2320 .signed => |s| @intCast(try math.add(i64, s, @as(i64, @intCast(b)))),
2321 // absptr can actually contain signed values in some cases (aarch64 MachO)
2322 .unsigned => |u| u +% b,
2323 } else switch (value) {
2324 .signed => |s| @as(u64, @intCast(s)),
2325 .unsigned => |u| u,
2326 };
2327
2328 if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) {
2329 if (@sizeOf(usize) != addr_size_bytes) {
2330 // See the documentation for `follow_indirect`
2331 return error.NonNativeIndirection;
2332 }
2333
2334 const native_ptr = math.cast(usize, ptr) orelse return error.PointerOverflow;
2335 return switch (addr_size_bytes) {
2336 2, 4, 8 => return @as(*const usize, @ptrFromInt(native_ptr)).*,
2337 else => return error.UnsupportedAddrSize,
2338 };
2339 } else {
2340 return ptr;
2341 }
2342}
2343
2344/// This represents the decoded .eh_frame_hdr header
2345pub const ExceptionFrameHeader = struct {
2346 eh_frame_ptr: usize,
2347 table_enc: u8,
2348 fde_count: usize,
2349 entries: []const u8,
2350
2351 pub fn entrySize(table_enc: u8) !u8 {
2352 return switch (table_enc & EH.PE.type_mask) {
2353 EH.PE.udata2,
2354 EH.PE.sdata2,
2355 => 4,
2356 EH.PE.udata4,
2357 EH.PE.sdata4,
2358 => 8,
2359 EH.PE.udata8,
2360 EH.PE.sdata8,
2361 => 16,
2362 // This is a binary search table, so all entries must be the same length
2363 else => return badDwarf(),
2364 };
2365 }
2366
2367 fn isValidPtr(
2368 self: ExceptionFrameHeader,
2369 ptr: usize,
2370 isValidMemory: *const fn (address: usize) bool,
2371 eh_frame_len: ?usize,
2372 ) bool {
2373 if (eh_frame_len) |len| {
2374 return ptr >= self.eh_frame_ptr and ptr < self.eh_frame_ptr + len;
2375 } else {
2376 return isValidMemory(ptr);
2377 }
2378 }
2379
2380 /// Find an entry by binary searching the eh_frame_hdr section.
2381 ///
2382 /// Since the length of the eh_frame section (`eh_frame_len`) may not be known by the caller,
2383 /// `isValidMemory` will be called before accessing any memory referenced by
2384 /// the header entries. If `eh_frame_len` is provided, then these checks can be skipped.
2385 pub fn findEntry(
2386 self: ExceptionFrameHeader,
2387 isValidMemory: *const fn (address: usize) bool,
2388 eh_frame_len: ?usize,
2389 eh_frame_hdr_ptr: usize,
2390 pc: usize,
2391 cie: *CommonInformationEntry,
2392 fde: *FrameDescriptionEntry,
2393 ) !void {
2394 const entry_size = try entrySize(self.table_enc);
2395
2396 var left: usize = 0;
2397 var len: usize = self.fde_count;
2398
2399 var stream = io.fixedBufferStream(self.entries);
2400 const reader = stream.reader();
2401
2402 while (len > 1) {
2403 const mid = left + len / 2;
2404
2405 try stream.seekTo(mid * entry_size);
2406 const pc_begin = try readEhPointer(reader, self.table_enc, @sizeOf(usize), .{
2407 .pc_rel_base = @intFromPtr(&self.entries[stream.pos]),
2408 .follow_indirect = true,
2409 .data_rel_base = eh_frame_hdr_ptr,
2410 }, builtin.cpu.arch.endian()) orelse return badDwarf();
2411
2412 if (pc < pc_begin) {
2413 len /= 2;
2414 } else {
2415 left = mid;
2416 if (pc == pc_begin) break;
2417 len -= len / 2;
2418 }
2419 }
2420
2421 if (len == 0) return badDwarf();
2422 try stream.seekTo(left * entry_size);
2423
2424 // Read past the pc_begin field of the entry
2425 _ = try readEhPointer(reader, self.table_enc, @sizeOf(usize), .{
2426 .pc_rel_base = @intFromPtr(&self.entries[stream.pos]),
2427 .follow_indirect = true,
2428 .data_rel_base = eh_frame_hdr_ptr,
2429 }, builtin.cpu.arch.endian()) orelse return badDwarf();
2430
2431 const fde_ptr = math.cast(usize, try readEhPointer(reader, self.table_enc, @sizeOf(usize), .{
2432 .pc_rel_base = @intFromPtr(&self.entries[stream.pos]),
2433 .follow_indirect = true,
2434 .data_rel_base = eh_frame_hdr_ptr,
2435 }, builtin.cpu.arch.endian()) orelse return badDwarf()) orelse return badDwarf();
2436
2437 // Verify the length fields of the FDE header are readable
2438 if (!self.isValidPtr(fde_ptr, isValidMemory, eh_frame_len) or fde_ptr < self.eh_frame_ptr) return badDwarf();
2439
2440 var fde_entry_header_len: usize = 4;
2441 if (!self.isValidPtr(fde_ptr + 3, isValidMemory, eh_frame_len)) return badDwarf();
2442 if (self.isValidPtr(fde_ptr + 11, isValidMemory, eh_frame_len)) fde_entry_header_len = 12;
2443
2444 // Even if eh_frame_len is not specified, all ranges accssed are checked by isValidPtr
2445 const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse math.maxInt(u32)];
2446
2447 const fde_offset = fde_ptr - self.eh_frame_ptr;
2448 var eh_frame_stream = io.fixedBufferStream(eh_frame);
2449 try eh_frame_stream.seekTo(fde_offset);
2450
2451 const fde_entry_header = try EntryHeader.read(&eh_frame_stream, .eh_frame, builtin.cpu.arch.endian());
2452 if (!self.isValidPtr(@intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), isValidMemory, eh_frame_len)) return badDwarf();
2453 if (fde_entry_header.type != .fde) return badDwarf();
2454
2455 // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable
2456 const cie_offset = fde_entry_header.type.fde;
2457 try eh_frame_stream.seekTo(cie_offset);
2458 const cie_entry_header = try EntryHeader.read(&eh_frame_stream, .eh_frame, builtin.cpu.arch.endian());
2459 if (!self.isValidPtr(@intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), isValidMemory, eh_frame_len)) return badDwarf();
2460 if (cie_entry_header.type != .cie) return badDwarf();
2461
2462 cie.* = try CommonInformationEntry.parse(
2463 cie_entry_header.entry_bytes,
2464 0,
2465 true,
2466 cie_entry_header.is_64,
2467 .eh_frame,
2468 cie_entry_header.length_offset,
2469 @sizeOf(usize),
2470 builtin.cpu.arch.endian(),
2471 );
2472
2473 fde.* = try FrameDescriptionEntry.parse(
2474 fde_entry_header.entry_bytes,
2475 0,
2476 true,
2477 cie.*,
2478 @sizeOf(usize),
2479 builtin.cpu.arch.endian(),
2480 );
2481 }
2482};
2483
2484pub const EntryHeader = struct {
2485 /// Offset of the length field in the backing buffer
2486 length_offset: usize,
2487 is_64: bool,
2488 type: union(enum) {
2489 cie,
2490 /// Value is the offset of the corresponding CIE
2491 fde: u64,
2492 terminator: void,
2493 },
2494 /// The entry's contents, not including the ID field
2495 entry_bytes: []const u8,
2496
2497 /// Reads a header for either an FDE or a CIE, then advances the stream to the position after the trailing structure.
2498 /// `stream` must be a stream backed by either the .eh_frame or .debug_frame sections.
2499 pub fn read(stream: *std.io.FixedBufferStream([]const u8), dwarf_section: DwarfSection, endian: std.builtin.Endian) !EntryHeader {
2500 assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame);
2501
2502 const reader = stream.reader();
2503 const length_offset = stream.pos;
2504
2505 var is_64: bool = undefined;
2506 const length = math.cast(usize, try readUnitLength(reader, endian, &is_64)) orelse return badDwarf();
2507 if (length == 0) return .{
2508 .length_offset = length_offset,
2509 .is_64 = is_64,
2510 .type = .{ .terminator = {} },
2511 .entry_bytes = &.{},
2512 };
2513
2514 const id_len = @as(u8, if (is_64) 8 else 4);
2515 const id = if (is_64) try reader.readInt(u64, endian) else try reader.readInt(u32, endian);
2516 const entry_bytes = stream.buffer[stream.pos..][0 .. length - id_len];
2517 const cie_id: u64 = switch (dwarf_section) {
2518 .eh_frame => CommonInformationEntry.eh_id,
2519 .debug_frame => if (is_64) CommonInformationEntry.dwarf64_id else CommonInformationEntry.dwarf32_id,
2520 else => unreachable,
2521 };
2522
2523 const result = EntryHeader{
2524 .length_offset = length_offset,
2525 .is_64 = is_64,
2526 .type = if (id == cie_id) .{ .cie = {} } else .{
2527 .fde = switch (dwarf_section) {
2528 .eh_frame => try std.math.sub(u64, stream.pos - id_len, id),
2529 .debug_frame => id,
2530 else => unreachable,
2531 },
2532 },
2533 .entry_bytes = entry_bytes,
2534 };
2535
2536 stream.pos += entry_bytes.len;
2537 return result;
2538 }
2539
2540 /// The length of the entry including the ID field, but not the length field itself
2541 pub fn entryLength(self: EntryHeader) usize {
2542 return self.entry_bytes.len + @as(u8, if (self.is_64) 8 else 4);
2543 }
2544};
2545
2546pub const CommonInformationEntry = struct {
2547 // Used in .eh_frame
2548 pub const eh_id = 0;
2549
2550 // Used in .debug_frame (DWARF32)
2551 pub const dwarf32_id = math.maxInt(u32);
2552
2553 // Used in .debug_frame (DWARF64)
2554 pub const dwarf64_id = math.maxInt(u64);
2555
2556 // Offset of the length field of this entry in the eh_frame section.
2557 // This is the key that FDEs use to reference CIEs.
2558 length_offset: u64,
2559 version: u8,
2560 address_size: u8,
2561 is_64: bool,
2562
2563 // Only present in version 4
2564 segment_selector_size: ?u8,
2565
2566 code_alignment_factor: u32,
2567 data_alignment_factor: i32,
2568 return_address_register: u8,
2569
2570 aug_str: []const u8,
2571 aug_data: []const u8,
2572 lsda_pointer_enc: u8,
2573 personality_enc: ?u8,
2574 personality_routine_pointer: ?u64,
2575 fde_pointer_enc: u8,
2576 initial_instructions: []const u8,
2577
2578 pub fn isSignalFrame(self: CommonInformationEntry) bool {
2579 for (self.aug_str) |c| if (c == 'S') return true;
2580 return false;
2581 }
2582
2583 pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool {
2584 for (self.aug_str) |c| if (c == 'B') return true;
2585 return false;
2586 }
2587
2588 pub fn mteTaggedFrame(self: CommonInformationEntry) bool {
2589 for (self.aug_str) |c| if (c == 'G') return true;
2590 return false;
2591 }
2592
2593 /// This function expects to read the CIE starting with the version field.
2594 /// The returned struct references memory backed by cie_bytes.
2595 ///
2596 /// See the FrameDescriptionEntry.parse documentation for the description
2597 /// of `pc_rel_offset` and `is_runtime`.
2598 ///
2599 /// `length_offset` specifies the offset of this CIE's length field in the
2600 /// .eh_frame / .debug_frame section.
2601 pub fn parse(
2602 cie_bytes: []const u8,
2603 pc_rel_offset: i64,
2604 is_runtime: bool,
2605 is_64: bool,
2606 dwarf_section: DwarfSection,
2607 length_offset: u64,
2608 addr_size_bytes: u8,
2609 endian: std.builtin.Endian,
2610 ) !CommonInformationEntry {
2611 if (addr_size_bytes > 8) return error.UnsupportedAddrSize;
2612
2613 var stream = io.fixedBufferStream(cie_bytes);
2614 const reader = stream.reader();
2615
2616 const version = try reader.readByte();
2617 switch (dwarf_section) {
2618 .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion,
2619 .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion,
2620 else => return error.UnsupportedDwarfSection,
2621 }
2622
2623 var has_eh_data = false;
2624 var has_aug_data = false;
2625
2626 var aug_str_len: usize = 0;
2627 var aug_str_start = stream.pos;
2628 var aug_byte = try reader.readByte();
2629 while (aug_byte != 0) : (aug_byte = try reader.readByte()) {
2630 switch (aug_byte) {
2631 'z' => {
2632 if (aug_str_len != 0) return badDwarf();
2633 has_aug_data = true;
2634 },
2635 'e' => {
2636 if (has_aug_data or aug_str_len != 0) return badDwarf();
2637 if (try reader.readByte() != 'h') return badDwarf();
2638 has_eh_data = true;
2639 },
2640 else => if (has_eh_data) return badDwarf(),
2641 }
2642
2643 aug_str_len += 1;
2644 }
2645
2646 if (has_eh_data) {
2647 // legacy data created by older versions of gcc - unsupported here
2648 for (0..addr_size_bytes) |_| _ = try reader.readByte();
2649 }
2650
2651 const address_size = if (version == 4) try reader.readByte() else addr_size_bytes;
2652 const segment_selector_size = if (version == 4) try reader.readByte() else null;
2653
2654 const code_alignment_factor = try leb.readULEB128(u32, reader);
2655 const data_alignment_factor = try leb.readILEB128(i32, reader);
2656 const return_address_register = if (version == 1) try reader.readByte() else try leb.readULEB128(u8, reader);
2657
2658 var lsda_pointer_enc: u8 = EH.PE.omit;
2659 var personality_enc: ?u8 = null;
2660 var personality_routine_pointer: ?u64 = null;
2661 var fde_pointer_enc: u8 = EH.PE.absptr;
2662
2663 var aug_data: []const u8 = &[_]u8{};
2664 const aug_str = if (has_aug_data) blk: {
2665 const aug_data_len = try leb.readULEB128(usize, reader);
2666 const aug_data_start = stream.pos;
2667 aug_data = cie_bytes[aug_data_start..][0..aug_data_len];
2668
2669 const aug_str = cie_bytes[aug_str_start..][0..aug_str_len];
2670 for (aug_str[1..]) |byte| {
2671 switch (byte) {
2672 'L' => {
2673 lsda_pointer_enc = try reader.readByte();
2674 },
2675 'P' => {
2676 personality_enc = try reader.readByte();
2677 personality_routine_pointer = try readEhPointer(
2678 reader,
2679 personality_enc.?,
2680 addr_size_bytes,
2681 .{
2682 .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[stream.pos]), pc_rel_offset),
2683 .follow_indirect = is_runtime,
2684 },
2685 endian,
2686 );
2687 },
2688 'R' => {
2689 fde_pointer_enc = try reader.readByte();
2690 },
2691 'S', 'B', 'G' => {},
2692 else => return badDwarf(),
2693 }
2694 }
2695
2696 // aug_data_len can include padding so the CIE ends on an address boundary
2697 try stream.seekTo(aug_data_start + aug_data_len);
2698 break :blk aug_str;
2699 } else &[_]u8{};
2700
2701 const initial_instructions = cie_bytes[stream.pos..];
2702 return .{
2703 .length_offset = length_offset,
2704 .version = version,
2705 .address_size = address_size,
2706 .is_64 = is_64,
2707 .segment_selector_size = segment_selector_size,
2708 .code_alignment_factor = code_alignment_factor,
2709 .data_alignment_factor = data_alignment_factor,
2710 .return_address_register = return_address_register,
2711 .aug_str = aug_str,
2712 .aug_data = aug_data,
2713 .lsda_pointer_enc = lsda_pointer_enc,
2714 .personality_enc = personality_enc,
2715 .personality_routine_pointer = personality_routine_pointer,
2716 .fde_pointer_enc = fde_pointer_enc,
2717 .initial_instructions = initial_instructions,
2718 };
2719 }
2720};
2721
2722pub const FrameDescriptionEntry = struct {
2723 // Offset into eh_frame where the CIE for this FDE is stored
2724 cie_length_offset: u64,
2725
2726 pc_begin: u64,
2727 pc_range: u64,
2728 lsda_pointer: ?u64,
2729 aug_data: []const u8,
2730 instructions: []const u8,
2731
2732 /// This function expects to read the FDE starting at the PC Begin field.
2733 /// The returned struct references memory backed by `fde_bytes`.
2734 ///
2735 /// `pc_rel_offset` specifies an offset to be applied to pc_rel_base values
2736 /// used when decoding pointers. This should be set to zero if fde_bytes is
2737 /// backed by the memory of a .eh_frame / .debug_frame section in the running executable.
2738 /// Otherwise, it should be the relative offset to translate addresses from
2739 /// where the section is currently stored in memory, to where it *would* be
2740 /// stored at runtime: section base addr - backing data base ptr.
2741 ///
2742 /// Similarly, `is_runtime` specifies this function is being called on a runtime
2743 /// section, and so indirect pointers can be followed.
2744 pub fn parse(
2745 fde_bytes: []const u8,
2746 pc_rel_offset: i64,
2747 is_runtime: bool,
2748 cie: CommonInformationEntry,
2749 addr_size_bytes: u8,
2750 endian: std.builtin.Endian,
2751 ) !FrameDescriptionEntry {
2752 if (addr_size_bytes > 8) return error.InvalidAddrSize;
2753
2754 var stream = io.fixedBufferStream(fde_bytes);
2755 const reader = stream.reader();
2756
2757 const pc_begin = try readEhPointer(
2758 reader,
2759 cie.fde_pointer_enc,
2760 addr_size_bytes,
2761 .{
2762 .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[stream.pos]), pc_rel_offset),
2763 .follow_indirect = is_runtime,
2764 },
2765 endian,
2766 ) orelse return badDwarf();
2767
2768 const pc_range = try readEhPointer(
2769 reader,
2770 cie.fde_pointer_enc,
2771 addr_size_bytes,
2772 .{
2773 .pc_rel_base = 0,
2774 .follow_indirect = false,
2775 },
2776 endian,
2777 ) orelse return badDwarf();
2778
2779 var aug_data: []const u8 = &[_]u8{};
2780 const lsda_pointer = if (cie.aug_str.len > 0) blk: {
2781 const aug_data_len = try leb.readULEB128(usize, reader);
2782 const aug_data_start = stream.pos;
2783 aug_data = fde_bytes[aug_data_start..][0..aug_data_len];
2784
2785 const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit)
2786 try readEhPointer(
2787 reader,
2788 cie.lsda_pointer_enc,
2789 addr_size_bytes,
2790 .{
2791 .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[stream.pos]), pc_rel_offset),
2792 .follow_indirect = is_runtime,
2793 },
2794 endian,
2795 )
2796 else
2797 null;
2798
2799 try stream.seekTo(aug_data_start + aug_data_len);
2800 break :blk lsda_pointer;
2801 } else null;
2802
2803 const instructions = fde_bytes[stream.pos..];
2804 return .{
2805 .cie_length_offset = cie.length_offset,
2806 .pc_begin = pc_begin,
2807 .pc_range = pc_range,
2808 .lsda_pointer = lsda_pointer,
2809 .aug_data = aug_data,
2810 .instructions = instructions,
2811 };
2812 }
2813};
2814
2815fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize {
2816 if (pc_rel_offset < 0) {
2817 return math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset)));
2818 } else {
2819 return math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset)));
2820 }
2821}
2822
2823test {
2824 std.testing.refAllDecls(@This());
2825}
lib/std/dwarf/EH.zig created+27
......@@ -0,0 +1,27 @@
1pub const PE = struct {
2 pub const absptr = 0x00;
3
4 pub const size_mask = 0x7;
5 pub const sign_mask = 0x8;
6 pub const type_mask = size_mask | sign_mask;
7
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;
16
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;
27};
lib/std/dwarf/abi.zig created+387
......@@ -0,0 +1,387 @@
1const builtin = @import("builtin");
2const std = @import("../std.zig");
3const os = std.os;
4const mem = std.mem;
5
6pub fn isSupportedArch(arch: std.Target.Cpu.Arch) bool {
7 return switch (arch) {
8 .x86,
9 .x86_64,
10 .arm,
11 .aarch64,
12 => true,
13 else => false,
14 };
15}
16
17pub fn ipRegNum() u8 {
18 return switch (builtin.cpu.arch) {
19 .x86 => 8,
20 .x86_64 => 16,
21 .arm => 15,
22 .aarch64 => 32,
23 else => unreachable,
24 };
25}
26
27pub fn fpRegNum(reg_context: RegisterContext) u8 {
28 return switch (builtin.cpu.arch) {
29 // GCC on OS X historicaly did the opposite of ELF for these registers (only in .eh_frame), and that is now the convention for MachO
30 .x86 => if (reg_context.eh_frame and reg_context.is_macho) 4 else 5,
31 .x86_64 => 6,
32 .arm => 11,
33 .aarch64 => 29,
34 else => unreachable,
35 };
36}
37
38pub fn spRegNum(reg_context: RegisterContext) u8 {
39 return switch (builtin.cpu.arch) {
40 .x86 => if (reg_context.eh_frame and reg_context.is_macho) 5 else 4,
41 .x86_64 => 7,
42 .arm => 13,
43 .aarch64 => 31,
44 else => unreachable,
45 };
46}
47
48/// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature.
49/// This function clears these signature bits to make the pointer usable.
50pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
51 if (builtin.cpu.arch == .aarch64) {
52 // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
53 // The save / restore is because `xpaclri` operates on x30 (LR)
54 return asm (
55 \\mov x16, x30
56 \\mov x30, x15
57 \\hint 0x07
58 \\mov x15, x30
59 \\mov x30, x16
60 : [ret] "={x15}" (-> usize),
61 : [ptr] "{x15}" (ptr),
62 : "x16"
63 );
64 }
65
66 return ptr;
67}
68
69pub const RegisterContext = struct {
70 eh_frame: bool,
71 is_macho: bool,
72};
73
74pub const AbiError = error{
75 InvalidRegister,
76 UnimplementedArch,
77 UnimplementedOs,
78 RegisterContextRequired,
79 ThreadContextNotSupported,
80};
81
82fn RegValueReturnType(comptime ContextPtrType: type, comptime T: type) type {
83 const reg_bytes_type = comptime RegBytesReturnType(ContextPtrType);
84 const info = @typeInfo(reg_bytes_type).Pointer;
85 return @Type(.{
86 .Pointer = .{
87 .size = .One,
88 .is_const = info.is_const,
89 .is_volatile = info.is_volatile,
90 .is_allowzero = info.is_allowzero,
91 .alignment = info.alignment,
92 .address_space = info.address_space,
93 .child = T,
94 .sentinel = null,
95 },
96 });
97}
98
99/// Returns a pointer to a register stored in a ThreadContext, preserving the pointer attributes of the context.
100pub fn regValueNative(
101 comptime T: type,
102 thread_context_ptr: anytype,
103 reg_number: u8,
104 reg_context: ?RegisterContext,
105) !RegValueReturnType(@TypeOf(thread_context_ptr), T) {
106 const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context);
107 if (@sizeOf(T) != reg_bytes.len) return error.IncompatibleRegisterSize;
108 return mem.bytesAsValue(T, reg_bytes[0..@sizeOf(T)]);
109}
110
111fn RegBytesReturnType(comptime ContextPtrType: type) type {
112 const info = @typeInfo(ContextPtrType);
113 if (info != .Pointer or info.Pointer.child != std.debug.ThreadContext) {
114 @compileError("Expected a pointer to std.debug.ThreadContext, got " ++ @typeName(@TypeOf(ContextPtrType)));
115 }
116
117 return if (info.Pointer.is_const) return []const u8 else []u8;
118}
119
120/// Returns a slice containing the backing storage for `reg_number`.
121///
122/// `reg_context` describes in what context the register number is used, as it can have different
123/// meanings depending on the DWARF container. It is only required when getting the stack or
124/// frame pointer register on some architectures.
125pub fn regBytes(
126 thread_context_ptr: anytype,
127 reg_number: u8,
128 reg_context: ?RegisterContext,
129) AbiError!RegBytesReturnType(@TypeOf(thread_context_ptr)) {
130 if (builtin.os.tag == .windows) {
131 return switch (builtin.cpu.arch) {
132 .x86 => switch (reg_number) {
133 0 => mem.asBytes(&thread_context_ptr.Eax),
134 1 => mem.asBytes(&thread_context_ptr.Ecx),
135 2 => mem.asBytes(&thread_context_ptr.Edx),
136 3 => mem.asBytes(&thread_context_ptr.Ebx),
137 4 => mem.asBytes(&thread_context_ptr.Esp),
138 5 => mem.asBytes(&thread_context_ptr.Ebp),
139 6 => mem.asBytes(&thread_context_ptr.Esi),
140 7 => mem.asBytes(&thread_context_ptr.Edi),
141 8 => mem.asBytes(&thread_context_ptr.Eip),
142 9 => mem.asBytes(&thread_context_ptr.EFlags),
143 10 => mem.asBytes(&thread_context_ptr.SegCs),
144 11 => mem.asBytes(&thread_context_ptr.SegSs),
145 12 => mem.asBytes(&thread_context_ptr.SegDs),
146 13 => mem.asBytes(&thread_context_ptr.SegEs),
147 14 => mem.asBytes(&thread_context_ptr.SegFs),
148 15 => mem.asBytes(&thread_context_ptr.SegGs),
149 else => error.InvalidRegister,
150 },
151 .x86_64 => switch (reg_number) {
152 0 => mem.asBytes(&thread_context_ptr.Rax),
153 1 => mem.asBytes(&thread_context_ptr.Rdx),
154 2 => mem.asBytes(&thread_context_ptr.Rcx),
155 3 => mem.asBytes(&thread_context_ptr.Rbx),
156 4 => mem.asBytes(&thread_context_ptr.Rsi),
157 5 => mem.asBytes(&thread_context_ptr.Rdi),
158 6 => mem.asBytes(&thread_context_ptr.Rbp),
159 7 => mem.asBytes(&thread_context_ptr.Rsp),
160 8 => mem.asBytes(&thread_context_ptr.R8),
161 9 => mem.asBytes(&thread_context_ptr.R9),
162 10 => mem.asBytes(&thread_context_ptr.R10),
163 11 => mem.asBytes(&thread_context_ptr.R11),
164 12 => mem.asBytes(&thread_context_ptr.R12),
165 13 => mem.asBytes(&thread_context_ptr.R13),
166 14 => mem.asBytes(&thread_context_ptr.R14),
167 15 => mem.asBytes(&thread_context_ptr.R15),
168 16 => mem.asBytes(&thread_context_ptr.Rip),
169 else => error.InvalidRegister,
170 },
171 .aarch64 => switch (reg_number) {
172 0...30 => mem.asBytes(&thread_context_ptr.DUMMYUNIONNAME.X[reg_number]),
173 31 => mem.asBytes(&thread_context_ptr.Sp),
174 32 => mem.asBytes(&thread_context_ptr.Pc),
175 else => error.InvalidRegister,
176 },
177 else => error.UnimplementedArch,
178 };
179 }
180
181 if (!std.debug.have_ucontext) return error.ThreadContextNotSupported;
182
183 const ucontext_ptr = thread_context_ptr;
184 return switch (builtin.cpu.arch) {
185 .x86 => switch (builtin.os.tag) {
186 .linux, .netbsd, .solaris => switch (reg_number) {
187 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EAX]),
188 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ECX]),
189 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDX]),
190 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBX]),
191 4...5 => if (reg_context) |r| bytes: {
192 if (reg_number == 4) {
193 break :bytes if (r.eh_frame and r.is_macho)
194 mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP])
195 else
196 mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP]);
197 } else {
198 break :bytes if (r.eh_frame and r.is_macho)
199 mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP])
200 else
201 mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP]);
202 }
203 } else error.RegisterContextRequired,
204 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESI]),
205 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDI]),
206 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EIP]),
207 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EFL]),
208 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.CS]),
209 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.SS]),
210 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.DS]),
211 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ES]),
212 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.FS]),
213 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.GS]),
214 16...23 => error.InvalidRegister, // TODO: Support loading ST0-ST7 from mcontext.fpregs
215 32...39 => error.InvalidRegister, // TODO: Support loading XMM0-XMM7 from mcontext.fpregs
216 else => error.InvalidRegister,
217 },
218 else => error.UnimplementedOs,
219 },
220 .x86_64 => switch (builtin.os.tag) {
221 .linux, .netbsd, .solaris => switch (reg_number) {
222 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RAX]),
223 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RDX]),
224 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RCX]),
225 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RBX]),
226 4 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RSI]),
227 5 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RDI]),
228 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RBP]),
229 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RSP]),
230 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R8]),
231 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R9]),
232 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R10]),
233 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R11]),
234 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R12]),
235 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R13]),
236 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R14]),
237 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R15]),
238 16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RIP]),
239 17...32 => |i| mem.asBytes(&ucontext_ptr.mcontext.fpregs.xmm[i - 17]),
240 else => error.InvalidRegister,
241 },
242 .freebsd => switch (reg_number) {
243 0 => mem.asBytes(&ucontext_ptr.mcontext.rax),
244 1 => mem.asBytes(&ucontext_ptr.mcontext.rdx),
245 2 => mem.asBytes(&ucontext_ptr.mcontext.rcx),
246 3 => mem.asBytes(&ucontext_ptr.mcontext.rbx),
247 4 => mem.asBytes(&ucontext_ptr.mcontext.rsi),
248 5 => mem.asBytes(&ucontext_ptr.mcontext.rdi),
249 6 => mem.asBytes(&ucontext_ptr.mcontext.rbp),
250 7 => mem.asBytes(&ucontext_ptr.mcontext.rsp),
251 8 => mem.asBytes(&ucontext_ptr.mcontext.r8),
252 9 => mem.asBytes(&ucontext_ptr.mcontext.r9),
253 10 => mem.asBytes(&ucontext_ptr.mcontext.r10),
254 11 => mem.asBytes(&ucontext_ptr.mcontext.r11),
255 12 => mem.asBytes(&ucontext_ptr.mcontext.r12),
256 13 => mem.asBytes(&ucontext_ptr.mcontext.r13),
257 14 => mem.asBytes(&ucontext_ptr.mcontext.r14),
258 15 => mem.asBytes(&ucontext_ptr.mcontext.r15),
259 16 => mem.asBytes(&ucontext_ptr.mcontext.rip),
260 // TODO: Extract xmm state from mcontext.fpstate?
261 else => error.InvalidRegister,
262 },
263 .openbsd => switch (reg_number) {
264 0 => mem.asBytes(&ucontext_ptr.sc_rax),
265 1 => mem.asBytes(&ucontext_ptr.sc_rdx),
266 2 => mem.asBytes(&ucontext_ptr.sc_rcx),
267 3 => mem.asBytes(&ucontext_ptr.sc_rbx),
268 4 => mem.asBytes(&ucontext_ptr.sc_rsi),
269 5 => mem.asBytes(&ucontext_ptr.sc_rdi),
270 6 => mem.asBytes(&ucontext_ptr.sc_rbp),
271 7 => mem.asBytes(&ucontext_ptr.sc_rsp),
272 8 => mem.asBytes(&ucontext_ptr.sc_r8),
273 9 => mem.asBytes(&ucontext_ptr.sc_r9),
274 10 => mem.asBytes(&ucontext_ptr.sc_r10),
275 11 => mem.asBytes(&ucontext_ptr.sc_r11),
276 12 => mem.asBytes(&ucontext_ptr.sc_r12),
277 13 => mem.asBytes(&ucontext_ptr.sc_r13),
278 14 => mem.asBytes(&ucontext_ptr.sc_r14),
279 15 => mem.asBytes(&ucontext_ptr.sc_r15),
280 16 => mem.asBytes(&ucontext_ptr.sc_rip),
281 // TODO: Extract xmm state from sc_fpstate?
282 else => error.InvalidRegister,
283 },
284 .macos => switch (reg_number) {
285 0 => mem.asBytes(&ucontext_ptr.mcontext.ss.rax),
286 1 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdx),
287 2 => mem.asBytes(&ucontext_ptr.mcontext.ss.rcx),
288 3 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbx),
289 4 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsi),
290 5 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdi),
291 6 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbp),
292 7 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsp),
293 8 => mem.asBytes(&ucontext_ptr.mcontext.ss.r8),
294 9 => mem.asBytes(&ucontext_ptr.mcontext.ss.r9),
295 10 => mem.asBytes(&ucontext_ptr.mcontext.ss.r10),
296 11 => mem.asBytes(&ucontext_ptr.mcontext.ss.r11),
297 12 => mem.asBytes(&ucontext_ptr.mcontext.ss.r12),
298 13 => mem.asBytes(&ucontext_ptr.mcontext.ss.r13),
299 14 => mem.asBytes(&ucontext_ptr.mcontext.ss.r14),
300 15 => mem.asBytes(&ucontext_ptr.mcontext.ss.r15),
301 16 => mem.asBytes(&ucontext_ptr.mcontext.ss.rip),
302 else => error.InvalidRegister,
303 },
304 else => error.UnimplementedOs,
305 },
306 .arm => switch (builtin.os.tag) {
307 .linux => switch (reg_number) {
308 0 => mem.asBytes(&ucontext_ptr.mcontext.arm_r0),
309 1 => mem.asBytes(&ucontext_ptr.mcontext.arm_r1),
310 2 => mem.asBytes(&ucontext_ptr.mcontext.arm_r2),
311 3 => mem.asBytes(&ucontext_ptr.mcontext.arm_r3),
312 4 => mem.asBytes(&ucontext_ptr.mcontext.arm_r4),
313 5 => mem.asBytes(&ucontext_ptr.mcontext.arm_r5),
314 6 => mem.asBytes(&ucontext_ptr.mcontext.arm_r6),
315 7 => mem.asBytes(&ucontext_ptr.mcontext.arm_r7),
316 8 => mem.asBytes(&ucontext_ptr.mcontext.arm_r8),
317 9 => mem.asBytes(&ucontext_ptr.mcontext.arm_r9),
318 10 => mem.asBytes(&ucontext_ptr.mcontext.arm_r10),
319 11 => mem.asBytes(&ucontext_ptr.mcontext.arm_fp),
320 12 => mem.asBytes(&ucontext_ptr.mcontext.arm_ip),
321 13 => mem.asBytes(&ucontext_ptr.mcontext.arm_sp),
322 14 => mem.asBytes(&ucontext_ptr.mcontext.arm_lr),
323 15 => mem.asBytes(&ucontext_ptr.mcontext.arm_pc),
324 // CPSR is not allocated a register number (See: https://github.com/ARM-software/abi-aa/blob/main/aadwarf32/aadwarf32.rst, Section 4.1)
325 else => error.InvalidRegister,
326 },
327 else => error.UnimplementedOs,
328 },
329 .aarch64 => switch (builtin.os.tag) {
330 .macos => switch (reg_number) {
331 0...28 => mem.asBytes(&ucontext_ptr.mcontext.ss.regs[reg_number]),
332 29 => mem.asBytes(&ucontext_ptr.mcontext.ss.fp),
333 30 => mem.asBytes(&ucontext_ptr.mcontext.ss.lr),
334 31 => mem.asBytes(&ucontext_ptr.mcontext.ss.sp),
335 32 => mem.asBytes(&ucontext_ptr.mcontext.ss.pc),
336
337 // TODO: Find storage for this state
338 //34 => mem.asBytes(&ucontext_ptr.ra_sign_state),
339
340 // V0-V31
341 64...95 => mem.asBytes(&ucontext_ptr.mcontext.ns.q[reg_number - 64]),
342 else => error.InvalidRegister,
343 },
344 .netbsd => switch (reg_number) {
345 0...34 => mem.asBytes(&ucontext_ptr.mcontext.gregs[reg_number]),
346 else => error.InvalidRegister,
347 },
348 .freebsd => switch (reg_number) {
349 0...29 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.x[reg_number]),
350 30 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.lr),
351 31 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.sp),
352
353 // TODO: This seems wrong, but it was in the previous debug.zig code for mapping PC, check this
354 32 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.elr),
355
356 else => error.InvalidRegister,
357 },
358 else => switch (reg_number) {
359 0...30 => mem.asBytes(&ucontext_ptr.mcontext.regs[reg_number]),
360 31 => mem.asBytes(&ucontext_ptr.mcontext.sp),
361 32 => mem.asBytes(&ucontext_ptr.mcontext.pc),
362 else => error.InvalidRegister,
363 },
364 },
365 else => error.UnimplementedArch,
366 };
367}
368
369/// Returns the ABI-defined default value this register has in the unwinding table
370/// before running any of the CIE instructions. The DWARF spec defines these as having
371/// the .undefined rule by default, but allows ABI authors to override that.
372pub fn getRegDefaultValue(reg_number: u8, context: *std.dwarf.UnwindContext, out: []u8) !void {
373 switch (builtin.cpu.arch) {
374 .aarch64 => {
375 // Callee-saved registers are initialized as if they had the .same_value rule
376 if (reg_number >= 19 and reg_number <= 28) {
377 const src = try regBytes(context.thread_context, reg_number, context.reg_context);
378 if (src.len != out.len) return error.RegisterSizeMismatch;
379 @memcpy(out, src);
380 return;
381 }
382 },
383 else => {},
384 }
385
386 @memset(out, undefined);
387}
lib/std/dwarf/call_frame.zig created+610
......@@ -0,0 +1,610 @@
1const builtin = @import("builtin");
2const std = @import("../std.zig");
3const mem = std.mem;
4const debug = std.debug;
5const leb = std.leb;
6const dwarf = std.dwarf;
7const abi = dwarf.abi;
8const expressions = dwarf.expressions;
9const assert = std.debug.assert;
10
11const Opcode = enum(u8) {
12 advance_loc = 0x1 << 6,
13 offset = 0x2 << 6,
14 restore = 0x3 << 6,
15
16 nop = 0x00,
17 set_loc = 0x01,
18 advance_loc1 = 0x02,
19 advance_loc2 = 0x03,
20 advance_loc4 = 0x04,
21 offset_extended = 0x05,
22 restore_extended = 0x06,
23 undefined = 0x07,
24 same_value = 0x08,
25 register = 0x09,
26 remember_state = 0x0a,
27 restore_state = 0x0b,
28 def_cfa = 0x0c,
29 def_cfa_register = 0x0d,
30 def_cfa_offset = 0x0e,
31 def_cfa_expression = 0x0f,
32 expression = 0x10,
33 offset_extended_sf = 0x11,
34 def_cfa_sf = 0x12,
35 def_cfa_offset_sf = 0x13,
36 val_offset = 0x14,
37 val_offset_sf = 0x15,
38 val_expression = 0x16,
39
40 // These opcodes encode an operand in the lower 6 bits of the opcode itself
41 pub const lo_inline = @intFromEnum(Opcode.advance_loc);
42 pub const hi_inline = @intFromEnum(Opcode.restore) | 0b111111;
43
44 // These opcodes are trailed by zero or more operands
45 pub const lo_reserved = @intFromEnum(Opcode.nop);
46 pub const hi_reserved = @intFromEnum(Opcode.val_expression);
47
48 // Vendor-specific opcodes
49 pub const lo_user = 0x1c;
50 pub const hi_user = 0x3f;
51};
52
53const Operand = enum {
54 opcode_delta,
55 opcode_register,
56 uleb128_register,
57 uleb128_offset,
58 sleb128_offset,
59 address,
60 u8_delta,
61 u16_delta,
62 u32_delta,
63 block,
64
65 fn Storage(comptime self: Operand) type {
66 return switch (self) {
67 .opcode_delta, .opcode_register => u8,
68 .uleb128_register => u8,
69 .uleb128_offset => u64,
70 .sleb128_offset => i64,
71 .address => u64,
72 .u8_delta => u8,
73 .u16_delta => u16,
74 .u32_delta => u32,
75 .block => []const u8,
76 };
77 }
78
79 fn read(
80 comptime self: Operand,
81 stream: *std.io.FixedBufferStream([]const u8),
82 opcode_value: ?u6,
83 addr_size_bytes: u8,
84 endian: std.builtin.Endian,
85 ) !Storage(self) {
86 const reader = stream.reader();
87 return switch (self) {
88 .opcode_delta, .opcode_register => opcode_value orelse return error.InvalidOperand,
89 .uleb128_register => try leb.readULEB128(u8, reader),
90 .uleb128_offset => try leb.readULEB128(u64, reader),
91 .sleb128_offset => try leb.readILEB128(i64, reader),
92 .address => switch (addr_size_bytes) {
93 2 => try reader.readInt(u16, endian),
94 4 => try reader.readInt(u32, endian),
95 8 => try reader.readInt(u64, endian),
96 else => return error.InvalidAddrSize,
97 },
98 .u8_delta => try reader.readByte(),
99 .u16_delta => try reader.readInt(u16, endian),
100 .u32_delta => try reader.readInt(u32, endian),
101 .block => {
102 const block_len = try leb.readULEB128(usize, reader);
103 if (stream.pos + block_len > stream.buffer.len) return error.InvalidOperand;
104
105 const block = stream.buffer[stream.pos..][0..block_len];
106 reader.context.pos += block_len;
107
108 return block;
109 },
110 };
111 }
112};
113
114fn InstructionType(comptime definition: anytype) type {
115 const definition_type = @typeInfo(@TypeOf(definition));
116 assert(definition_type == .Struct);
117
118 const definition_len = definition_type.Struct.fields.len;
119 comptime var fields: [definition_len]std.builtin.Type.StructField = undefined;
120 inline for (definition_type.Struct.fields, &fields) |definition_field, *operands_field| {
121 const opcode = std.enums.nameCast(Operand, @field(definition, definition_field.name));
122 const storage_type = opcode.Storage();
123 operands_field.* = .{
124 .name = definition_field.name,
125 .type = storage_type,
126 .default_value = null,
127 .is_comptime = false,
128 .alignment = @alignOf(storage_type),
129 };
130 }
131
132 const InstructionOperands = @Type(.{
133 .Struct = .{
134 .layout = .Auto,
135 .fields = &fields,
136 .decls = &.{},
137 .is_tuple = false,
138 },
139 });
140
141 return struct {
142 const Self = @This();
143 operands: InstructionOperands,
144
145 pub fn read(
146 stream: *std.io.FixedBufferStream([]const u8),
147 opcode_value: ?u6,
148 addr_size_bytes: u8,
149 endian: std.builtin.Endian,
150 ) !Self {
151 var operands: InstructionOperands = undefined;
152 inline for (definition_type.Struct.fields) |definition_field| {
153 const operand = comptime std.enums.nameCast(Operand, @field(definition, definition_field.name));
154 @field(operands, definition_field.name) = try operand.read(stream, opcode_value, addr_size_bytes, endian);
155 }
156
157 return .{ .operands = operands };
158 }
159 };
160}
161
162pub const Instruction = union(Opcode) {
163 advance_loc: InstructionType(.{ .delta = .opcode_delta }),
164 offset: InstructionType(.{ .register = .opcode_register, .offset = .uleb128_offset }),
165 offset_extended: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }),
166 restore: InstructionType(.{ .register = .opcode_register }),
167 restore_extended: InstructionType(.{ .register = .uleb128_register }),
168 nop: InstructionType(.{}),
169 set_loc: InstructionType(.{ .address = .address }),
170 advance_loc1: InstructionType(.{ .delta = .u8_delta }),
171 advance_loc2: InstructionType(.{ .delta = .u16_delta }),
172 advance_loc4: InstructionType(.{ .delta = .u32_delta }),
173 undefined: InstructionType(.{ .register = .uleb128_register }),
174 same_value: InstructionType(.{ .register = .uleb128_register }),
175 register: InstructionType(.{ .register = .uleb128_register, .target_register = .uleb128_register }),
176 remember_state: InstructionType(.{}),
177 restore_state: InstructionType(.{}),
178 def_cfa: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }),
179 def_cfa_register: InstructionType(.{ .register = .uleb128_register }),
180 def_cfa_offset: InstructionType(.{ .offset = .uleb128_offset }),
181 def_cfa_expression: InstructionType(.{ .block = .block }),
182 expression: InstructionType(.{ .register = .uleb128_register, .block = .block }),
183 offset_extended_sf: InstructionType(.{ .register = .uleb128_register, .offset = .sleb128_offset }),
184 def_cfa_sf: InstructionType(.{ .register = .uleb128_register, .offset = .sleb128_offset }),
185 def_cfa_offset_sf: InstructionType(.{ .offset = .sleb128_offset }),
186 val_offset: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }),
187 val_offset_sf: InstructionType(.{ .register = .uleb128_register, .offset = .sleb128_offset }),
188 val_expression: InstructionType(.{ .register = .uleb128_register, .block = .block }),
189
190 fn readOperands(
191 self: *Instruction,
192 stream: *std.io.FixedBufferStream([]const u8),
193 opcode_value: ?u6,
194 addr_size_bytes: u8,
195 endian: std.builtin.Endian,
196 ) !void {
197 switch (self.*) {
198 inline else => |*inst| inst.* = try @TypeOf(inst.*).read(stream, opcode_value, addr_size_bytes, endian),
199 }
200 }
201
202 pub fn read(
203 stream: *std.io.FixedBufferStream([]const u8),
204 addr_size_bytes: u8,
205 endian: std.builtin.Endian,
206 ) !Instruction {
207 return switch (try stream.reader().readByte()) {
208 inline Opcode.lo_inline...Opcode.hi_inline => |opcode| blk: {
209 const e: Opcode = @enumFromInt(opcode & 0b11000000);
210 var result = @unionInit(Instruction, @tagName(e), undefined);
211 try result.readOperands(stream, @as(u6, @intCast(opcode & 0b111111)), addr_size_bytes, endian);
212 break :blk result;
213 },
214 inline Opcode.lo_reserved...Opcode.hi_reserved => |opcode| blk: {
215 const e: Opcode = @enumFromInt(opcode);
216 var result = @unionInit(Instruction, @tagName(e), undefined);
217 try result.readOperands(stream, null, addr_size_bytes, endian);
218 break :blk result;
219 },
220 Opcode.lo_user...Opcode.hi_user => error.UnimplementedUserOpcode,
221 else => error.InvalidOpcode,
222 };
223 }
224};
225
226/// Since register rules are applied (usually) during a panic,
227/// checked addition / subtraction is used so that we can return
228/// an error and fall back to FP-based unwinding.
229pub fn applyOffset(base: usize, offset: i64) !usize {
230 return if (offset >= 0)
231 try std.math.add(usize, base, @as(usize, @intCast(offset)))
232 else
233 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
234}
235
236/// This is a virtual machine that runs DWARF call frame instructions.
237pub const VirtualMachine = struct {
238 /// See section 6.4.1 of the DWARF5 specification for details on each
239 const RegisterRule = union(enum) {
240 // The spec says that the default rule for each column is the undefined rule.
241 // However, it also allows ABI / compiler authors to specify alternate defaults, so
242 // there is a distinction made here.
243 default: void,
244
245 undefined: void,
246 same_value: void,
247
248 // offset(N)
249 offset: i64,
250
251 // val_offset(N)
252 val_offset: i64,
253
254 // register(R)
255 register: u8,
256
257 // expression(E)
258 expression: []const u8,
259
260 // val_expression(E)
261 val_expression: []const u8,
262
263 // Augmenter-defined rule
264 architectural: void,
265 };
266
267 /// Each row contains unwinding rules for a set of registers.
268 pub const Row = struct {
269 /// Offset from `FrameDescriptionEntry.pc_begin`
270 offset: u64 = 0,
271
272 /// Special-case column that defines the CFA (Canonical Frame Address) rule.
273 /// The register field of this column defines the register that CFA is derived from.
274 cfa: Column = .{},
275
276 /// The register fields in these columns define the register the rule applies to.
277 columns: ColumnRange = .{},
278
279 /// Indicates that the next write to any column in this row needs to copy
280 /// the backing column storage first, as it may be referenced by previous rows.
281 copy_on_write: bool = false,
282 };
283
284 pub const Column = struct {
285 register: ?u8 = null,
286 rule: RegisterRule = .{ .default = {} },
287
288 /// Resolves the register rule and places the result into `out` (see dwarf.abi.regBytes)
289 pub fn resolveValue(
290 self: Column,
291 context: *dwarf.UnwindContext,
292 expression_context: dwarf.expressions.ExpressionContext,
293 out: []u8,
294 ) !void {
295 switch (self.rule) {
296 .default => {
297 const register = self.register orelse return error.InvalidRegister;
298 try abi.getRegDefaultValue(register, context, out);
299 },
300 .undefined => {
301 @memset(out, undefined);
302 },
303 .same_value => {
304 // TODO: This copy could be eliminated if callers always copy the state then call this function to update it
305 const register = self.register orelse return error.InvalidRegister;
306 const src = try abi.regBytes(context.thread_context, register, context.reg_context);
307 if (src.len != out.len) return error.RegisterSizeMismatch;
308 @memcpy(out, src);
309 },
310 .offset => |offset| {
311 if (context.cfa) |cfa| {
312 const addr = try applyOffset(cfa, offset);
313 if (expression_context.isValidMemory) |isValidMemory| if (!isValidMemory(addr)) return error.InvalidAddress;
314 const ptr: *const usize = @ptrFromInt(addr);
315 mem.writeIntSliceNative(usize, out, ptr.*);
316 } else return error.InvalidCFA;
317 },
318 .val_offset => |offset| {
319 if (context.cfa) |cfa| {
320 mem.writeIntSliceNative(usize, out, try applyOffset(cfa, offset));
321 } else return error.InvalidCFA;
322 },
323 .register => |register| {
324 const src = try abi.regBytes(context.thread_context, register, context.reg_context);
325 if (src.len != out.len) return error.RegisterSizeMismatch;
326 @memcpy(out, try abi.regBytes(context.thread_context, register, context.reg_context));
327 },
328 .expression => |expression| {
329 context.stack_machine.reset();
330 const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?);
331 const addr = if (value) |v| blk: {
332 if (v != .generic) return error.InvalidExpressionValue;
333 break :blk v.generic;
334 } else return error.NoExpressionValue;
335
336 if (!context.isValidMemory(addr)) return error.InvalidExpressionAddress;
337 const ptr: *usize = @ptrFromInt(addr);
338 mem.writeIntSliceNative(usize, out, ptr.*);
339 },
340 .val_expression => |expression| {
341 context.stack_machine.reset();
342 const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?);
343 if (value) |v| {
344 if (v != .generic) return error.InvalidExpressionValue;
345 mem.writeIntSliceNative(usize, out, v.generic);
346 } else return error.NoExpressionValue;
347 },
348 .architectural => return error.UnimplementedRegisterRule,
349 }
350 }
351 };
352
353 const ColumnRange = struct {
354 /// Index into `columns` of the first column in this row.
355 start: usize = undefined,
356 len: u8 = 0,
357 };
358
359 columns: std.ArrayListUnmanaged(Column) = .{},
360 stack: std.ArrayListUnmanaged(ColumnRange) = .{},
361 current_row: Row = .{},
362
363 /// The result of executing the CIE's initial_instructions
364 cie_row: ?Row = null,
365
366 pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void {
367 self.stack.deinit(allocator);
368 self.columns.deinit(allocator);
369 self.* = undefined;
370 }
371
372 pub fn reset(self: *VirtualMachine) void {
373 self.stack.clearRetainingCapacity();
374 self.columns.clearRetainingCapacity();
375 self.current_row = .{};
376 self.cie_row = null;
377 }
378
379 /// Return a slice backed by the row's non-CFA columns
380 pub fn rowColumns(self: VirtualMachine, row: Row) []Column {
381 return self.columns.items[row.columns.start..][0..row.columns.len];
382 }
383
384 /// Either retrieves or adds a column for `register` (non-CFA) in the current row.
385 fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column {
386 for (self.rowColumns(self.current_row)) |*c| {
387 if (c.register == register) return c;
388 }
389
390 if (self.current_row.columns.len == 0) {
391 self.current_row.columns.start = self.columns.items.len;
392 }
393 self.current_row.columns.len += 1;
394
395 const column = try self.columns.addOne(allocator);
396 column.* = .{
397 .register = register,
398 };
399
400 return column;
401 }
402
403 /// Runs the CIE instructions, then the FDE instructions. Execution halts
404 /// once the row that corresponds to `pc` is known, and the row is returned.
405 pub fn runTo(
406 self: *VirtualMachine,
407 allocator: std.mem.Allocator,
408 pc: u64,
409 cie: dwarf.CommonInformationEntry,
410 fde: dwarf.FrameDescriptionEntry,
411 addr_size_bytes: u8,
412 endian: std.builtin.Endian,
413 ) !Row {
414 assert(self.cie_row == null);
415 if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange;
416
417 var prev_row: Row = self.current_row;
418
419 var cie_stream = std.io.fixedBufferStream(cie.initial_instructions);
420 var fde_stream = std.io.fixedBufferStream(fde.instructions);
421 var streams = [_]*std.io.FixedBufferStream([]const u8){
422 &cie_stream,
423 &fde_stream,
424 };
425
426 for (&streams, 0..) |stream, i| {
427 while (stream.pos < stream.buffer.len) {
428 const instruction = try dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian);
429 prev_row = try self.step(allocator, cie, i == 0, instruction);
430 if (pc < fde.pc_begin + self.current_row.offset) return prev_row;
431 }
432 }
433
434 return self.current_row;
435 }
436
437 pub fn runToNative(
438 self: *VirtualMachine,
439 allocator: std.mem.Allocator,
440 pc: u64,
441 cie: dwarf.CommonInformationEntry,
442 fde: dwarf.FrameDescriptionEntry,
443 ) !Row {
444 return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), builtin.target.cpu.arch.endian());
445 }
446
447 fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void {
448 if (!self.current_row.copy_on_write) return;
449
450 const new_start = self.columns.items.len;
451 if (self.current_row.columns.len > 0) {
452 try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len);
453 self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row));
454 self.current_row.columns.start = new_start;
455 }
456 }
457
458 /// Executes a single instruction.
459 /// If this instruction is from the CIE, `is_initial` should be set.
460 /// Returns the value of `current_row` before executing this instruction.
461 pub fn step(
462 self: *VirtualMachine,
463 allocator: std.mem.Allocator,
464 cie: dwarf.CommonInformationEntry,
465 is_initial: bool,
466 instruction: Instruction,
467 ) !Row {
468 // CIE instructions must be run before FDE instructions
469 assert(!is_initial or self.cie_row == null);
470 if (!is_initial and self.cie_row == null) {
471 self.cie_row = self.current_row;
472 self.current_row.copy_on_write = true;
473 }
474
475 const prev_row = self.current_row;
476 switch (instruction) {
477 .set_loc => |i| {
478 if (i.operands.address <= self.current_row.offset) return error.InvalidOperation;
479 // TODO: Check cie.segment_selector_size != 0 for DWARFV4
480 self.current_row.offset = i.operands.address;
481 },
482 inline .advance_loc,
483 .advance_loc1,
484 .advance_loc2,
485 .advance_loc4,
486 => |i| {
487 self.current_row.offset += i.operands.delta * cie.code_alignment_factor;
488 self.current_row.copy_on_write = true;
489 },
490 inline .offset,
491 .offset_extended,
492 .offset_extended_sf,
493 => |i| {
494 try self.resolveCopyOnWrite(allocator);
495 const column = try self.getOrAddColumn(allocator, i.operands.register);
496 column.rule = .{ .offset = @as(i64, @intCast(i.operands.offset)) * cie.data_alignment_factor };
497 },
498 inline .restore,
499 .restore_extended,
500 => |i| {
501 try self.resolveCopyOnWrite(allocator);
502 if (self.cie_row) |cie_row| {
503 const column = try self.getOrAddColumn(allocator, i.operands.register);
504 column.rule = for (self.rowColumns(cie_row)) |cie_column| {
505 if (cie_column.register == i.operands.register) break cie_column.rule;
506 } else .{ .default = {} };
507 } else return error.InvalidOperation;
508 },
509 .nop => {},
510 .undefined => |i| {
511 try self.resolveCopyOnWrite(allocator);
512 const column = try self.getOrAddColumn(allocator, i.operands.register);
513 column.rule = .{ .undefined = {} };
514 },
515 .same_value => |i| {
516 try self.resolveCopyOnWrite(allocator);
517 const column = try self.getOrAddColumn(allocator, i.operands.register);
518 column.rule = .{ .same_value = {} };
519 },
520 .register => |i| {
521 try self.resolveCopyOnWrite(allocator);
522 const column = try self.getOrAddColumn(allocator, i.operands.register);
523 column.rule = .{ .register = i.operands.target_register };
524 },
525 .remember_state => {
526 try self.stack.append(allocator, self.current_row.columns);
527 self.current_row.copy_on_write = true;
528 },
529 .restore_state => {
530 const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation;
531 self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len);
532 try self.columns.ensureUnusedCapacity(allocator, restored_columns.len);
533
534 self.current_row.columns.start = self.columns.items.len;
535 self.current_row.columns.len = restored_columns.len;
536 self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]);
537 },
538 .def_cfa => |i| {
539 try self.resolveCopyOnWrite(allocator);
540 self.current_row.cfa = .{
541 .register = i.operands.register,
542 .rule = .{ .val_offset = @intCast(i.operands.offset) },
543 };
544 },
545 .def_cfa_sf => |i| {
546 try self.resolveCopyOnWrite(allocator);
547 self.current_row.cfa = .{
548 .register = i.operands.register,
549 .rule = .{ .val_offset = i.operands.offset * cie.data_alignment_factor },
550 };
551 },
552 .def_cfa_register => |i| {
553 try self.resolveCopyOnWrite(allocator);
554 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
555 self.current_row.cfa.register = i.operands.register;
556 },
557 .def_cfa_offset => |i| {
558 try self.resolveCopyOnWrite(allocator);
559 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
560 self.current_row.cfa.rule = .{
561 .val_offset = @intCast(i.operands.offset),
562 };
563 },
564 .def_cfa_offset_sf => |i| {
565 try self.resolveCopyOnWrite(allocator);
566 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
567 self.current_row.cfa.rule = .{
568 .val_offset = i.operands.offset * cie.data_alignment_factor,
569 };
570 },
571 .def_cfa_expression => |i| {
572 try self.resolveCopyOnWrite(allocator);
573 self.current_row.cfa.register = undefined;
574 self.current_row.cfa.rule = .{
575 .expression = i.operands.block,
576 };
577 },
578 .expression => |i| {
579 try self.resolveCopyOnWrite(allocator);
580 const column = try self.getOrAddColumn(allocator, i.operands.register);
581 column.rule = .{
582 .expression = i.operands.block,
583 };
584 },
585 .val_offset => |i| {
586 try self.resolveCopyOnWrite(allocator);
587 const column = try self.getOrAddColumn(allocator, i.operands.register);
588 column.rule = .{
589 .val_offset = @as(i64, @intCast(i.operands.offset)) * cie.data_alignment_factor,
590 };
591 },
592 .val_offset_sf => |i| {
593 try self.resolveCopyOnWrite(allocator);
594 const column = try self.getOrAddColumn(allocator, i.operands.register);
595 column.rule = .{
596 .val_offset = i.operands.offset * cie.data_alignment_factor,
597 };
598 },
599 .val_expression => |i| {
600 try self.resolveCopyOnWrite(allocator);
601 const column = try self.getOrAddColumn(allocator, i.operands.register);
602 column.rule = .{
603 .val_expression = i.operands.block,
604 };
605 },
606 }
607
608 return prev_row;
609 }
610};
lib/std/dwarf/expressions.zig created+1639
......@@ -0,0 +1,1639 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const OP = @import("OP.zig");
4const leb = std.leb;
5const dwarf = std.dwarf;
6const abi = dwarf.abi;
7const mem = std.mem;
8const assert = std.debug.assert;
9
10/// Expressions can be evaluated in different contexts, each requiring its own set of inputs.
11/// Callers should specify all the fields relevant to their context. If a field is required
12/// by the expression and it isn't in the context, error.IncompleteExpressionContext is returned.
13pub const ExpressionContext = struct {
14 /// This expression is from a DWARF64 section
15 is_64: bool = false,
16
17 /// If specified, any addresses will pass through this function before being acccessed
18 isValidMemory: ?*const fn (address: usize) bool = null,
19
20 /// The compilation unit this expression relates to, if any
21 compile_unit: ?*const dwarf.CompileUnit = null,
22
23 /// When evaluating a user-presented expression, this is the address of the object being evaluated
24 object_address: ?*const anyopaque = null,
25
26 /// .debug_addr section
27 debug_addr: ?[]const u8 = null,
28
29 /// Thread context
30 thread_context: ?*std.debug.ThreadContext = null,
31 reg_context: ?abi.RegisterContext = null,
32
33 /// Call frame address, if in a CFI context
34 cfa: ?usize = null,
35
36 /// This expression is a sub-expression from an OP.entry_value instruction
37 entry_value_context: bool = false,
38};
39
40pub const ExpressionOptions = struct {
41 /// The address size of the target architecture
42 addr_size: u8 = @sizeOf(usize),
43
44 /// Endianess of the target architecture
45 endian: std.builtin.Endian = builtin.target.cpu.arch.endian(),
46
47 /// Restrict the stack machine to a subset of opcodes used in call frame instructions
48 call_frame_context: bool = false,
49};
50
51// Explcitly defined to support executing sub-expressions
52pub const ExpressionError = error{
53 UnimplementedExpressionCall,
54 UnimplementedOpcode,
55 UnimplementedUserOpcode,
56 UnimplementedTypedComparison,
57 UnimplementedTypeConversion,
58
59 UnknownExpressionOpcode,
60
61 IncompleteExpressionContext,
62
63 InvalidCFAOpcode,
64 InvalidExpression,
65 InvalidFrameBase,
66 InvalidIntegralTypeSize,
67 InvalidRegister,
68 InvalidSubExpression,
69 InvalidTypeLength,
70
71 TruncatedIntegralType,
72} || abi.AbiError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero };
73
74/// A stack machine that can decode and run DWARF expressions.
75/// Expressions can be decoded for non-native address size and endianness,
76/// but can only be executed if the current target matches the configuration.
77pub fn StackMachine(comptime options: ExpressionOptions) type {
78 const addr_type = switch (options.addr_size) {
79 2 => u16,
80 4 => u32,
81 8 => u64,
82 else => @compileError("Unsupported address size of " ++ options.addr_size),
83 };
84
85 const addr_type_signed = switch (options.addr_size) {
86 2 => i16,
87 4 => i32,
88 8 => i64,
89 else => @compileError("Unsupported address size of " ++ options.addr_size),
90 };
91
92 return struct {
93 const Self = @This();
94
95 const Operand = union(enum) {
96 generic: addr_type,
97 register: u8,
98 type_size: u8,
99 branch_offset: i16,
100 base_register: struct {
101 base_register: u8,
102 offset: i64,
103 },
104 composite_location: struct {
105 size: u64,
106 offset: i64,
107 },
108 block: []const u8,
109 register_type: struct {
110 register: u8,
111 type_offset: addr_type,
112 },
113 const_type: struct {
114 type_offset: addr_type,
115 value_bytes: []const u8,
116 },
117 deref_type: struct {
118 size: u8,
119 type_offset: addr_type,
120 },
121 };
122
123 const Value = union(enum) {
124 generic: addr_type,
125
126 // Typed value with a maximum size of a register
127 regval_type: struct {
128 // Offset of DW_TAG_base_type DIE
129 type_offset: addr_type,
130 type_size: u8,
131 value: addr_type,
132 },
133
134 // Typed value specified directly in the instruction stream
135 const_type: struct {
136 // Offset of DW_TAG_base_type DIE
137 type_offset: addr_type,
138 // Backed by the instruction stream
139 value_bytes: []const u8,
140 },
141
142 pub fn asIntegral(self: Value) !addr_type {
143 return switch (self) {
144 .generic => |v| v,
145
146 // TODO: For these two prongs, look up the type and assert it's integral?
147 .regval_type => |regval_type| regval_type.value,
148 .const_type => |const_type| {
149 const value: u64 = switch (const_type.value_bytes.len) {
150 1 => mem.readIntSliceNative(u8, const_type.value_bytes),
151 2 => mem.readIntSliceNative(u16, const_type.value_bytes),
152 4 => mem.readIntSliceNative(u32, const_type.value_bytes),
153 8 => mem.readIntSliceNative(u64, const_type.value_bytes),
154 else => return error.InvalidIntegralTypeSize,
155 };
156
157 return std.math.cast(addr_type, value) orelse error.TruncatedIntegralType;
158 },
159 };
160 }
161 };
162
163 stack: std.ArrayListUnmanaged(Value) = .{},
164
165 pub fn reset(self: *Self) void {
166 self.stack.clearRetainingCapacity();
167 }
168
169 pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
170 self.stack.deinit(allocator);
171 }
172
173 fn generic(value: anytype) Operand {
174 const int_info = @typeInfo(@TypeOf(value)).Int;
175 if (@sizeOf(@TypeOf(value)) > options.addr_size) {
176 return .{ .generic = switch (int_info.signedness) {
177 .signed => @bitCast(@as(addr_type_signed, @truncate(value))),
178 .unsigned => @truncate(value),
179 } };
180 } else {
181 return .{ .generic = switch (int_info.signedness) {
182 .signed => @bitCast(@as(addr_type_signed, @intCast(value))),
183 .unsigned => @intCast(value),
184 } };
185 }
186 }
187
188 pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8, context: ExpressionContext) !?Operand {
189 const reader = stream.reader();
190 return switch (opcode) {
191 OP.addr => generic(try reader.readInt(addr_type, options.endian)),
192 OP.call_ref => if (context.is_64)
193 generic(try reader.readInt(u64, options.endian))
194 else
195 generic(try reader.readInt(u32, options.endian)),
196 OP.const1u,
197 OP.pick,
198 => generic(try reader.readByte()),
199 OP.deref_size,
200 OP.xderef_size,
201 => .{ .type_size = try reader.readByte() },
202 OP.const1s => generic(try reader.readByteSigned()),
203 OP.const2u,
204 OP.call2,
205 => generic(try reader.readInt(u16, options.endian)),
206 OP.call4 => generic(try reader.readInt(u32, options.endian)),
207 OP.const2s => generic(try reader.readInt(i16, options.endian)),
208 OP.bra,
209 OP.skip,
210 => .{ .branch_offset = try reader.readInt(i16, options.endian) },
211 OP.const4u => generic(try reader.readInt(u32, options.endian)),
212 OP.const4s => generic(try reader.readInt(i32, options.endian)),
213 OP.const8u => generic(try reader.readInt(u64, options.endian)),
214 OP.const8s => generic(try reader.readInt(i64, options.endian)),
215 OP.constu,
216 OP.plus_uconst,
217 OP.addrx,
218 OP.constx,
219 OP.convert,
220 OP.reinterpret,
221 => generic(try leb.readULEB128(u64, reader)),
222 OP.consts,
223 OP.fbreg,
224 => generic(try leb.readILEB128(i64, reader)),
225 OP.lit0...OP.lit31 => |n| generic(n - OP.lit0),
226 OP.reg0...OP.reg31 => |n| .{ .register = n - OP.reg0 },
227 OP.breg0...OP.breg31 => |n| .{ .base_register = .{
228 .base_register = n - OP.breg0,
229 .offset = try leb.readILEB128(i64, reader),
230 } },
231 OP.regx => .{ .register = try leb.readULEB128(u8, reader) },
232 OP.bregx => blk: {
233 const base_register = try leb.readULEB128(u8, reader);
234 const offset = try leb.readILEB128(i64, reader);
235 break :blk .{ .base_register = .{
236 .base_register = base_register,
237 .offset = offset,
238 } };
239 },
240 OP.regval_type => blk: {
241 const register = try leb.readULEB128(u8, reader);
242 const type_offset = try leb.readULEB128(addr_type, reader);
243 break :blk .{ .register_type = .{
244 .register = register,
245 .type_offset = type_offset,
246 } };
247 },
248 OP.piece => .{
249 .composite_location = .{
250 .size = try leb.readULEB128(u8, reader),
251 .offset = 0,
252 },
253 },
254 OP.bit_piece => blk: {
255 const size = try leb.readULEB128(u8, reader);
256 const offset = try leb.readILEB128(i64, reader);
257 break :blk .{ .composite_location = .{
258 .size = size,
259 .offset = offset,
260 } };
261 },
262 OP.implicit_value, OP.entry_value => blk: {
263 const size = try leb.readULEB128(u8, reader);
264 if (stream.pos + size > stream.buffer.len) return error.InvalidExpression;
265 const block = stream.buffer[stream.pos..][0..size];
266 stream.pos += size;
267 break :blk .{
268 .block = block,
269 };
270 },
271 OP.const_type => blk: {
272 const type_offset = try leb.readULEB128(addr_type, reader);
273 const size = try reader.readByte();
274 if (stream.pos + size > stream.buffer.len) return error.InvalidExpression;
275 const value_bytes = stream.buffer[stream.pos..][0..size];
276 stream.pos += size;
277 break :blk .{ .const_type = .{
278 .type_offset = type_offset,
279 .value_bytes = value_bytes,
280 } };
281 },
282 OP.deref_type,
283 OP.xderef_type,
284 => .{
285 .deref_type = .{
286 .size = try reader.readByte(),
287 .type_offset = try leb.readULEB128(addr_type, reader),
288 },
289 },
290 OP.lo_user...OP.hi_user => return error.UnimplementedUserOpcode,
291 else => null,
292 };
293 }
294
295 pub fn run(
296 self: *Self,
297 expression: []const u8,
298 allocator: std.mem.Allocator,
299 context: ExpressionContext,
300 initial_value: ?usize,
301 ) ExpressionError!?Value {
302 if (initial_value) |i| try self.stack.append(allocator, .{ .generic = i });
303 var stream = std.io.fixedBufferStream(expression);
304 while (try self.step(&stream, allocator, context)) {}
305 if (self.stack.items.len == 0) return null;
306 return self.stack.items[self.stack.items.len - 1];
307 }
308
309 /// Reads an opcode and its operands from `stream`, then executes it
310 pub fn step(
311 self: *Self,
312 stream: *std.io.FixedBufferStream([]const u8),
313 allocator: std.mem.Allocator,
314 context: ExpressionContext,
315 ) ExpressionError!bool {
316 if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != comptime builtin.target.cpu.arch.endian())
317 @compileError("Execution of non-native address sizes / endianness is not supported");
318
319 const opcode = try stream.reader().readByte();
320 if (options.call_frame_context and !isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode;
321 switch (opcode) {
322
323 // 2.5.1.1: Literal Encodings
324 OP.lit0...OP.lit31,
325 OP.addr,
326 OP.const1u,
327 OP.const2u,
328 OP.const4u,
329 OP.const8u,
330 OP.const1s,
331 OP.const2s,
332 OP.const4s,
333 OP.const8s,
334 OP.constu,
335 OP.consts,
336 => try self.stack.append(allocator, .{ .generic = (try readOperand(stream, opcode, context)).?.generic }),
337
338 OP.const_type => {
339 const const_type = (try readOperand(stream, opcode, context)).?.const_type;
340 try self.stack.append(allocator, .{ .const_type = .{
341 .type_offset = const_type.type_offset,
342 .value_bytes = const_type.value_bytes,
343 } });
344 },
345
346 OP.addrx,
347 OP.constx,
348 => {
349 if (context.compile_unit == null) return error.IncompleteExpressionContext;
350 if (context.debug_addr == null) return error.IncompleteExpressionContext;
351 const debug_addr_index = (try readOperand(stream, opcode, context)).?.generic;
352 const offset = context.compile_unit.?.addr_base + debug_addr_index;
353 if (offset >= context.debug_addr.?.len) return error.InvalidExpression;
354 const value = mem.readIntSliceNative(usize, context.debug_addr.?[offset..][0..@sizeOf(usize)]);
355 try self.stack.append(allocator, .{ .generic = value });
356 },
357
358 // 2.5.1.2: Register Values
359 OP.fbreg => {
360 if (context.compile_unit == null) return error.IncompleteExpressionContext;
361 if (context.compile_unit.?.frame_base == null) return error.IncompleteExpressionContext;
362
363 const offset: i64 = @intCast((try readOperand(stream, opcode, context)).?.generic);
364 _ = offset;
365
366 switch (context.compile_unit.?.frame_base.?.*) {
367 .ExprLoc => {
368 // TODO: Run this expression in a nested stack machine
369 return error.UnimplementedOpcode;
370 },
371 .LocListOffset => {
372 // TODO: Read value from .debug_loclists
373 return error.UnimplementedOpcode;
374 },
375 .SecOffset => {
376 // TODO: Read value from .debug_loclists
377 return error.UnimplementedOpcode;
378 },
379 else => return error.InvalidFrameBase,
380 }
381 },
382 OP.breg0...OP.breg31,
383 OP.bregx,
384 => {
385 if (context.thread_context == null) return error.IncompleteExpressionContext;
386
387 const base_register = (try readOperand(stream, opcode, context)).?.base_register;
388 var value: i64 = @intCast(mem.readIntSliceNative(usize, try abi.regBytes(
389 context.thread_context.?,
390 base_register.base_register,
391 context.reg_context,
392 )));
393 value += base_register.offset;
394 try self.stack.append(allocator, .{ .generic = @intCast(value) });
395 },
396 OP.regval_type => {
397 const register_type = (try readOperand(stream, opcode, context)).?.register_type;
398 const value = mem.readIntSliceNative(usize, try abi.regBytes(
399 context.thread_context.?,
400 register_type.register,
401 context.reg_context,
402 ));
403 try self.stack.append(allocator, .{
404 .regval_type = .{
405 .type_offset = register_type.type_offset,
406 .type_size = @sizeOf(addr_type),
407 .value = value,
408 },
409 });
410 },
411
412 // 2.5.1.3: Stack Operations
413 OP.dup => {
414 if (self.stack.items.len == 0) return error.InvalidExpression;
415 try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1]);
416 },
417 OP.drop => {
418 _ = self.stack.pop();
419 },
420 OP.pick, OP.over => {
421 const stack_index = if (opcode == OP.over) 1 else (try readOperand(stream, opcode, context)).?.generic;
422 if (stack_index >= self.stack.items.len) return error.InvalidExpression;
423 try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1 - stack_index]);
424 },
425 OP.swap => {
426 if (self.stack.items.len < 2) return error.InvalidExpression;
427 mem.swap(Value, &self.stack.items[self.stack.items.len - 1], &self.stack.items[self.stack.items.len - 2]);
428 },
429 OP.rot => {
430 if (self.stack.items.len < 3) return error.InvalidExpression;
431 const first = self.stack.items[self.stack.items.len - 1];
432 self.stack.items[self.stack.items.len - 1] = self.stack.items[self.stack.items.len - 2];
433 self.stack.items[self.stack.items.len - 2] = self.stack.items[self.stack.items.len - 3];
434 self.stack.items[self.stack.items.len - 3] = first;
435 },
436 OP.deref,
437 OP.xderef,
438 OP.deref_size,
439 OP.xderef_size,
440 OP.deref_type,
441 OP.xderef_type,
442 => {
443 if (self.stack.items.len == 0) return error.InvalidExpression;
444 var addr = try self.stack.items[self.stack.items.len - 1].asIntegral();
445 const addr_space_identifier: ?usize = switch (opcode) {
446 OP.xderef,
447 OP.xderef_size,
448 OP.xderef_type,
449 => blk: {
450 _ = self.stack.pop();
451 if (self.stack.items.len == 0) return error.InvalidExpression;
452 break :blk try self.stack.items[self.stack.items.len - 1].asIntegral();
453 },
454 else => null,
455 };
456
457 // Usage of addr_space_identifier in the address calculation is implementation defined.
458 // This code will need to be updated to handle any architectures that utilize this.
459 _ = addr_space_identifier;
460
461 if (context.isValidMemory) |isValidMemory| if (!isValidMemory(addr)) return error.InvalidExpression;
462
463 const operand = try readOperand(stream, opcode, context);
464 const size = switch (opcode) {
465 OP.deref,
466 OP.xderef,
467 => @sizeOf(addr_type),
468 OP.deref_size,
469 OP.xderef_size,
470 => operand.?.type_size,
471 OP.deref_type,
472 OP.xderef_type,
473 => operand.?.deref_type.size,
474 else => unreachable,
475 };
476
477 const value: addr_type = std.math.cast(addr_type, @as(u64, switch (size) {
478 1 => @as(*const u8, @ptrFromInt(addr)).*,
479 2 => @as(*const u16, @ptrFromInt(addr)).*,
480 4 => @as(*const u32, @ptrFromInt(addr)).*,
481 8 => @as(*const u64, @ptrFromInt(addr)).*,
482 else => return error.InvalidExpression,
483 })) orelse return error.InvalidExpression;
484
485 switch (opcode) {
486 OP.deref_type,
487 OP.xderef_type,
488 => {
489 self.stack.items[self.stack.items.len - 1] = .{
490 .regval_type = .{
491 .type_offset = operand.?.deref_type.type_offset,
492 .type_size = operand.?.deref_type.size,
493 .value = value,
494 },
495 };
496 },
497 else => {
498 self.stack.items[self.stack.items.len - 1] = .{ .generic = value };
499 },
500 }
501 },
502 OP.push_object_address => {
503 // In sub-expressions, `push_object_address` is not meaningful (as per the
504 // spec), so treat it like a nop
505 if (!context.entry_value_context) {
506 if (context.object_address == null) return error.IncompleteExpressionContext;
507 try self.stack.append(allocator, .{ .generic = @intFromPtr(context.object_address.?) });
508 }
509 },
510 OP.form_tls_address => {
511 return error.UnimplementedOpcode;
512 },
513 OP.call_frame_cfa => {
514 if (context.cfa) |cfa| {
515 try self.stack.append(allocator, .{ .generic = cfa });
516 } else return error.IncompleteExpressionContext;
517 },
518
519 // 2.5.1.4: Arithmetic and Logical Operations
520 OP.abs => {
521 if (self.stack.items.len == 0) return error.InvalidExpression;
522 const value: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
523 self.stack.items[self.stack.items.len - 1] = .{
524 .generic = std.math.absCast(value),
525 };
526 },
527 OP.@"and" => {
528 if (self.stack.items.len < 2) return error.InvalidExpression;
529 const a = try self.stack.pop().asIntegral();
530 self.stack.items[self.stack.items.len - 1] = .{
531 .generic = a & try self.stack.items[self.stack.items.len - 1].asIntegral(),
532 };
533 },
534 OP.div => {
535 if (self.stack.items.len < 2) return error.InvalidExpression;
536 const a: isize = @bitCast(try self.stack.pop().asIntegral());
537 const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
538 self.stack.items[self.stack.items.len - 1] = .{
539 .generic = @bitCast(try std.math.divTrunc(isize, b, a)),
540 };
541 },
542 OP.minus => {
543 if (self.stack.items.len < 2) return error.InvalidExpression;
544 const b = try self.stack.pop().asIntegral();
545 self.stack.items[self.stack.items.len - 1] = .{
546 .generic = try std.math.sub(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b),
547 };
548 },
549 OP.mod => {
550 if (self.stack.items.len < 2) return error.InvalidExpression;
551 const a: isize = @bitCast(try self.stack.pop().asIntegral());
552 const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
553 self.stack.items[self.stack.items.len - 1] = .{
554 .generic = @bitCast(@mod(b, a)),
555 };
556 },
557 OP.mul => {
558 if (self.stack.items.len < 2) return error.InvalidExpression;
559 const a: isize = @bitCast(try self.stack.pop().asIntegral());
560 const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
561 self.stack.items[self.stack.items.len - 1] = .{
562 .generic = @bitCast(@mulWithOverflow(a, b)[0]),
563 };
564 },
565 OP.neg => {
566 if (self.stack.items.len == 0) return error.InvalidExpression;
567 self.stack.items[self.stack.items.len - 1] = .{
568 .generic = @bitCast(
569 try std.math.negate(
570 @as(isize, @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral())),
571 ),
572 ),
573 };
574 },
575 OP.not => {
576 if (self.stack.items.len == 0) return error.InvalidExpression;
577 self.stack.items[self.stack.items.len - 1] = .{
578 .generic = ~try self.stack.items[self.stack.items.len - 1].asIntegral(),
579 };
580 },
581 OP.@"or" => {
582 if (self.stack.items.len < 2) return error.InvalidExpression;
583 const a = try self.stack.pop().asIntegral();
584 self.stack.items[self.stack.items.len - 1] = .{
585 .generic = a | try self.stack.items[self.stack.items.len - 1].asIntegral(),
586 };
587 },
588 OP.plus => {
589 if (self.stack.items.len < 2) return error.InvalidExpression;
590 const b = try self.stack.pop().asIntegral();
591 self.stack.items[self.stack.items.len - 1] = .{
592 .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b),
593 };
594 },
595 OP.plus_uconst => {
596 if (self.stack.items.len == 0) return error.InvalidExpression;
597 const constant = (try readOperand(stream, opcode, context)).?.generic;
598 self.stack.items[self.stack.items.len - 1] = .{
599 .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), constant),
600 };
601 },
602 OP.shl => {
603 if (self.stack.items.len < 2) return error.InvalidExpression;
604 const a = try self.stack.pop().asIntegral();
605 const b = try self.stack.items[self.stack.items.len - 1].asIntegral();
606 self.stack.items[self.stack.items.len - 1] = .{
607 .generic = std.math.shl(usize, b, a),
608 };
609 },
610 OP.shr => {
611 if (self.stack.items.len < 2) return error.InvalidExpression;
612 const a = try self.stack.pop().asIntegral();
613 const b = try self.stack.items[self.stack.items.len - 1].asIntegral();
614 self.stack.items[self.stack.items.len - 1] = .{
615 .generic = std.math.shr(usize, b, a),
616 };
617 },
618 OP.shra => {
619 if (self.stack.items.len < 2) return error.InvalidExpression;
620 const a = try self.stack.pop().asIntegral();
621 const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
622 self.stack.items[self.stack.items.len - 1] = .{
623 .generic = @bitCast(std.math.shr(isize, b, a)),
624 };
625 },
626 OP.xor => {
627 if (self.stack.items.len < 2) return error.InvalidExpression;
628 const a = try self.stack.pop().asIntegral();
629 self.stack.items[self.stack.items.len - 1] = .{
630 .generic = a ^ try self.stack.items[self.stack.items.len - 1].asIntegral(),
631 };
632 },
633
634 // 2.5.1.5: Control Flow Operations
635 OP.le,
636 OP.ge,
637 OP.eq,
638 OP.lt,
639 OP.gt,
640 OP.ne,
641 => {
642 if (self.stack.items.len < 2) return error.InvalidExpression;
643 const a = self.stack.pop();
644 const b = self.stack.items[self.stack.items.len - 1];
645
646 if (a == .generic and b == .generic) {
647 const a_int: isize = @bitCast(a.asIntegral() catch unreachable);
648 const b_int: isize = @bitCast(b.asIntegral() catch unreachable);
649 const result = @intFromBool(switch (opcode) {
650 OP.le => b_int <= a_int,
651 OP.ge => b_int >= a_int,
652 OP.eq => b_int == a_int,
653 OP.lt => b_int < a_int,
654 OP.gt => b_int > a_int,
655 OP.ne => b_int != a_int,
656 else => unreachable,
657 });
658
659 self.stack.items[self.stack.items.len - 1] = .{ .generic = result };
660 } else {
661 // TODO: Load the types referenced by these values, find their comparison operator, and run it
662 return error.UnimplementedTypedComparison;
663 }
664 },
665 OP.skip, OP.bra => {
666 const branch_offset = (try readOperand(stream, opcode, context)).?.branch_offset;
667 const condition = if (opcode == OP.bra) blk: {
668 if (self.stack.items.len == 0) return error.InvalidExpression;
669 break :blk try self.stack.pop().asIntegral() != 0;
670 } else true;
671
672 if (condition) {
673 const new_pos = std.math.cast(
674 usize,
675 try std.math.add(isize, @as(isize, @intCast(stream.pos)), branch_offset),
676 ) orelse return error.InvalidExpression;
677
678 if (new_pos < 0 or new_pos > stream.buffer.len) return error.InvalidExpression;
679 stream.pos = new_pos;
680 }
681 },
682 OP.call2,
683 OP.call4,
684 OP.call_ref,
685 => {
686 const debug_info_offset = (try readOperand(stream, opcode, context)).?.generic;
687 _ = debug_info_offset;
688
689 // TODO: Load a DIE entry at debug_info_offset in a .debug_info section (the spec says that it
690 // can be in a separate exe / shared object from the one containing this expression).
691 // Transfer control to the DW_AT_location attribute, with the current stack as input.
692
693 return error.UnimplementedExpressionCall;
694 },
695
696 // 2.5.1.6: Type Conversions
697 OP.convert => {
698 if (self.stack.items.len == 0) return error.InvalidExpression;
699 const type_offset = (try readOperand(stream, opcode, context)).?.generic;
700
701 // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size
702 const value = self.stack.items[self.stack.items.len - 1];
703 if (type_offset == 0) {
704 self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() };
705 } else {
706 // TODO: Load the DW_TAG_base_type entry in context.compile_unit, find a conversion operator
707 // from the old type to the new type, run it.
708 return error.UnimplementedTypeConversion;
709 }
710 },
711 OP.reinterpret => {
712 if (self.stack.items.len == 0) return error.InvalidExpression;
713 const type_offset = (try readOperand(stream, opcode, context)).?.generic;
714
715 // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size
716 const value = self.stack.items[self.stack.items.len - 1];
717 if (type_offset == 0) {
718 self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() };
719 } else {
720 self.stack.items[self.stack.items.len - 1] = switch (value) {
721 .generic => |v| .{
722 .regval_type = .{
723 .type_offset = type_offset,
724 .type_size = @sizeOf(addr_type),
725 .value = v,
726 },
727 },
728 .regval_type => |r| .{
729 .regval_type = .{
730 .type_offset = type_offset,
731 .type_size = r.type_size,
732 .value = r.value,
733 },
734 },
735 .const_type => |c| .{
736 .const_type = .{
737 .type_offset = type_offset,
738 .value_bytes = c.value_bytes,
739 },
740 },
741 };
742 }
743 },
744
745 // 2.5.1.7: Special Operations
746 OP.nop => {},
747 OP.entry_value => {
748 const block = (try readOperand(stream, opcode, context)).?.block;
749 if (block.len == 0) return error.InvalidSubExpression;
750
751 // TODO: The spec states that this sub-expression needs to observe the state (ie. registers)
752 // as it was upon entering the current subprogram. If this isn't being called at the
753 // end of a frame unwind operation, an additional ThreadContext with this state will be needed.
754
755 if (isOpcodeRegisterLocation(block[0])) {
756 if (context.thread_context == null) return error.IncompleteExpressionContext;
757
758 var block_stream = std.io.fixedBufferStream(block);
759 const register = (try readOperand(&block_stream, block[0], context)).?.register;
760 const value = mem.readIntSliceNative(usize, try abi.regBytes(context.thread_context.?, register, context.reg_context));
761 try self.stack.append(allocator, .{ .generic = value });
762 } else {
763 var stack_machine: Self = .{};
764 defer stack_machine.deinit(allocator);
765
766 var sub_context = context;
767 sub_context.entry_value_context = true;
768 const result = try stack_machine.run(block, allocator, sub_context, null);
769 try self.stack.append(allocator, result orelse return error.InvalidSubExpression);
770 }
771 },
772
773 // These have already been handled by readOperand
774 OP.lo_user...OP.hi_user => unreachable,
775 else => {
776 //std.debug.print("Unknown DWARF expression opcode: {x}\n", .{opcode});
777 return error.UnknownExpressionOpcode;
778 },
779 }
780
781 return stream.pos < stream.buffer.len;
782 }
783 };
784}
785
786pub fn Builder(comptime options: ExpressionOptions) type {
787 const addr_type = switch (options.addr_size) {
788 2 => u16,
789 4 => u32,
790 8 => u64,
791 else => @compileError("Unsupported address size of " ++ options.addr_size),
792 };
793
794 return struct {
795 /// Zero-operand instructions
796 pub fn writeOpcode(writer: anytype, comptime opcode: u8) !void {
797 if (options.call_frame_context and !comptime isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode;
798 switch (opcode) {
799 OP.dup,
800 OP.drop,
801 OP.over,
802 OP.swap,
803 OP.rot,
804 OP.deref,
805 OP.xderef,
806 OP.push_object_address,
807 OP.form_tls_address,
808 OP.call_frame_cfa,
809 OP.abs,
810 OP.@"and",
811 OP.div,
812 OP.minus,
813 OP.mod,
814 OP.mul,
815 OP.neg,
816 OP.not,
817 OP.@"or",
818 OP.plus,
819 OP.shl,
820 OP.shr,
821 OP.shra,
822 OP.xor,
823 OP.le,
824 OP.ge,
825 OP.eq,
826 OP.lt,
827 OP.gt,
828 OP.ne,
829 OP.nop,
830 OP.stack_value,
831 => try writer.writeByte(opcode),
832 else => @compileError("This opcode requires operands, use `write<Opcode>()` instead"),
833 }
834 }
835
836 // 2.5.1.1: Literal Encodings
837 pub fn writeLiteral(writer: anytype, literal: u8) !void {
838 switch (literal) {
839 0...31 => |n| try writer.writeByte(n + OP.lit0),
840 else => return error.InvalidLiteral,
841 }
842 }
843
844 pub fn writeConst(writer: anytype, comptime T: type, value: T) !void {
845 if (@typeInfo(T) != .Int) @compileError("Constants must be integers");
846
847 switch (T) {
848 u8, i8, u16, i16, u32, i32, u64, i64 => {
849 try writer.writeByte(switch (T) {
850 u8 => OP.const1u,
851 i8 => OP.const1s,
852 u16 => OP.const2u,
853 i16 => OP.const2s,
854 u32 => OP.const4u,
855 i32 => OP.const4s,
856 u64 => OP.const8u,
857 i64 => OP.const8s,
858 else => unreachable,
859 });
860
861 try writer.writeInt(T, value, options.endian);
862 },
863 else => switch (@typeInfo(T).Int.signedness) {
864 .unsigned => {
865 try writer.writeByte(OP.constu);
866 try leb.writeULEB128(writer, value);
867 },
868 .signed => {
869 try writer.writeByte(OP.consts);
870 try leb.writeILEB128(writer, value);
871 },
872 },
873 }
874 }
875
876 pub fn writeConstx(writer: anytype, debug_addr_offset: anytype) !void {
877 try writer.writeByte(OP.constx);
878 try leb.writeULEB128(writer, debug_addr_offset);
879 }
880
881 pub fn writeConstType(writer: anytype, die_offset: anytype, value_bytes: []const u8) !void {
882 if (options.call_frame_context) return error.InvalidCFAOpcode;
883 if (value_bytes.len > 0xff) return error.InvalidTypeLength;
884 try writer.writeByte(OP.const_type);
885 try leb.writeULEB128(writer, die_offset);
886 try writer.writeByte(@intCast(value_bytes.len));
887 try writer.writeAll(value_bytes);
888 }
889
890 pub fn writeAddr(writer: anytype, value: addr_type) !void {
891 try writer.writeByte(OP.addr);
892 try writer.writeInt(addr_type, value, options.endian);
893 }
894
895 pub fn writeAddrx(writer: anytype, debug_addr_offset: anytype) !void {
896 if (options.call_frame_context) return error.InvalidCFAOpcode;
897 try writer.writeByte(OP.addrx);
898 try leb.writeULEB128(writer, debug_addr_offset);
899 }
900
901 // 2.5.1.2: Register Values
902 pub fn writeFbreg(writer: anytype, offset: anytype) !void {
903 try writer.writeByte(OP.fbreg);
904 try leb.writeILEB128(writer, offset);
905 }
906
907 pub fn writeBreg(writer: anytype, register: u8, offset: anytype) !void {
908 if (register > 31) return error.InvalidRegister;
909 try writer.writeByte(OP.breg0 + register);
910 try leb.writeILEB128(writer, offset);
911 }
912
913 pub fn writeBregx(writer: anytype, register: anytype, offset: anytype) !void {
914 try writer.writeByte(OP.bregx);
915 try leb.writeULEB128(writer, register);
916 try leb.writeILEB128(writer, offset);
917 }
918
919 pub fn writeRegvalType(writer: anytype, register: anytype, offset: anytype) !void {
920 if (options.call_frame_context) return error.InvalidCFAOpcode;
921 try writer.writeByte(OP.regval_type);
922 try leb.writeULEB128(writer, register);
923 try leb.writeULEB128(writer, offset);
924 }
925
926 // 2.5.1.3: Stack Operations
927 pub fn writePick(writer: anytype, index: u8) !void {
928 try writer.writeByte(OP.pick);
929 try writer.writeByte(index);
930 }
931
932 pub fn writeDerefSize(writer: anytype, size: u8) !void {
933 try writer.writeByte(OP.deref_size);
934 try writer.writeByte(size);
935 }
936
937 pub fn writeXDerefSize(writer: anytype, size: u8) !void {
938 try writer.writeByte(OP.xderef_size);
939 try writer.writeByte(size);
940 }
941
942 pub fn writeDerefType(writer: anytype, size: u8, die_offset: anytype) !void {
943 if (options.call_frame_context) return error.InvalidCFAOpcode;
944 try writer.writeByte(OP.deref_type);
945 try writer.writeByte(size);
946 try leb.writeULEB128(writer, die_offset);
947 }
948
949 pub fn writeXDerefType(writer: anytype, size: u8, die_offset: anytype) !void {
950 try writer.writeByte(OP.xderef_type);
951 try writer.writeByte(size);
952 try leb.writeULEB128(writer, die_offset);
953 }
954
955 // 2.5.1.4: Arithmetic and Logical Operations
956
957 pub fn writePlusUconst(writer: anytype, uint_value: anytype) !void {
958 try writer.writeByte(OP.plus_uconst);
959 try leb.writeULEB128(writer, uint_value);
960 }
961
962 // 2.5.1.5: Control Flow Operations
963
964 pub fn writeSkip(writer: anytype, offset: i16) !void {
965 try writer.writeByte(OP.skip);
966 try writer.writeInt(i16, offset, options.endian);
967 }
968
969 pub fn writeBra(writer: anytype, offset: i16) !void {
970 try writer.writeByte(OP.bra);
971 try writer.writeInt(i16, offset, options.endian);
972 }
973
974 pub fn writeCall(writer: anytype, comptime T: type, offset: T) !void {
975 if (options.call_frame_context) return error.InvalidCFAOpcode;
976 switch (T) {
977 u16 => try writer.writeByte(OP.call2),
978 u32 => try writer.writeByte(OP.call4),
979 else => @compileError("Call operand must be a 2 or 4 byte offset"),
980 }
981
982 try writer.writeInt(T, offset, options.endian);
983 }
984
985 pub fn writeCallRef(writer: anytype, comptime is_64: bool, value: if (is_64) u64 else u32) !void {
986 if (options.call_frame_context) return error.InvalidCFAOpcode;
987 try writer.writeByte(OP.call_ref);
988 try writer.writeInt(if (is_64) u64 else u32, value, options.endian);
989 }
990
991 pub fn writeConvert(writer: anytype, die_offset: anytype) !void {
992 if (options.call_frame_context) return error.InvalidCFAOpcode;
993 try writer.writeByte(OP.convert);
994 try leb.writeULEB128(writer, die_offset);
995 }
996
997 pub fn writeReinterpret(writer: anytype, die_offset: anytype) !void {
998 if (options.call_frame_context) return error.InvalidCFAOpcode;
999 try writer.writeByte(OP.reinterpret);
1000 try leb.writeULEB128(writer, die_offset);
1001 }
1002
1003 // 2.5.1.7: Special Operations
1004
1005 pub fn writeEntryValue(writer: anytype, expression: []const u8) !void {
1006 try writer.writeByte(OP.entry_value);
1007 try leb.writeULEB128(writer, expression.len);
1008 try writer.writeAll(expression);
1009 }
1010
1011 // 2.6: Location Descriptions
1012 pub fn writeReg(writer: anytype, register: u8) !void {
1013 try writer.writeByte(OP.reg0 + register);
1014 }
1015
1016 pub fn writeRegx(writer: anytype, register: anytype) !void {
1017 try writer.writeByte(OP.regx);
1018 try leb.writeULEB128(writer, register);
1019 }
1020
1021 pub fn writeImplicitValue(writer: anytype, value_bytes: []const u8) !void {
1022 try writer.writeByte(OP.implicit_value);
1023 try leb.writeULEB128(writer, value_bytes.len);
1024 try writer.writeAll(value_bytes);
1025 }
1026 };
1027}
1028
1029// Certain opcodes are not allowed in a CFA context, see 6.4.2
1030fn isOpcodeValidInCFA(opcode: u8) bool {
1031 return switch (opcode) {
1032 OP.addrx,
1033 OP.call2,
1034 OP.call4,
1035 OP.call_ref,
1036 OP.const_type,
1037 OP.constx,
1038 OP.convert,
1039 OP.deref_type,
1040 OP.regval_type,
1041 OP.reinterpret,
1042 OP.push_object_address,
1043 OP.call_frame_cfa,
1044 => false,
1045 else => true,
1046 };
1047}
1048
1049fn isOpcodeRegisterLocation(opcode: u8) bool {
1050 return switch (opcode) {
1051 OP.reg0...OP.reg31, OP.regx => true,
1052 else => false,
1053 };
1054}
1055
1056const testing = std.testing;
1057test "DWARF expressions" {
1058 const allocator = std.testing.allocator;
1059
1060 const options = ExpressionOptions{};
1061 var stack_machine = StackMachine(options){};
1062 defer stack_machine.deinit(allocator);
1063
1064 const b = Builder(options);
1065
1066 var program = std.ArrayList(u8).init(allocator);
1067 defer program.deinit();
1068
1069 const writer = program.writer();
1070
1071 // Literals
1072 {
1073 const context = ExpressionContext{};
1074 for (0..32) |i| {
1075 try b.writeLiteral(writer, @intCast(i));
1076 }
1077
1078 _ = try stack_machine.run(program.items, allocator, context, 0);
1079
1080 for (0..32) |i| {
1081 const expected = 31 - i;
1082 try testing.expectEqual(expected, stack_machine.stack.popOrNull().?.generic);
1083 }
1084 }
1085
1086 // Constants
1087 {
1088 stack_machine.reset();
1089 program.clearRetainingCapacity();
1090
1091 const input = [_]comptime_int{
1092 1,
1093 -1,
1094 @as(usize, @truncate(0x0fff)),
1095 @as(isize, @truncate(-0x0fff)),
1096 @as(usize, @truncate(0x0fffffff)),
1097 @as(isize, @truncate(-0x0fffffff)),
1098 @as(usize, @truncate(0x0fffffffffffffff)),
1099 @as(isize, @truncate(-0x0fffffffffffffff)),
1100 @as(usize, @truncate(0x8000000)),
1101 @as(isize, @truncate(-0x8000000)),
1102 @as(usize, @truncate(0x12345678_12345678)),
1103 @as(usize, @truncate(0xffffffff_ffffffff)),
1104 @as(usize, @truncate(0xeeeeeeee_eeeeeeee)),
1105 };
1106
1107 try b.writeConst(writer, u8, input[0]);
1108 try b.writeConst(writer, i8, input[1]);
1109 try b.writeConst(writer, u16, input[2]);
1110 try b.writeConst(writer, i16, input[3]);
1111 try b.writeConst(writer, u32, input[4]);
1112 try b.writeConst(writer, i32, input[5]);
1113 try b.writeConst(writer, u64, input[6]);
1114 try b.writeConst(writer, i64, input[7]);
1115 try b.writeConst(writer, u28, input[8]);
1116 try b.writeConst(writer, i28, input[9]);
1117 try b.writeAddr(writer, input[10]);
1118
1119 var mock_compile_unit: dwarf.CompileUnit = undefined;
1120 mock_compile_unit.addr_base = 1;
1121
1122 var mock_debug_addr = std.ArrayList(u8).init(allocator);
1123 defer mock_debug_addr.deinit();
1124
1125 try mock_debug_addr.writer().writeIntNative(u16, 0);
1126 try mock_debug_addr.writer().writeIntNative(usize, input[11]);
1127 try mock_debug_addr.writer().writeIntNative(usize, input[12]);
1128
1129 const context = ExpressionContext{
1130 .compile_unit = &mock_compile_unit,
1131 .debug_addr = mock_debug_addr.items,
1132 };
1133
1134 try b.writeConstx(writer, @as(usize, 1));
1135 try b.writeAddrx(writer, @as(usize, 1 + @sizeOf(usize)));
1136
1137 const die_offset: usize = @truncate(0xaabbccdd);
1138 const type_bytes: []const u8 = &.{ 1, 2, 3, 4 };
1139 try b.writeConstType(writer, die_offset, type_bytes);
1140
1141 _ = try stack_machine.run(program.items, allocator, context, 0);
1142
1143 const const_type = stack_machine.stack.popOrNull().?.const_type;
1144 try testing.expectEqual(die_offset, const_type.type_offset);
1145 try testing.expectEqualSlices(u8, type_bytes, const_type.value_bytes);
1146
1147 const expected = .{
1148 .{ usize, input[12], usize },
1149 .{ usize, input[11], usize },
1150 .{ usize, input[10], usize },
1151 .{ isize, input[9], isize },
1152 .{ usize, input[8], usize },
1153 .{ isize, input[7], isize },
1154 .{ usize, input[6], usize },
1155 .{ isize, input[5], isize },
1156 .{ usize, input[4], usize },
1157 .{ isize, input[3], isize },
1158 .{ usize, input[2], usize },
1159 .{ isize, input[1], isize },
1160 .{ usize, input[0], usize },
1161 };
1162
1163 inline for (expected) |e| {
1164 try testing.expectEqual(@as(e[0], e[1]), @as(e[2], @bitCast(stack_machine.stack.popOrNull().?.generic)));
1165 }
1166 }
1167
1168 // Register values
1169 if (@sizeOf(std.debug.ThreadContext) != 0) {
1170 stack_machine.reset();
1171 program.clearRetainingCapacity();
1172
1173 const reg_context = abi.RegisterContext{
1174 .eh_frame = true,
1175 .is_macho = builtin.os.tag == .macos,
1176 };
1177 var thread_context: std.debug.ThreadContext = undefined;
1178 std.debug.relocateContext(&thread_context);
1179 const context = ExpressionContext{
1180 .thread_context = &thread_context,
1181 .reg_context = reg_context,
1182 };
1183
1184 // Only test register operations on arch / os that have them implemented
1185 if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| {
1186
1187 // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it
1188
1189 mem.writeIntSliceNative(usize, reg_bytes, 0xee);
1190 (try abi.regValueNative(usize, &thread_context, abi.fpRegNum(reg_context), reg_context)).* = 1;
1191 (try abi.regValueNative(usize, &thread_context, abi.spRegNum(reg_context), reg_context)).* = 2;
1192 (try abi.regValueNative(usize, &thread_context, abi.ipRegNum(), reg_context)).* = 3;
1193
1194 try b.writeBreg(writer, abi.fpRegNum(reg_context), @as(usize, 100));
1195 try b.writeBreg(writer, abi.spRegNum(reg_context), @as(usize, 200));
1196 try b.writeBregx(writer, abi.ipRegNum(), @as(usize, 300));
1197 try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400));
1198
1199 _ = try stack_machine.run(program.items, allocator, context, 0);
1200
1201 const regval_type = stack_machine.stack.popOrNull().?.regval_type;
1202 try testing.expectEqual(@as(usize, 400), regval_type.type_offset);
1203 try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size);
1204 try testing.expectEqual(@as(usize, 0xee), regval_type.value);
1205
1206 try testing.expectEqual(@as(usize, 303), stack_machine.stack.popOrNull().?.generic);
1207 try testing.expectEqual(@as(usize, 202), stack_machine.stack.popOrNull().?.generic);
1208 try testing.expectEqual(@as(usize, 101), stack_machine.stack.popOrNull().?.generic);
1209 } else |err| {
1210 switch (err) {
1211 error.UnimplementedArch,
1212 error.UnimplementedOs,
1213 error.ThreadContextNotSupported,
1214 => {},
1215 else => return err,
1216 }
1217 }
1218 }
1219
1220 // Stack operations
1221 {
1222 var context = ExpressionContext{};
1223
1224 stack_machine.reset();
1225 program.clearRetainingCapacity();
1226 try b.writeConst(writer, u8, 1);
1227 try b.writeOpcode(writer, OP.dup);
1228 _ = try stack_machine.run(program.items, allocator, context, null);
1229 try testing.expectEqual(@as(usize, 1), stack_machine.stack.popOrNull().?.generic);
1230 try testing.expectEqual(@as(usize, 1), stack_machine.stack.popOrNull().?.generic);
1231
1232 stack_machine.reset();
1233 program.clearRetainingCapacity();
1234 try b.writeConst(writer, u8, 1);
1235 try b.writeOpcode(writer, OP.drop);
1236 _ = try stack_machine.run(program.items, allocator, context, null);
1237 try testing.expect(stack_machine.stack.popOrNull() == null);
1238
1239 stack_machine.reset();
1240 program.clearRetainingCapacity();
1241 try b.writeConst(writer, u8, 4);
1242 try b.writeConst(writer, u8, 5);
1243 try b.writeConst(writer, u8, 6);
1244 try b.writePick(writer, 2);
1245 _ = try stack_machine.run(program.items, allocator, context, null);
1246 try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic);
1247
1248 stack_machine.reset();
1249 program.clearRetainingCapacity();
1250 try b.writeConst(writer, u8, 4);
1251 try b.writeConst(writer, u8, 5);
1252 try b.writeConst(writer, u8, 6);
1253 try b.writeOpcode(writer, OP.over);
1254 _ = try stack_machine.run(program.items, allocator, context, null);
1255 try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic);
1256
1257 stack_machine.reset();
1258 program.clearRetainingCapacity();
1259 try b.writeConst(writer, u8, 5);
1260 try b.writeConst(writer, u8, 6);
1261 try b.writeOpcode(writer, OP.swap);
1262 _ = try stack_machine.run(program.items, allocator, context, null);
1263 try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic);
1264 try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic);
1265
1266 stack_machine.reset();
1267 program.clearRetainingCapacity();
1268 try b.writeConst(writer, u8, 4);
1269 try b.writeConst(writer, u8, 5);
1270 try b.writeConst(writer, u8, 6);
1271 try b.writeOpcode(writer, OP.rot);
1272 _ = try stack_machine.run(program.items, allocator, context, null);
1273 try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic);
1274 try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic);
1275 try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic);
1276
1277 const deref_target: usize = @truncate(0xffeeffee_ffeeffee);
1278
1279 stack_machine.reset();
1280 program.clearRetainingCapacity();
1281 try b.writeAddr(writer, @intFromPtr(&deref_target));
1282 try b.writeOpcode(writer, OP.deref);
1283 _ = try stack_machine.run(program.items, allocator, context, null);
1284 try testing.expectEqual(deref_target, stack_machine.stack.popOrNull().?.generic);
1285
1286 stack_machine.reset();
1287 program.clearRetainingCapacity();
1288 try b.writeLiteral(writer, 0);
1289 try b.writeAddr(writer, @intFromPtr(&deref_target));
1290 try b.writeOpcode(writer, OP.xderef);
1291 _ = try stack_machine.run(program.items, allocator, context, null);
1292 try testing.expectEqual(deref_target, stack_machine.stack.popOrNull().?.generic);
1293
1294 stack_machine.reset();
1295 program.clearRetainingCapacity();
1296 try b.writeAddr(writer, @intFromPtr(&deref_target));
1297 try b.writeDerefSize(writer, 1);
1298 _ = try stack_machine.run(program.items, allocator, context, null);
1299 try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), stack_machine.stack.popOrNull().?.generic);
1300
1301 stack_machine.reset();
1302 program.clearRetainingCapacity();
1303 try b.writeLiteral(writer, 0);
1304 try b.writeAddr(writer, @intFromPtr(&deref_target));
1305 try b.writeXDerefSize(writer, 1);
1306 _ = try stack_machine.run(program.items, allocator, context, null);
1307 try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), stack_machine.stack.popOrNull().?.generic);
1308
1309 const type_offset: usize = @truncate(0xaabbaabb_aabbaabb);
1310
1311 stack_machine.reset();
1312 program.clearRetainingCapacity();
1313 try b.writeAddr(writer, @intFromPtr(&deref_target));
1314 try b.writeDerefType(writer, 1, type_offset);
1315 _ = try stack_machine.run(program.items, allocator, context, null);
1316 const deref_type = stack_machine.stack.popOrNull().?.regval_type;
1317 try testing.expectEqual(type_offset, deref_type.type_offset);
1318 try testing.expectEqual(@as(u8, 1), deref_type.type_size);
1319 try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), deref_type.value);
1320
1321 stack_machine.reset();
1322 program.clearRetainingCapacity();
1323 try b.writeLiteral(writer, 0);
1324 try b.writeAddr(writer, @intFromPtr(&deref_target));
1325 try b.writeXDerefType(writer, 1, type_offset);
1326 _ = try stack_machine.run(program.items, allocator, context, null);
1327 const xderef_type = stack_machine.stack.popOrNull().?.regval_type;
1328 try testing.expectEqual(type_offset, xderef_type.type_offset);
1329 try testing.expectEqual(@as(u8, 1), xderef_type.type_size);
1330 try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), xderef_type.value);
1331
1332 context.object_address = &deref_target;
1333
1334 stack_machine.reset();
1335 program.clearRetainingCapacity();
1336 try b.writeOpcode(writer, OP.push_object_address);
1337 _ = try stack_machine.run(program.items, allocator, context, null);
1338 try testing.expectEqual(@as(usize, @intFromPtr(context.object_address.?)), stack_machine.stack.popOrNull().?.generic);
1339
1340 // TODO: Test OP.form_tls_address
1341
1342 context.cfa = @truncate(0xccddccdd_ccddccdd);
1343
1344 stack_machine.reset();
1345 program.clearRetainingCapacity();
1346 try b.writeOpcode(writer, OP.call_frame_cfa);
1347 _ = try stack_machine.run(program.items, allocator, context, null);
1348 try testing.expectEqual(context.cfa.?, stack_machine.stack.popOrNull().?.generic);
1349 }
1350
1351 // Arithmetic and Logical Operations
1352 {
1353 var context = ExpressionContext{};
1354
1355 stack_machine.reset();
1356 program.clearRetainingCapacity();
1357 try b.writeConst(writer, i16, -4096);
1358 try b.writeOpcode(writer, OP.abs);
1359 _ = try stack_machine.run(program.items, allocator, context, null);
1360 try testing.expectEqual(@as(usize, 4096), stack_machine.stack.popOrNull().?.generic);
1361
1362 stack_machine.reset();
1363 program.clearRetainingCapacity();
1364 try b.writeConst(writer, u16, 0xff0f);
1365 try b.writeConst(writer, u16, 0xf0ff);
1366 try b.writeOpcode(writer, OP.@"and");
1367 _ = try stack_machine.run(program.items, allocator, context, null);
1368 try testing.expectEqual(@as(usize, 0xf00f), stack_machine.stack.popOrNull().?.generic);
1369
1370 stack_machine.reset();
1371 program.clearRetainingCapacity();
1372 try b.writeConst(writer, i16, -404);
1373 try b.writeConst(writer, i16, 100);
1374 try b.writeOpcode(writer, OP.div);
1375 _ = try stack_machine.run(program.items, allocator, context, null);
1376 try testing.expectEqual(@as(isize, -404 / 100), @as(isize, @bitCast(stack_machine.stack.popOrNull().?.generic)));
1377
1378 stack_machine.reset();
1379 program.clearRetainingCapacity();
1380 try b.writeConst(writer, u16, 200);
1381 try b.writeConst(writer, u16, 50);
1382 try b.writeOpcode(writer, OP.minus);
1383 _ = try stack_machine.run(program.items, allocator, context, null);
1384 try testing.expectEqual(@as(usize, 150), stack_machine.stack.popOrNull().?.generic);
1385
1386 stack_machine.reset();
1387 program.clearRetainingCapacity();
1388 try b.writeConst(writer, u16, 123);
1389 try b.writeConst(writer, u16, 100);
1390 try b.writeOpcode(writer, OP.mod);
1391 _ = try stack_machine.run(program.items, allocator, context, null);
1392 try testing.expectEqual(@as(usize, 23), stack_machine.stack.popOrNull().?.generic);
1393
1394 stack_machine.reset();
1395 program.clearRetainingCapacity();
1396 try b.writeConst(writer, u16, 0xff);
1397 try b.writeConst(writer, u16, 0xee);
1398 try b.writeOpcode(writer, OP.mul);
1399 _ = try stack_machine.run(program.items, allocator, context, null);
1400 try testing.expectEqual(@as(usize, 0xed12), stack_machine.stack.popOrNull().?.generic);
1401
1402 stack_machine.reset();
1403 program.clearRetainingCapacity();
1404 try b.writeConst(writer, u16, 5);
1405 try b.writeOpcode(writer, OP.neg);
1406 try b.writeConst(writer, i16, -6);
1407 try b.writeOpcode(writer, OP.neg);
1408 _ = try stack_machine.run(program.items, allocator, context, null);
1409 try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic);
1410 try testing.expectEqual(@as(isize, -5), @as(isize, @bitCast(stack_machine.stack.popOrNull().?.generic)));
1411
1412 stack_machine.reset();
1413 program.clearRetainingCapacity();
1414 try b.writeConst(writer, u16, 0xff0f);
1415 try b.writeOpcode(writer, OP.not);
1416 _ = try stack_machine.run(program.items, allocator, context, null);
1417 try testing.expectEqual(~@as(usize, 0xff0f), stack_machine.stack.popOrNull().?.generic);
1418
1419 stack_machine.reset();
1420 program.clearRetainingCapacity();
1421 try b.writeConst(writer, u16, 0xff0f);
1422 try b.writeConst(writer, u16, 0xf0ff);
1423 try b.writeOpcode(writer, OP.@"or");
1424 _ = try stack_machine.run(program.items, allocator, context, null);
1425 try testing.expectEqual(@as(usize, 0xffff), stack_machine.stack.popOrNull().?.generic);
1426
1427 stack_machine.reset();
1428 program.clearRetainingCapacity();
1429 try b.writeConst(writer, i16, 402);
1430 try b.writeConst(writer, i16, 100);
1431 try b.writeOpcode(writer, OP.plus);
1432 _ = try stack_machine.run(program.items, allocator, context, null);
1433 try testing.expectEqual(@as(usize, 502), stack_machine.stack.popOrNull().?.generic);
1434
1435 stack_machine.reset();
1436 program.clearRetainingCapacity();
1437 try b.writeConst(writer, u16, 4096);
1438 try b.writePlusUconst(writer, @as(usize, 8192));
1439 _ = try stack_machine.run(program.items, allocator, context, null);
1440 try testing.expectEqual(@as(usize, 4096 + 8192), stack_machine.stack.popOrNull().?.generic);
1441
1442 stack_machine.reset();
1443 program.clearRetainingCapacity();
1444 try b.writeConst(writer, u16, 0xfff);
1445 try b.writeConst(writer, u16, 1);
1446 try b.writeOpcode(writer, OP.shl);
1447 _ = try stack_machine.run(program.items, allocator, context, null);
1448 try testing.expectEqual(@as(usize, 0xfff << 1), stack_machine.stack.popOrNull().?.generic);
1449
1450 stack_machine.reset();
1451 program.clearRetainingCapacity();
1452 try b.writeConst(writer, u16, 0xfff);
1453 try b.writeConst(writer, u16, 1);
1454 try b.writeOpcode(writer, OP.shr);
1455 _ = try stack_machine.run(program.items, allocator, context, null);
1456 try testing.expectEqual(@as(usize, 0xfff >> 1), stack_machine.stack.popOrNull().?.generic);
1457
1458 stack_machine.reset();
1459 program.clearRetainingCapacity();
1460 try b.writeConst(writer, u16, 0xfff);
1461 try b.writeConst(writer, u16, 1);
1462 try b.writeOpcode(writer, OP.shr);
1463 _ = try stack_machine.run(program.items, allocator, context, null);
1464 try testing.expectEqual(@as(usize, @bitCast(@as(isize, 0xfff) >> 1)), stack_machine.stack.popOrNull().?.generic);
1465
1466 stack_machine.reset();
1467 program.clearRetainingCapacity();
1468 try b.writeConst(writer, u16, 0xf0ff);
1469 try b.writeConst(writer, u16, 0xff0f);
1470 try b.writeOpcode(writer, OP.xor);
1471 _ = try stack_machine.run(program.items, allocator, context, null);
1472 try testing.expectEqual(@as(usize, 0x0ff0), stack_machine.stack.popOrNull().?.generic);
1473 }
1474
1475 // Control Flow Operations
1476 {
1477 var context = ExpressionContext{};
1478 const expected = .{
1479 .{ OP.le, 1, 1, 0 },
1480 .{ OP.ge, 1, 0, 1 },
1481 .{ OP.eq, 1, 0, 0 },
1482 .{ OP.lt, 0, 1, 0 },
1483 .{ OP.gt, 0, 0, 1 },
1484 .{ OP.ne, 0, 1, 1 },
1485 };
1486
1487 inline for (expected) |e| {
1488 stack_machine.reset();
1489 program.clearRetainingCapacity();
1490
1491 try b.writeConst(writer, u16, 0);
1492 try b.writeConst(writer, u16, 0);
1493 try b.writeOpcode(writer, e[0]);
1494 try b.writeConst(writer, u16, 0);
1495 try b.writeConst(writer, u16, 1);
1496 try b.writeOpcode(writer, e[0]);
1497 try b.writeConst(writer, u16, 1);
1498 try b.writeConst(writer, u16, 0);
1499 try b.writeOpcode(writer, e[0]);
1500 _ = try stack_machine.run(program.items, allocator, context, null);
1501 try testing.expectEqual(@as(usize, e[3]), stack_machine.stack.popOrNull().?.generic);
1502 try testing.expectEqual(@as(usize, e[2]), stack_machine.stack.popOrNull().?.generic);
1503 try testing.expectEqual(@as(usize, e[1]), stack_machine.stack.popOrNull().?.generic);
1504 }
1505
1506 stack_machine.reset();
1507 program.clearRetainingCapacity();
1508 try b.writeLiteral(writer, 2);
1509 try b.writeSkip(writer, 1);
1510 try b.writeLiteral(writer, 3);
1511 _ = try stack_machine.run(program.items, allocator, context, null);
1512 try testing.expectEqual(@as(usize, 2), stack_machine.stack.popOrNull().?.generic);
1513
1514 stack_machine.reset();
1515 program.clearRetainingCapacity();
1516 try b.writeLiteral(writer, 2);
1517 try b.writeBra(writer, 1);
1518 try b.writeLiteral(writer, 3);
1519 try b.writeLiteral(writer, 0);
1520 try b.writeBra(writer, 1);
1521 try b.writeLiteral(writer, 4);
1522 try b.writeLiteral(writer, 5);
1523 _ = try stack_machine.run(program.items, allocator, context, null);
1524 try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic);
1525 try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic);
1526 try testing.expect(stack_machine.stack.popOrNull() == null);
1527
1528 // TODO: Test call2, call4, call_ref once implemented
1529
1530 }
1531
1532 // Type conversions
1533 {
1534 var context = ExpressionContext{};
1535 stack_machine.reset();
1536 program.clearRetainingCapacity();
1537
1538 // TODO: Test typed OP.convert once implemented
1539
1540 const value: usize = @truncate(0xffeeffee_ffeeffee);
1541 var value_bytes: [options.addr_size]u8 = undefined;
1542 mem.writeIntSliceNative(usize, &value_bytes, value);
1543
1544 // Convert to generic type
1545 stack_machine.reset();
1546 program.clearRetainingCapacity();
1547 try b.writeConstType(writer, @as(usize, 0), &value_bytes);
1548 try b.writeConvert(writer, @as(usize, 0));
1549 _ = try stack_machine.run(program.items, allocator, context, null);
1550 try testing.expectEqual(value, stack_machine.stack.popOrNull().?.generic);
1551
1552 // Reinterpret to generic type
1553 stack_machine.reset();
1554 program.clearRetainingCapacity();
1555 try b.writeConstType(writer, @as(usize, 0), &value_bytes);
1556 try b.writeReinterpret(writer, @as(usize, 0));
1557 _ = try stack_machine.run(program.items, allocator, context, null);
1558 try testing.expectEqual(value, stack_machine.stack.popOrNull().?.generic);
1559
1560 // Reinterpret to new type
1561 const die_offset: usize = 0xffee;
1562
1563 stack_machine.reset();
1564 program.clearRetainingCapacity();
1565 try b.writeConstType(writer, @as(usize, 0), &value_bytes);
1566 try b.writeReinterpret(writer, die_offset);
1567 _ = try stack_machine.run(program.items, allocator, context, null);
1568 const const_type = stack_machine.stack.popOrNull().?.const_type;
1569 try testing.expectEqual(die_offset, const_type.type_offset);
1570
1571 stack_machine.reset();
1572 program.clearRetainingCapacity();
1573 try b.writeLiteral(writer, 0);
1574 try b.writeReinterpret(writer, die_offset);
1575 _ = try stack_machine.run(program.items, allocator, context, null);
1576 const regval_type = stack_machine.stack.popOrNull().?.regval_type;
1577 try testing.expectEqual(die_offset, regval_type.type_offset);
1578 }
1579
1580 // Special operations
1581 {
1582 var context = ExpressionContext{};
1583
1584 stack_machine.reset();
1585 program.clearRetainingCapacity();
1586 try b.writeOpcode(writer, OP.nop);
1587 _ = try stack_machine.run(program.items, allocator, context, null);
1588 try testing.expect(stack_machine.stack.popOrNull() == null);
1589
1590 // Sub-expression
1591 {
1592 var sub_program = std.ArrayList(u8).init(allocator);
1593 defer sub_program.deinit();
1594 const sub_writer = sub_program.writer();
1595 try b.writeLiteral(sub_writer, 3);
1596
1597 stack_machine.reset();
1598 program.clearRetainingCapacity();
1599 try b.writeEntryValue(writer, sub_program.items);
1600 _ = try stack_machine.run(program.items, allocator, context, null);
1601 try testing.expectEqual(@as(usize, 3), stack_machine.stack.popOrNull().?.generic);
1602 }
1603
1604 // Register location description
1605 const reg_context = abi.RegisterContext{
1606 .eh_frame = true,
1607 .is_macho = builtin.os.tag == .macos,
1608 };
1609 var thread_context: std.debug.ThreadContext = undefined;
1610 std.debug.relocateContext(&thread_context);
1611 context = ExpressionContext{
1612 .thread_context = &thread_context,
1613 .reg_context = reg_context,
1614 };
1615
1616 if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| {
1617 mem.writeIntSliceNative(usize, reg_bytes, 0xee);
1618
1619 var sub_program = std.ArrayList(u8).init(allocator);
1620 defer sub_program.deinit();
1621 const sub_writer = sub_program.writer();
1622 try b.writeReg(sub_writer, 0);
1623
1624 stack_machine.reset();
1625 program.clearRetainingCapacity();
1626 try b.writeEntryValue(writer, sub_program.items);
1627 _ = try stack_machine.run(program.items, allocator, context, null);
1628 try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.popOrNull().?.generic);
1629 } else |err| {
1630 switch (err) {
1631 error.UnimplementedArch,
1632 error.UnimplementedOs,
1633 error.ThreadContextNotSupported,
1634 => {},
1635 else => return err,
1636 }
1637 }
1638 }
1639}
lib/std/elf.zig+8
......@@ -371,6 +371,9 @@ pub const SHT_LOUSER = 0x80000000;
371371/// End of application-specific
372372pub const SHT_HIUSER = 0xffffffff;
373373
374// Note type for .note.gnu.build_id
375pub const NT_GNU_BUILD_ID = 3;
376
374377/// Local symbol
375378pub const STB_LOCAL = 0;
376379/// Global symbol
......@@ -1055,6 +1058,11 @@ pub const Shdr = switch (@sizeOf(usize)) {
10551058 8 => Elf64_Shdr,
10561059 else => @compileError("expected pointer size of 32 or 64"),
10571060};
1061pub const Chdr = switch (@sizeOf(usize)) {
1062 4 => Elf32_Chdr,
1063 8 => Elf64_Chdr,
1064 else => @compileError("expected pointer size of 32 or 64"),
1065};
10581066pub const Sym = switch (@sizeOf(usize)) {
10591067 4 => Elf32_Sym,
10601068 8 => Elf64_Sym,
lib/std/macho.zig+61
......@@ -2064,3 +2064,64 @@ pub const UNWIND_ARM64_FRAME_D14_D15_PAIR: u32 = 0x00000800;
20642064
20652065pub const UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK: u32 = 0x00FFF000;
20662066pub const UNWIND_ARM64_DWARF_SECTION_OFFSET: u32 = 0x00FFFFFF;
2067
2068pub const CompactUnwindEncoding = packed struct(u32) {
2069 value: packed union {
2070 x86_64: packed union {
2071 frame: packed struct(u24) {
2072 reg4: u3,
2073 reg3: u3,
2074 reg2: u3,
2075 reg1: u3,
2076 reg0: u3,
2077 unused: u1 = 0,
2078 frame_offset: u8,
2079 },
2080 frameless: packed struct(u24) {
2081 stack_reg_permutation: u10,
2082 stack_reg_count: u3,
2083 stack: packed union {
2084 direct: packed struct(u11) {
2085 _: u3,
2086 stack_size: u8,
2087 },
2088 indirect: packed struct(u11) {
2089 stack_adjust: u3,
2090 sub_offset: u8,
2091 },
2092 },
2093 },
2094 dwarf: u24,
2095 },
2096 arm64: packed union {
2097 frame: packed struct(u24) {
2098 x_reg_pairs: packed struct(u5) {
2099 x19_x20: u1,
2100 x21_x22: u1,
2101 x23_x24: u1,
2102 x25_x26: u1,
2103 x27_x28: u1,
2104 },
2105 d_reg_pairs: packed struct(u4) {
2106 d8_d9: u1,
2107 d10_d11: u1,
2108 d12_d13: u1,
2109 d14_d15: u1,
2110 },
2111 _: u15,
2112 },
2113 frameless: packed struct(u24) {
2114 _: u12 = 0,
2115 stack_size: u12,
2116 },
2117 dwarf: u24,
2118 },
2119 },
2120 mode: packed union {
2121 x86_64: UNWIND_X86_64_MODE,
2122 arm64: UNWIND_ARM64_MODE,
2123 },
2124 personality_index: u2,
2125 has_lsda: u1,
2126 start: u1,
2127};
lib/std/os/linux.zig+1
......@@ -86,6 +86,7 @@ pub const timeval = arch_bits.timeval;
8686pub const timezone = arch_bits.timezone;
8787pub const ucontext_t = arch_bits.ucontext_t;
8888pub const user_desc = arch_bits.user_desc;
89pub const getcontext = arch_bits.getcontext;
8990
9091pub const tls = @import("linux/tls.zig");
9192pub const pie = @import("linux/start_pie.zig");
lib/std/os/linux/x86.zig+83
......@@ -389,3 +389,86 @@ pub const SC = struct {
389389 pub const recvmmsg = 19;
390390 pub const sendmmsg = 20;
391391};
392
393fn gpRegisterOffset(comptime reg_index: comptime_int) usize {
394 return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index;
395}
396
397noinline fn getContextReturnAddress() usize {
398 return @returnAddress();
399}
400
401pub fn getContextInternal() callconv(.Naked) void {
402 asm volatile (
403 \\ movl $0, (%[flags_offset])(%%edx)
404 \\ movl $0, (%[link_offset])(%%edx)
405 \\ movl %%edi, (%[edi_offset])(%%edx)
406 \\ movl %%esi, (%[esi_offset])(%%edx)
407 \\ movl %%ebp, (%[ebp_offset])(%%edx)
408 \\ movl %%ebx, (%[ebx_offset])(%%edx)
409 \\ movl %%edx, (%[edx_offset])(%%edx)
410 \\ movl %%ecx, (%[ecx_offset])(%%edx)
411 \\ movl %%eax, (%[eax_offset])(%%edx)
412 \\ movl (%%esp), %%ecx
413 \\ movl %%ecx, (%[eip_offset])(%%edx)
414 \\ leal 4(%%esp), %%ecx
415 \\ movl %%ecx, (%[esp_offset])(%%edx)
416 \\ xorl %%ecx, %%ecx
417 \\ movw %%fs, %%cx
418 \\ movl %%ecx, (%[fs_offset])(%%edx)
419 \\ leal (%[regspace_offset])(%%edx), %%ecx
420 \\ movl %%ecx, (%[fpregs_offset])(%%edx)
421 \\ fnstenv (%%ecx)
422 \\ fldenv (%%ecx)
423 \\ pushl %%ebx
424 \\ pushl %%esi
425 \\ xorl %%ebx, %%ebx
426 \\ movl %[sigaltstack], %%eax
427 \\ leal (%[stack_offset])(%%edx), %%ecx
428 \\ int $0x80
429 \\ cmpl $0, %%eax
430 \\ jne return
431 \\ movl %[sigprocmask], %%eax
432 \\ xorl %%ecx, %%ecx
433 \\ leal (%[sigmask_offset])(%%edx), %%edx
434 \\ movl %[sigset_size], %%esi
435 \\ int $0x80
436 \\ return:
437 \\ popl %%esi
438 \\ popl %%ebx
439 :
440 : [flags_offset] "p" (@offsetOf(ucontext_t, "flags")),
441 [link_offset] "p" (@offsetOf(ucontext_t, "link")),
442 [edi_offset] "p" (comptime gpRegisterOffset(REG.EDI)),
443 [esi_offset] "p" (comptime gpRegisterOffset(REG.ESI)),
444 [ebp_offset] "p" (comptime gpRegisterOffset(REG.EBP)),
445 [esp_offset] "p" (comptime gpRegisterOffset(REG.ESP)),
446 [ebx_offset] "p" (comptime gpRegisterOffset(REG.EBX)),
447 [edx_offset] "p" (comptime gpRegisterOffset(REG.EDX)),
448 [ecx_offset] "p" (comptime gpRegisterOffset(REG.ECX)),
449 [eax_offset] "p" (comptime gpRegisterOffset(REG.EAX)),
450 [eip_offset] "p" (comptime gpRegisterOffset(REG.EIP)),
451 [fs_offset] "p" (comptime gpRegisterOffset(REG.FS)),
452 [fpregs_offset] "p" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")),
453 [regspace_offset] "p" (@offsetOf(ucontext_t, "regspace")),
454 [sigaltstack] "i" (@intFromEnum(linux.SYS.sigaltstack)),
455 [stack_offset] "p" (@offsetOf(ucontext_t, "stack")),
456 [sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)),
457 [sigmask_offset] "p" (@offsetOf(ucontext_t, "sigmask")),
458 [sigset_size] "i" (linux.NSIG / 8),
459 : "memory", "eax", "ecx", "edx"
460 );
461}
462
463pub inline fn getcontext(context: *ucontext_t) usize {
464 // This method is used so that getContextInternal can control
465 // its prologue in order to read ESP from a constant offset.
466 // The unused &getContextInternal input is required so the function is included in the binary.
467 return asm volatile (
468 \\ call os.linux.x86.getContextInternal
469 : [ret] "={eax}" (-> usize),
470 : [context] "{edx}" (context),
471 [getContextInternal] "X" (&getContextInternal),
472 : "memory", "ecx"
473 );
474}
lib/std/os/linux/x86_64.zig+94
......@@ -395,3 +395,97 @@ pub const ucontext_t = extern struct {
395395 sigmask: sigset_t,
396396 fpregs_mem: [64]usize,
397397};
398
399fn gpRegisterOffset(comptime reg_index: comptime_int) usize {
400 return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index;
401}
402
403fn getContextInternal() callconv(.Naked) void {
404 // TODO: Read GS/FS registers?
405 asm volatile (
406 \\ movq $0, (%[flags_offset])(%%rdi)
407 \\ movq $0, (%[link_offset])(%%rdi)
408 \\ movq %%r8, (%[r8_offset])(%%rdi)
409 \\ movq %%r9, (%[r9_offset])(%%rdi)
410 \\ movq %%r10, (%[r10_offset])(%%rdi)
411 \\ movq %%r11, (%[r11_offset])(%%rdi)
412 \\ movq %%r12, (%[r12_offset])(%%rdi)
413 \\ movq %%r13, (%[r13_offset])(%%rdi)
414 \\ movq %%r14, (%[r14_offset])(%%rdi)
415 \\ movq %%r15, (%[r15_offset])(%%rdi)
416 \\ movq %%rdi, (%[rdi_offset])(%%rdi)
417 \\ movq %%rsi, (%[rsi_offset])(%%rdi)
418 \\ movq %%rbp, (%[rbp_offset])(%%rdi)
419 \\ movq %%rbx, (%[rbx_offset])(%%rdi)
420 \\ movq %%rdx, (%[rdx_offset])(%%rdi)
421 \\ movq %%rax, (%[rax_offset])(%%rdi)
422 \\ movq %%rcx, (%[rcx_offset])(%%rdi)
423 \\ movq (%%rsp), %%rcx
424 \\ movq %%rcx, (%[rip_offset])(%%rdi)
425 \\ leaq 8(%%rsp), %%rcx
426 \\ movq %%rcx, (%[rsp_offset])(%%rdi)
427 \\ pushfq
428 \\ popq (%[efl_offset])(%%rdi)
429 \\ leaq (%[fpmem_offset])(%%rdi), %%rcx
430 \\ movq %%rcx, (%[fpstate_offset])(%%rdi)
431 \\ fnstenv (%%rcx)
432 \\ fldenv (%%rcx)
433 \\ stmxcsr (%[mxcsr_offset])(%%rdi)
434 \\ leaq (%[stack_offset])(%%rdi), %%rsi
435 \\ movq %%rdi, %%r8
436 \\ xorq %%rdi, %%rdi
437 \\ movq %[sigaltstack], %%rax
438 \\ syscall
439 \\ cmpq $0, %%rax
440 \\ jne return
441 \\ movq %[sigprocmask], %%rax
442 \\ xorq %%rsi, %%rsi
443 \\ leaq (%[sigmask_offset])(%%r8), %%rdx
444 \\ movq %[sigset_size], %%r10
445 \\ syscall
446 \\ return:
447 :
448 : [flags_offset] "p" (@offsetOf(ucontext_t, "flags")),
449 [link_offset] "p" (@offsetOf(ucontext_t, "link")),
450 [r8_offset] "p" (comptime gpRegisterOffset(REG.R8)),
451 [r9_offset] "p" (comptime gpRegisterOffset(REG.R9)),
452 [r10_offset] "p" (comptime gpRegisterOffset(REG.R10)),
453 [r11_offset] "p" (comptime gpRegisterOffset(REG.R11)),
454 [r12_offset] "p" (comptime gpRegisterOffset(REG.R12)),
455 [r13_offset] "p" (comptime gpRegisterOffset(REG.R13)),
456 [r14_offset] "p" (comptime gpRegisterOffset(REG.R14)),
457 [r15_offset] "p" (comptime gpRegisterOffset(REG.R15)),
458 [rdi_offset] "p" (comptime gpRegisterOffset(REG.RDI)),
459 [rsi_offset] "p" (comptime gpRegisterOffset(REG.RSI)),
460 [rbp_offset] "p" (comptime gpRegisterOffset(REG.RBP)),
461 [rbx_offset] "p" (comptime gpRegisterOffset(REG.RBX)),
462 [rdx_offset] "p" (comptime gpRegisterOffset(REG.RDX)),
463 [rax_offset] "p" (comptime gpRegisterOffset(REG.RAX)),
464 [rcx_offset] "p" (comptime gpRegisterOffset(REG.RCX)),
465 [rsp_offset] "p" (comptime gpRegisterOffset(REG.RSP)),
466 [rip_offset] "p" (comptime gpRegisterOffset(REG.RIP)),
467 [efl_offset] "p" (comptime gpRegisterOffset(REG.EFL)),
468 [fpstate_offset] "p" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")),
469 [fpmem_offset] "p" (@offsetOf(ucontext_t, "fpregs_mem")),
470 [mxcsr_offset] "p" (@offsetOf(ucontext_t, "fpregs_mem") + @offsetOf(fpstate, "mxcsr")),
471 [sigaltstack] "i" (@intFromEnum(linux.SYS.sigaltstack)),
472 [stack_offset] "p" (@offsetOf(ucontext_t, "stack")),
473 [sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)),
474 [sigmask_offset] "p" (@offsetOf(ucontext_t, "sigmask")),
475 [sigset_size] "i" (linux.NSIG / 8),
476 : "memory", "rcx", "rdx", "rdi", "rsi", "r8", "r10", "r11"
477 );
478}
479
480pub inline fn getcontext(context: *ucontext_t) usize {
481 // This method is used so that getContextInternal can control
482 // its prologue in order to read RSP from a constant offset
483 // The unused &getContextInternal input is required so the function is included in the binary.
484 return asm volatile (
485 \\ call os.linux.x86_64.getContextInternal
486 : [ret] "={rax}" (-> usize),
487 : [context] "{rdi}" (context),
488 [getContextInternal] "X" (&getContextInternal),
489 : "memory", "rcx", "rdx", "rdi", "rsi", "r8", "r10", "r11"
490 );
491}
lib/std/os/windows.zig+29
......@@ -3301,6 +3301,35 @@ pub const REGSAM = ACCESS_MASK;
33013301pub const ACCESS_MASK = DWORD;
33023302pub const LSTATUS = LONG;
33033303
3304pub const SECTION_INHERIT = enum(c_int) {
3305 ViewShare = 0,
3306 ViewUnmap = 1,
3307};
3308
3309pub const SECTION_QUERY = 0x0001;
3310pub const SECTION_MAP_WRITE = 0x0002;
3311pub const SECTION_MAP_READ = 0x0004;
3312pub const SECTION_MAP_EXECUTE = 0x0008;
3313pub const SECTION_EXTEND_SIZE = 0x0010;
3314pub const SECTION_ALL_ACCESS =
3315 STANDARD_RIGHTS_REQUIRED |
3316 SECTION_QUERY |
3317 SECTION_MAP_WRITE |
3318 SECTION_MAP_READ |
3319 SECTION_MAP_EXECUTE |
3320 SECTION_EXTEND_SIZE;
3321
3322pub const SEC_64K_PAGES = 0x80000;
3323pub const SEC_FILE = 0x800000;
3324pub const SEC_IMAGE = 0x1000000;
3325pub const SEC_PROTECTED_IMAGE = 0x2000000;
3326pub const SEC_RESERVE = 0x4000000;
3327pub const SEC_COMMIT = 0x8000000;
3328pub const SEC_IMAGE_NO_EXECUTE = SEC_IMAGE | SEC_NOCACHE;
3329pub const SEC_NOCACHE = 0x10000000;
3330pub const SEC_WRITECOMBINE = 0x40000000;
3331pub const SEC_LARGE_PAGES = 0x80000000;
3332
33043333pub const HKEY = *opaque {};
33053334
33063335pub const HKEY_LOCAL_MACHINE: HKEY = @as(HKEY, @ptrFromInt(0x80000002));
lib/std/os/windows/ntdll.zig+26
......@@ -36,6 +36,7 @@ const THREADINFOCLASS = windows.THREADINFOCLASS;
3636const PROCESSINFOCLASS = windows.PROCESSINFOCLASS;
3737const LPVOID = windows.LPVOID;
3838const LPCVOID = windows.LPCVOID;
39const SECTION_INHERIT = windows.SECTION_INHERIT;
3940
4041pub extern "ntdll" fn NtQueryInformationProcess(
4142 ProcessHandle: HANDLE,
......@@ -125,6 +126,31 @@ pub extern "ntdll" fn NtCreateFile(
125126 EaBuffer: ?*anyopaque,
126127 EaLength: ULONG,
127128) callconv(WINAPI) NTSTATUS;
129pub extern "ntdll" fn NtCreateSection(
130 SectionHandle: *HANDLE,
131 DesiredAccess: ACCESS_MASK,
132 ObjectAttributes: ?*OBJECT_ATTRIBUTES,
133 MaximumSize: ?*LARGE_INTEGER,
134 SectionPageProtection: ULONG,
135 AllocationAttributes: ULONG,
136 FileHandle: ?HANDLE,
137) callconv(WINAPI) NTSTATUS;
138pub extern "ntdll" fn NtMapViewOfSection(
139 SectionHandle: HANDLE,
140 ProcessHandle: HANDLE,
141 BaseAddress: *PVOID,
142 ZeroBits: ?*ULONG,
143 CommitSize: SIZE_T,
144 SectionOffset: ?*LARGE_INTEGER,
145 ViewSize: *SIZE_T,
146 InheritDispostion: SECTION_INHERIT,
147 AllocationType: ULONG,
148 Win32Protect: ULONG,
149) callconv(WINAPI) NTSTATUS;
150pub extern "ntdll" fn NtUnmapViewOfSection(
151 ProcessHandle: HANDLE,
152 BaseAddress: PVOID,
153) callconv(WINAPI) NTSTATUS;
128154pub extern "ntdll" fn NtDeviceIoControlFile(
129155 FileHandle: HANDLE,
130156 Event: ?HANDLE,
src/Compilation.zig+2
......@@ -5288,6 +5288,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
52885288 \\pub const position_independent_executable = {};
52895289 \\pub const strip_debug_info = {};
52905290 \\pub const code_model = std.builtin.CodeModel.{};
5291 \\pub const omit_frame_pointer = {};
52915292 \\
52925293 , .{
52935294 std.zig.fmtId(@tagName(target.ofmt)),
......@@ -5301,6 +5302,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
53015302 comp.bin_file.options.pie,
53025303 comp.bin_file.options.strip,
53035304 std.zig.fmtId(@tagName(comp.bin_file.options.machine_code_model)),
5305 comp.bin_file.options.omit_frame_pointer,
53045306 });
53055307
53065308 if (target.os.tag == .wasi) {
src/crash_report.zig+12-58
......@@ -203,53 +203,11 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any
203203 };
204204
205205 const stack_ctx: StackContext = switch (builtin.cpu.arch) {
206 .x86 => ctx: {
207 const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
208 const ip = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EIP]));
209 const bp = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EBP]));
210 break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
211 },
212 .x86_64 => ctx: {
213 const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
214 const ip = switch (builtin.os.tag) {
215 .linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RIP])),
216 .freebsd => @as(usize, @intCast(ctx.mcontext.rip)),
217 .openbsd => @as(usize, @intCast(ctx.sc_rip)),
218 .macos => @as(usize, @intCast(ctx.mcontext.ss.rip)),
219 else => unreachable,
220 };
221 const bp = switch (builtin.os.tag) {
222 .linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RBP])),
223 .openbsd => @as(usize, @intCast(ctx.sc_rbp)),
224 .freebsd => @as(usize, @intCast(ctx.mcontext.rbp)),
225 .macos => @as(usize, @intCast(ctx.mcontext.ss.rbp)),
226 else => unreachable,
227 };
228 break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
229 },
230 .arm => ctx: {
231 const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
232 const ip = @as(usize, @intCast(ctx.mcontext.arm_pc));
233 const bp = @as(usize, @intCast(ctx.mcontext.arm_fp));
234 break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
235 },
236 .aarch64 => ctx: {
237 const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
238 const ip = switch (native_os) {
239 .macos => @as(usize, @intCast(ctx.mcontext.ss.pc)),
240 .netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.PC])),
241 .freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.elr)),
242 else => @as(usize, @intCast(ctx.mcontext.pc)),
243 };
244 // x29 is the ABI-designated frame pointer
245 const bp = switch (native_os) {
246 .macos => @as(usize, @intCast(ctx.mcontext.ss.fp)),
247 .netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.FP])),
248 .freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.x[os.REG.FP])),
249 else => @as(usize, @intCast(ctx.mcontext.regs[29])),
250 };
251 break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
252 },
206 .x86,
207 .x86_64,
208 .arm,
209 .aarch64,
210 => StackContext{ .exception = @ptrCast(@alignCast(ctx_ptr)) },
253211 else => .not_supported,
254212 };
255213
......@@ -275,10 +233,9 @@ fn handleSegfaultWindows(info: *os.windows.EXCEPTION_POINTERS) callconv(os.windo
275233fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn {
276234 PanicSwitch.preDispatch();
277235
278 const stack_ctx = if (@hasDecl(os.windows, "CONTEXT")) ctx: {
279 const regs = info.ContextRecord.getRegs();
280 break :ctx StackContext{ .exception = .{ .bp = regs.bp, .ip = regs.ip } };
281 } else ctx: {
236 const stack_ctx = if (@hasDecl(os.windows, "CONTEXT"))
237 StackContext{ .exception = info.ContextRecord }
238 else ctx: {
282239 const addr = @intFromPtr(info.ExceptionRecord.ExceptionAddress);
283240 break :ctx StackContext{ .current = .{ .ret_addr = addr } };
284241 };
......@@ -293,7 +250,7 @@ fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg
293250 },
294251 .illegal_instruction => {
295252 const ip: ?usize = switch (stack_ctx) {
296 .exception => |ex| ex.ip,
253 .exception => |ex| ex.getRegs().ip,
297254 .current => |cur| cur.ret_addr,
298255 .not_supported => null,
299256 };
......@@ -314,10 +271,7 @@ const StackContext = union(enum) {
314271 current: struct {
315272 ret_addr: ?usize,
316273 },
317 exception: struct {
318 bp: usize,
319 ip: usize,
320 },
274 exception: *const debug.ThreadContext,
321275 not_supported: void,
322276
323277 pub fn dumpStackTrace(ctx: @This()) void {
......@@ -325,8 +279,8 @@ const StackContext = union(enum) {
325279 .current => |ct| {
326280 debug.dumpCurrentStackTrace(ct.ret_addr);
327281 },
328 .exception => |ex| {
329 debug.dumpStackTraceFromBase(ex.bp, ex.ip);
282 .exception => |context| {
283 debug.dumpStackTraceFromBase(context);
330284 },
331285 .not_supported => {
332286 const stderr = io.getStdErr().writer();
src/target.zig+1-1
......@@ -510,7 +510,7 @@ pub fn clangAssemblerSupportsMcpuArg(target: std.Target) bool {
510510}
511511
512512pub fn needUnwindTables(target: std.Target) bool {
513 return target.os.tag == .windows;
513 return target.os.tag == .windows or target.isDarwin();
514514}
515515
516516pub fn defaultAddressSpace(
test/standalone.zig+8
......@@ -230,6 +230,14 @@ pub const build_cases = [_]BuildCase{
230230 .build_root = "test/standalone/zerolength_check",
231231 .import = @import("standalone/zerolength_check/build.zig"),
232232 },
233 .{
234 .build_root = "test/standalone/stack_iterator",
235 .import = @import("standalone/stack_iterator/build.zig"),
236 },
237 .{
238 .build_root = "test/standalone/coff_dwarf",
239 .import = @import("standalone/coff_dwarf/build.zig"),
240 },
233241};
234242
235243const std = @import("std");
test/standalone/coff_dwarf/build.zig created+35
......@@ -0,0 +1,35 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4/// This tests the path where DWARF information is embedded in a COFF binary
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 const optimize: std.builtin.OptimizeMode = .Debug;
10 const target = b.standardTargetOptions(.{});
11
12 if (builtin.os.tag != .windows) return;
13
14 const exe = b.addExecutable(.{
15 .name = "main",
16 .root_source_file = .{ .path = "main.zig" },
17 .optimize = optimize,
18 .target = target,
19 });
20
21 const lib = b.addSharedLibrary(.{
22 .name = "shared_lib",
23 .optimize = optimize,
24 .target = target,
25 });
26 lib.addCSourceFile("shared_lib.c", &.{"-gdwarf"});
27 lib.linkLibC();
28 exe.linkLibrary(lib);
29
30 const run = b.addRunArtifact(exe);
31 run.expectExitCode(0);
32 run.skip_foreign_checks = true;
33
34 test_step.dependOn(&run.step);
35}
test/standalone/coff_dwarf/main.zig created+27
......@@ -0,0 +1,27 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const testing = std.testing;
4
5extern fn add(a: u32, b: u32, addr: *usize) u32;
6
7pub fn main() !void {
8 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
9 defer assert(gpa.deinit() == .ok);
10 const allocator = gpa.allocator();
11
12 var debug_info = try std.debug.openSelfDebugInfo(allocator);
13 defer debug_info.deinit();
14
15 var add_addr: usize = undefined;
16 _ = add(1, 2, &add_addr);
17
18 const module = try debug_info.getModuleForAddress(add_addr);
19 const symbol = try module.getSymbolAtAddress(allocator, add_addr);
20 defer symbol.deinit(allocator);
21
22 try testing.expectEqualStrings("add", symbol.symbol_name);
23 try testing.expect(symbol.line_info != null);
24 try testing.expectEqualStrings("shared_lib.c", std.fs.path.basename(symbol.line_info.?.file_name));
25 try testing.expectEqual(@as(u64, 3), symbol.line_info.?.line);
26 try testing.expectEqual(@as(u64, 0), symbol.line_info.?.column);
27}
test/standalone/coff_dwarf/shared_lib.c created+6
......@@ -0,0 +1,6 @@
1#include <stdint.h>
2
3__declspec(dllexport) uint32_t add(uint32_t a, uint32_t b, uintptr_t* addr) {
4 *addr = (uintptr_t)&add;
5 return a + b;
6}
test/standalone/stack_iterator/build.zig created+94
......@@ -0,0 +1,94 @@
1const std = @import("std");
2
3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const target = b.standardTargetOptions(.{});
8 const optimize = b.standardOptimizeOption(.{});
9
10 // Unwinding with a frame pointer
11 //
12 // getcontext version: zig std
13 //
14 // Unwind info type:
15 // - ELF: DWARF .debug_frame
16 // - MachO: __unwind_info encodings:
17 // - x86_64: RBP_FRAME
18 // - aarch64: FRAME, DWARF
19 {
20 const exe = b.addExecutable(.{
21 .name = "unwind_fp",
22 .root_source_file = .{ .path = "unwind.zig" },
23 .target = target,
24 .optimize = optimize,
25 });
26
27 if (target.isDarwin()) exe.unwind_tables = true;
28 exe.omit_frame_pointer = false;
29
30 const run_cmd = b.addRunArtifact(exe);
31 test_step.dependOn(&run_cmd.step);
32 }
33
34 // Unwinding without a frame pointer
35 //
36 // getcontext version: zig std
37 //
38 // Unwind info type:
39 // - ELF: DWARF .eh_frame_hdr + .eh_frame
40 // - MachO: __unwind_info encodings:
41 // - x86_64: STACK_IMMD, STACK_IND
42 // - aarch64: FRAMELESS, DWARF
43 {
44 const exe = b.addExecutable(.{
45 .name = "unwind_nofp",
46 .root_source_file = .{ .path = "unwind.zig" },
47 .target = target,
48 .optimize = optimize,
49 });
50
51 exe.omit_frame_pointer = true;
52 exe.unwind_tables = true;
53
54 const run_cmd = b.addRunArtifact(exe);
55 test_step.dependOn(&run_cmd.step);
56 }
57
58 // Unwinding through a C shared library without a frame pointer (libc)
59 //
60 // getcontext version: libc
61 //
62 // Unwind info type:
63 // - ELF: DWARF .eh_frame + .debug_frame
64 // - MachO: __unwind_info encodings:
65 // - x86_64: STACK_IMMD, STACK_IND
66 // - aarch64: FRAMELESS, DWARF
67 {
68 const c_shared_lib = b.addSharedLibrary(.{
69 .name = "c_shared_lib",
70 .target = target,
71 .optimize = optimize,
72 });
73
74 if (target.isWindows()) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)");
75
76 c_shared_lib.strip = false;
77 c_shared_lib.addCSourceFile("shared_lib.c", &.{"-fomit-frame-pointer"});
78 c_shared_lib.linkLibC();
79
80 const exe = b.addExecutable(.{
81 .name = "shared_lib_unwind",
82 .root_source_file = .{ .path = "shared_lib_unwind.zig" },
83 .target = target,
84 .optimize = optimize,
85 });
86
87 if (target.isDarwin()) exe.unwind_tables = true;
88 exe.omit_frame_pointer = true;
89 exe.linkLibrary(c_shared_lib);
90
91 const run_cmd = b.addRunArtifact(exe);
92 test_step.dependOn(&run_cmd.step);
93 }
94}
test/standalone/stack_iterator/shared_lib.c created+22
......@@ -0,0 +1,22 @@
1#include <stdint.h>
2
3#ifndef LIB_API
4#define LIB_API
5#endif
6
7__attribute__((noinline)) void frame1(
8 void** expected,
9 void** unwound,
10 void (*frame2)(void** expected, void** unwound)) {
11 expected[3] = __builtin_extract_return_addr(__builtin_return_address(0));
12 frame2(expected, unwound);
13}
14
15LIB_API void frame0(
16 void** expected,
17 void** unwound,
18 void (*frame2)(void** expected, void** unwound)) {
19 expected[4] = __builtin_extract_return_addr(__builtin_return_address(0));
20 frame1(expected, unwound, frame2);
21}
22
test/standalone/stack_iterator/shared_lib_unwind.zig created+47
......@@ -0,0 +1,47 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const debug = std.debug;
4const testing = std.testing;
5
6noinline fn frame4(expected: *[5]usize, unwound: *[5]usize) void {
7 expected[0] = @returnAddress();
8
9 var context: debug.ThreadContext = undefined;
10 testing.expect(debug.getContext(&context)) catch @panic("failed to getContext");
11
12 var debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo");
13 var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context) catch @panic("failed to initWithContext");
14 defer it.deinit();
15
16 for (unwound) |*addr| {
17 if (it.next()) |return_address| addr.* = return_address;
18 }
19}
20
21noinline fn frame3(expected: *[5]usize, unwound: *[5]usize) void {
22 expected[1] = @returnAddress();
23 frame4(expected, unwound);
24}
25
26fn frame2(expected: *[5]usize, unwound: *[5]usize) callconv(.C) void {
27 expected[2] = @returnAddress();
28 frame3(expected, unwound);
29}
30
31extern fn frame0(
32 expected: *[5]usize,
33 unwound: *[5]usize,
34 frame_2: *const fn (expected: *[5]usize, unwound: *[5]usize) callconv(.C) void,
35) void;
36
37pub fn main() !void {
38 // Disabled until the DWARF unwinder bugs on .aarch64 are solved
39 if (builtin.omit_frame_pointer and comptime builtin.target.isDarwin() and builtin.cpu.arch == .aarch64) return;
40
41 if (!std.debug.have_ucontext or !std.debug.have_getcontext) return;
42
43 var expected: [5]usize = undefined;
44 var unwound: [5]usize = undefined;
45 frame0(&expected, &unwound, &frame2);
46 try testing.expectEqual(expected, unwound);
47}
test/standalone/stack_iterator/unwind.zig created+99
......@@ -0,0 +1,99 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const debug = std.debug;
4const testing = std.testing;
5
6noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void {
7 expected[0] = @returnAddress();
8
9 var context: debug.ThreadContext = undefined;
10 testing.expect(debug.getContext(&context)) catch @panic("failed to getContext");
11
12 var debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo");
13 var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context) catch @panic("failed to initWithContext");
14 defer it.deinit();
15
16 for (unwound) |*addr| {
17 if (it.next()) |return_address| addr.* = return_address;
18 }
19}
20
21noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {
22 // Excercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored
23 if (builtin.target.ofmt != .c) {
24 switch (builtin.cpu.arch) {
25 .x86 => {
26 if (builtin.omit_frame_pointer) {
27 asm volatile (
28 \\movl $3, %%ebx
29 \\movl $1, %%ecx
30 \\movl $2, %%edx
31 \\movl $7, %%edi
32 \\movl $6, %%esi
33 \\movl $5, %%ebp
34 ::: "ebx", "ecx", "edx", "edi", "esi", "ebp");
35 } else {
36 asm volatile (
37 \\movl $3, %%ebx
38 \\movl $1, %%ecx
39 \\movl $2, %%edx
40 \\movl $7, %%edi
41 \\movl $6, %%esi
42 ::: "ebx", "ecx", "edx", "edi", "esi");
43 }
44 },
45 .x86_64 => {
46 if (builtin.omit_frame_pointer) {
47 asm volatile (
48 \\movq $3, %%rbx
49 \\movq $12, %%r12
50 \\movq $13, %%r13
51 \\movq $14, %%r14
52 \\movq $15, %%r15
53 \\movq $6, %%rbp
54 ::: "rbx", "r12", "r13", "r14", "r15", "rbp");
55 } else {
56 asm volatile (
57 \\movq $3, %%rbx
58 \\movq $12, %%r12
59 \\movq $13, %%r13
60 \\movq $14, %%r14
61 \\movq $15, %%r15
62 ::: "rbx", "r12", "r13", "r14", "r15");
63 }
64 },
65 else => {},
66 }
67 }
68
69 expected[1] = @returnAddress();
70 frame3(expected, unwound);
71}
72
73noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void {
74 expected[2] = @returnAddress();
75
76 // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding
77 // to exercise the stack-indirect encoding path
78 var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined;
79 _ = pad;
80
81 frame2(expected, unwound);
82}
83
84noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void {
85 expected[3] = @returnAddress();
86 frame1(expected, unwound);
87}
88
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.isDarwin() and builtin.cpu.arch == .aarch64) return;
92
93 if (!std.debug.have_ucontext or !std.debug.have_getcontext) return;
94
95 var expected: [4]usize = undefined;
96 var unwound: [4]usize = undefined;
97 frame0(&expected, &unwound);
98 try testing.expectEqual(expected, unwound);
99}