authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-02 17:10:41-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-08-02 17:10:41-07:00
loga931bfada5e358ace980b2f8fbc50ce424ced526
tree5aabd9fb3833765926ee5409c1ce14e04d2d9fd0
parent9e2668cd2ecc587390335e1c9f6e1592a7bd6eb6
parent6d606cc38b4df2b20af9d77367f8ab22bbbea092
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #20908 from ziglang/reorg-std.debug-again

std.debug: reorg and clarify API goals

11 files changed, 3512 insertions(+), 3428 deletions(-)

lib/std/debug.zig+154-1515
......@@ -1,16 +1,11 @@
1const std = @import("std.zig");
21const builtin = @import("builtin");
2const std = @import("std.zig");
33const math = std.math;
44const mem = std.mem;
55const io = std.io;
66const posix = std.posix;
77const fs = std.fs;
88const testing = std.testing;
9const elf = std.elf;
10const DW = std.dwarf;
11const macho = std.macho;
12const coff = std.coff;
13const pdb = std.pdb;
149const root = @import("root");
1510const File = std.fs.File;
1611const windows = std.os.windows;
......@@ -18,8 +13,24 @@ const native_arch = builtin.cpu.arch;
1813const native_os = builtin.os.tag;
1914const native_endian = native_arch.endian();
2015
16pub const MemoryAccessor = @import("debug/MemoryAccessor.zig");
2117pub const Dwarf = @import("debug/Dwarf.zig");
18pub const Pdb = @import("debug/Pdb.zig");
19pub const SelfInfo = @import("debug/SelfInfo.zig");
20
21/// Unresolved source locations can be represented with a single `usize` that
22/// corresponds to a virtual memory address of the program counter. Combined
23/// with debug information, those values can be converted into a resolved
24/// source location, including file, line, and column.
25pub const SourceLocation = struct {
26 line: u64,
27 column: u64,
28 file_name: []const u8,
29};
2230
31/// Deprecated because it returns the optimization mode of the standard
32/// library, when the caller probably wants to use the optimization mode of
33/// their own module.
2334pub const runtime_safety = switch (builtin.mode) {
2435 .Debug, .ReleaseSafe => true,
2536 .ReleaseFast, .ReleaseSmall => false,
......@@ -46,39 +57,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
4657 else => true,
4758};
4859
49pub const LineInfo = struct {
50 line: u64,
51 column: u64,
52 file_name: []const u8,
53
54 pub fn deinit(self: LineInfo, allocator: mem.Allocator) void {
55 allocator.free(self.file_name);
56 }
57};
58
59pub const SymbolInfo = struct {
60 symbol_name: []const u8 = "???",
61 compile_unit_name: []const u8 = "???",
62 line_info: ?LineInfo = null,
63
64 pub fn deinit(self: SymbolInfo, allocator: mem.Allocator) void {
65 if (self.line_info) |li| {
66 li.deinit(allocator);
67 }
68 }
69};
70const PdbOrDwarf = union(enum) {
71 pdb: pdb.Pdb,
72 dwarf: Dwarf,
73
74 fn deinit(self: *PdbOrDwarf, allocator: mem.Allocator) void {
75 switch (self.*) {
76 .pdb => |*inner| inner.deinit(),
77 .dwarf => |*inner| inner.deinit(allocator),
78 }
79 }
80};
81
8260/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
8361///
8462/// During the lock, any `std.Progress` information is cleared from the terminal.
......@@ -104,13 +82,13 @@ pub fn getStderrMutex() *std.Thread.Mutex {
10482}
10583
10684/// TODO multithreaded awareness
107var self_debug_info: ?Info = null;
85var self_debug_info: ?SelfInfo = null;
10886
109pub fn getSelfDebugInfo() !*Info {
87pub fn getSelfDebugInfo() !*SelfInfo {
11088 if (self_debug_info) |*info| {
11189 return info;
11290 } else {
113 self_debug_info = try openSelfDebugInfo(getDebugInfoAllocator());
91 self_debug_info = try SelfInfo.open(getDebugInfoAllocator());
11492 return &self_debug_info.?;
11593 }
11694}
......@@ -266,7 +244,7 @@ pub inline fn getContext(context: *ThreadContext) bool {
266244/// Tries to print the stack trace starting from the supplied base pointer to stderr,
267245/// unbuffered, and ignores any error returned.
268246/// TODO multithreaded awareness
269pub fn dumpStackTraceFromBase(context: *const ThreadContext) void {
247pub fn dumpStackTraceFromBase(context: *ThreadContext) void {
270248 nosuspend {
271249 if (comptime builtin.target.isWasm()) {
272250 if (native_os == .wasi) {
......@@ -348,7 +326,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT
348326 stack_trace.index = slice.len;
349327 } else {
350328 // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required).
351 // A new path for loading Info needs to be created which will only attempt to parse in-memory sections, because
329 // A new path for loading SelfInfo needs to be created which will only attempt to parse in-memory sections, because
352330 // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding.
353331 var it = StackIterator.init(first_address, null);
354332 defer it.deinit();
......@@ -526,7 +504,7 @@ pub fn writeStackTrace(
526504 stack_trace: std.builtin.StackTrace,
527505 out_stream: anytype,
528506 allocator: mem.Allocator,
529 debug_info: *Info,
507 debug_info: *SelfInfo,
530508 tty_config: io.tty.Config,
531509) !void {
532510 _ = allocator;
......@@ -563,12 +541,12 @@ pub const StackIterator = struct {
563541 fp: usize,
564542 ma: MemoryAccessor = MemoryAccessor.init,
565543
566 // When Info and a register context is available, this iterator can unwind
544 // When SelfInfo and a register context is available, this iterator can unwind
567545 // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer),
568546 // using DWARF and MachO unwind info.
569547 unwind_state: if (have_ucontext) ?struct {
570 debug_info: *Info,
571 dwarf_context: Dwarf.UnwindContext,
548 debug_info: *SelfInfo,
549 dwarf_context: SelfInfo.UnwindContext,
572550 last_error: ?UnwindError = null,
573551 failed: bool = false,
574552 } else void = if (have_ucontext) null else {},
......@@ -592,20 +570,22 @@ pub const StackIterator = struct {
592570 };
593571 }
594572
595 pub fn initWithContext(first_address: ?usize, debug_info: *Info, context: *const posix.ucontext_t) !StackIterator {
573 pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t) !StackIterator {
596574 // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that
597575 // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder.
598 if (comptime builtin.target.isDarwin() and native_arch == .aarch64) {
576 if (builtin.target.isDarwin() and native_arch == .aarch64)
599577 return init(first_address, context.mcontext.ss.fp);
600 } else {
578
579 if (SelfInfo.supports_unwinding) {
601580 var iterator = init(first_address, null);
602581 iterator.unwind_state = .{
603582 .debug_info = debug_info,
604 .dwarf_context = try Dwarf.UnwindContext.init(debug_info.allocator, context),
583 .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context),
605584 };
606
607585 return iterator;
608586 }
587
588 return init(first_address, null);
609589 }
610590
611591 pub fn deinit(it: *StackIterator) void {
......@@ -667,116 +647,6 @@ pub const StackIterator = struct {
667647 return address;
668648 }
669649
670 fn isValidMemory(address: usize) bool {
671 // We are unable to determine validity of memory for freestanding targets
672 if (native_os == .freestanding or native_os == .uefi) return true;
673
674 const aligned_address = address & ~@as(usize, @intCast((mem.page_size - 1)));
675 if (aligned_address == 0) return false;
676 const aligned_memory = @as([*]align(mem.page_size) u8, @ptrFromInt(aligned_address))[0..mem.page_size];
677
678 if (native_os == .windows) {
679 var memory_info: windows.MEMORY_BASIC_INFORMATION = undefined;
680
681 // The only error this function can throw is ERROR_INVALID_PARAMETER.
682 // supply an address that invalid i'll be thrown.
683 const rc = windows.VirtualQuery(aligned_memory, &memory_info, aligned_memory.len) catch {
684 return false;
685 };
686
687 // Result code has to be bigger than zero (number of bytes written)
688 if (rc == 0) {
689 return false;
690 }
691
692 // Free pages cannot be read, they are unmapped
693 if (memory_info.State == windows.MEM_FREE) {
694 return false;
695 }
696
697 return true;
698 } else if (have_msync) {
699 posix.msync(aligned_memory, posix.MSF.ASYNC) catch |err| {
700 switch (err) {
701 error.UnmappedMemory => return false,
702 else => unreachable,
703 }
704 };
705
706 return true;
707 } else {
708 // We are unable to determine validity of memory on this target.
709 return true;
710 }
711 }
712
713 pub const MemoryAccessor = struct {
714 var cached_pid: posix.pid_t = -1;
715
716 mem: switch (native_os) {
717 .linux => File,
718 else => void,
719 },
720
721 pub const init: MemoryAccessor = .{
722 .mem = switch (native_os) {
723 .linux => .{ .handle = -1 },
724 else => {},
725 },
726 };
727
728 fn read(ma: *MemoryAccessor, address: usize, buf: []u8) bool {
729 switch (native_os) {
730 .linux => while (true) switch (ma.mem.handle) {
731 -2 => break,
732 -1 => {
733 const linux = std.os.linux;
734 const pid = switch (@atomicLoad(posix.pid_t, &cached_pid, .monotonic)) {
735 -1 => pid: {
736 const pid = linux.getpid();
737 @atomicStore(posix.pid_t, &cached_pid, pid, .monotonic);
738 break :pid pid;
739 },
740 else => |pid| pid,
741 };
742 const bytes_read = linux.process_vm_readv(
743 pid,
744 &.{.{ .base = buf.ptr, .len = buf.len }},
745 &.{.{ .base = @ptrFromInt(address), .len = buf.len }},
746 0,
747 );
748 switch (linux.E.init(bytes_read)) {
749 .SUCCESS => return bytes_read == buf.len,
750 .FAULT => return false,
751 .INVAL, .PERM, .SRCH => unreachable, // own pid is always valid
752 .NOMEM => {},
753 .NOSYS => {}, // QEMU is known not to implement this syscall.
754 else => unreachable, // unexpected
755 }
756 var path_buf: [
757 std.fmt.count("/proc/{d}/mem", .{math.minInt(posix.pid_t)})
758 ]u8 = undefined;
759 const path = std.fmt.bufPrint(&path_buf, "/proc/{d}/mem", .{pid}) catch
760 unreachable;
761 ma.mem = std.fs.openFileAbsolute(path, .{}) catch {
762 ma.mem.handle = -2;
763 break;
764 };
765 },
766 else => return (ma.mem.pread(buf, address) catch return false) == buf.len,
767 },
768 else => {},
769 }
770 if (!isValidMemory(address)) return false;
771 @memcpy(buf, @as([*]const u8, @ptrFromInt(address)));
772 return true;
773 }
774 pub fn load(ma: *MemoryAccessor, comptime Type: type, address: usize) ?Type {
775 var result: Type = undefined;
776 return if (ma.read(address, std.mem.asBytes(&result))) result else null;
777 }
778 };
779
780650 fn next_unwind(it: *StackIterator) !usize {
781651 const unwind_state = &it.unwind_state.?;
782652 const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc);
......@@ -785,7 +655,13 @@ pub const StackIterator = struct {
785655 // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding
786656 // via DWARF before attempting to use the compact unwind info will produce incorrect results.
787657 if (module.unwind_info) |unwind_info| {
788 if (Dwarf.unwindFrameMachO(&unwind_state.dwarf_context, &it.ma, unwind_info, module.eh_frame, module.base_address)) |return_address| {
658 if (SelfInfo.unwindFrameMachO(
659 &unwind_state.dwarf_context,
660 &it.ma,
661 unwind_info,
662 module.eh_frame,
663 module.base_address,
664 )) |return_address| {
789665 return return_address;
790666 } else |err| {
791667 if (err != error.RequiresDWARFUnwind) return err;
......@@ -796,7 +672,7 @@ pub const StackIterator = struct {
796672 }
797673
798674 if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| {
799 return di.unwindFrame(&unwind_state.dwarf_context, &it.ma, null);
675 return SelfInfo.unwindFrameDwarf(di, &unwind_state.dwarf_context, &it.ma, null);
800676 } else return error.MissingDebugInfo;
801677 }
802678
......@@ -845,22 +721,19 @@ pub const StackIterator = struct {
845721 }
846722};
847723
848const have_msync = switch (native_os) {
849 .wasi, .emscripten, .windows => false,
850 else => true,
851};
852
853724pub fn writeCurrentStackTrace(
854725 out_stream: anytype,
855 debug_info: *Info,
726 debug_info: *SelfInfo,
856727 tty_config: io.tty.Config,
857728 start_addr: ?usize,
858729) !void {
859 var context: ThreadContext = undefined;
860 const has_context = getContext(&context);
861730 if (native_os == .windows) {
731 var context: ThreadContext = undefined;
732 assert(getContext(&context));
862733 return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr);
863734 }
735 var context: ThreadContext = undefined;
736 const has_context = getContext(&context);
864737
865738 var it = (if (has_context) blk: {
866739 break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null;
......@@ -938,7 +811,7 @@ pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const w
938811
939812pub fn writeStackTraceWindows(
940813 out_stream: anytype,
941 debug_info: *Info,
814 debug_info: *SelfInfo,
942815 tty_config: io.tty.Config,
943816 context: *const windows.CONTEXT,
944817 start_addr: ?usize,
......@@ -957,52 +830,7 @@ pub fn writeStackTraceWindows(
957830 }
958831}
959832
960fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
961 var min: usize = 0;
962 var max: usize = symbols.len - 1;
963 while (min < max) {
964 const mid = min + (max - min) / 2;
965 const curr = &symbols[mid];
966 const next = &symbols[mid + 1];
967 if (address >= next.address()) {
968 min = mid + 1;
969 } else if (address < curr.address()) {
970 max = mid;
971 } else {
972 return curr;
973 }
974 }
975
976 const max_sym = &symbols[symbols.len - 1];
977 if (address >= max_sym.address())
978 return max_sym;
979
980 return null;
981}
982
983test machoSearchSymbols {
984 const symbols = [_]MachoSymbol{
985 .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
986 .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
987 .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
988 };
989
990 try testing.expectEqual(null, machoSearchSymbols(&symbols, 0));
991 try testing.expectEqual(null, machoSearchSymbols(&symbols, 99));
992 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?);
993 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?);
994 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?);
995
996 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?);
997 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?);
998 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?);
999
1000 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?);
1001 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?);
1002 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?);
1003}
1004
1005fn printUnknownSource(debug_info: *Info, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
833fn printUnknownSource(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
1006834 const module_name = debug_info.getModuleNameForAddress(address);
1007835 return printLineInfo(
1008836 out_stream,
......@@ -1015,14 +843,14 @@ fn printUnknownSource(debug_info: *Info, out_stream: anytype, address: usize, tt
1015843 );
1016844}
1017845
1018fn printLastUnwindError(it: *StackIterator, debug_info: *Info, out_stream: anytype, tty_config: io.tty.Config) void {
846fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, out_stream: anytype, tty_config: io.tty.Config) void {
1019847 if (!have_ucontext) return;
1020848 if (it.getLastError()) |unwind_error| {
1021849 printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config) catch {};
1022850 }
1023851}
1024852
1025fn printUnwindError(debug_info: *Info, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void {
853fn printUnwindError(debug_info: *SelfInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void {
1026854 const module_name = debug_info.getModuleNameForAddress(address) orelse "???";
1027855 try tty_config.setColor(out_stream, .dim);
1028856 if (err == error.MissingDebugInfo) {
......@@ -1033,7 +861,7 @@ fn printUnwindError(debug_info: *Info, out_stream: anytype, address: usize, err:
1033861 try tty_config.setColor(out_stream, .reset);
1034862}
1035863
1036pub fn printSourceAtAddress(debug_info: *Info, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
864pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
1037865 const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {
1038866 error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config),
1039867 else => return err,
......@@ -1058,7 +886,7 @@ pub fn printSourceAtAddress(debug_info: *Info, out_stream: anytype, address: usi
1058886
1059887fn printLineInfo(
1060888 out_stream: anytype,
1061 line_info: ?LineInfo,
889 line_info: ?SourceLocation,
1062890 address: usize,
1063891 symbol_name: []const u8,
1064892 compile_unit_name: []const u8,
......@@ -1104,428 +932,7 @@ fn printLineInfo(
1104932 }
1105933}
1106934
1107pub const OpenSelfDebugInfoError = error{
1108 MissingDebugInfo,
1109 UnsupportedOperatingSystem,
1110} || @typeInfo(@typeInfo(@TypeOf(Info.init)).Fn.return_type.?).ErrorUnion.error_set;
1111
1112pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!Info {
1113 nosuspend {
1114 if (builtin.strip_debug_info)
1115 return error.MissingDebugInfo;
1116 if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) {
1117 return root.os.debug.openSelfDebugInfo(allocator);
1118 }
1119 switch (native_os) {
1120 .linux,
1121 .freebsd,
1122 .netbsd,
1123 .dragonfly,
1124 .openbsd,
1125 .macos,
1126 .solaris,
1127 .illumos,
1128 .windows,
1129 => return try Info.init(allocator),
1130 else => return error.UnsupportedOperatingSystem,
1131 }
1132 }
1133}
1134
1135fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebugInfo {
1136 nosuspend {
1137 var di = ModuleDebugInfo{
1138 .base_address = undefined,
1139 .coff_image_base = coff_obj.getImageBase(),
1140 .coff_section_headers = undefined,
1141 };
1142
1143 if (coff_obj.getSectionByName(".debug_info")) |_| {
1144 // This coff file has embedded DWARF debug info
1145 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
1146 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1147
1148 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1149 sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
1150 break :blk .{
1151 .data = try coff_obj.getSectionDataAlloc(section_header, allocator),
1152 .virtual_address = section_header.virtual_address,
1153 .owned = true,
1154 };
1155 } else null;
1156 }
1157
1158 var dwarf = Dwarf{
1159 .endian = native_endian,
1160 .sections = sections,
1161 .is_macho = false,
1162 };
1163
1164 try Dwarf.open(&dwarf, allocator);
1165 di.dwarf = dwarf;
1166 }
1167
1168 const raw_path = try coff_obj.getPdbPath() orelse return di;
1169 const path = blk: {
1170 if (fs.path.isAbsolute(raw_path)) {
1171 break :blk raw_path;
1172 } else {
1173 const self_dir = try fs.selfExeDirPathAlloc(allocator);
1174 defer allocator.free(self_dir);
1175 break :blk try fs.path.join(allocator, &.{ self_dir, raw_path });
1176 }
1177 };
1178 defer if (path.ptr != raw_path.ptr) allocator.free(path);
1179
1180 di.pdb = pdb.Pdb.init(allocator, path) catch |err| switch (err) {
1181 error.FileNotFound, error.IsDir => {
1182 if (di.dwarf == null) return error.MissingDebugInfo;
1183 return di;
1184 },
1185 else => return err,
1186 };
1187 try di.pdb.?.parseInfoStream();
1188 try di.pdb.?.parseDbiStream();
1189
1190 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
1191 return error.InvalidDebugInfo;
1192
1193 // Only used by the pdb path
1194 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
1195 errdefer allocator.free(di.coff_section_headers);
1196
1197 return di;
1198 }
1199}
1200
1201fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 {
1202 const start = math.cast(usize, offset) orelse return error.Overflow;
1203 const end = start + (math.cast(usize, size) orelse return error.Overflow);
1204 return ptr[start..end];
1205}
1206
1207/// Reads debug info from an ELF file, or the current binary if none in specified.
1208/// If the required sections aren't present but a reference to external debug info is,
1209/// then this this function will recurse to attempt to load the debug sections from
1210/// an external file.
1211pub fn readElfDebugInfo(
1212 allocator: mem.Allocator,
1213 elf_filename: ?[]const u8,
1214 build_id: ?[]const u8,
1215 expected_crc: ?u32,
1216 parent_sections: *Dwarf.SectionArray,
1217 parent_mapped_mem: ?[]align(mem.page_size) const u8,
1218) !ModuleDebugInfo {
1219 nosuspend {
1220 const elf_file = (if (elf_filename) |filename| blk: {
1221 break :blk fs.cwd().openFile(filename, .{});
1222 } else fs.openSelfExe(.{})) catch |err| switch (err) {
1223 error.FileNotFound => return error.MissingDebugInfo,
1224 else => return err,
1225 };
1226
1227 const mapped_mem = try mapWholeFile(elf_file);
1228 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
1229
1230 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
1231 if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
1232 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
1233
1234 const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {
1235 elf.ELFDATA2LSB => .little,
1236 elf.ELFDATA2MSB => .big,
1237 else => return error.InvalidElfEndian,
1238 };
1239 assert(endian == native_endian); // this is our own debug info
1240
1241 const shoff = hdr.e_shoff;
1242 const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
1243 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow]));
1244 const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
1245 const shdrs = @as(
1246 [*]const elf.Shdr,
1247 @ptrCast(@alignCast(&mapped_mem[shoff])),
1248 )[0..hdr.e_shnum];
1249
1250 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
1251
1252 // Combine section list. This takes ownership over any owned sections from the parent scope.
1253 for (parent_sections, &sections) |*parent, *section| {
1254 if (parent.*) |*p| {
1255 section.* = p.*;
1256 p.owned = false;
1257 }
1258 }
1259 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1260
1261 var separate_debug_filename: ?[]const u8 = null;
1262 var separate_debug_crc: ?u32 = null;
1263
1264 for (shdrs) |*shdr| {
1265 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
1266 const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
1267
1268 if (mem.eql(u8, name, ".gnu_debuglink")) {
1269 const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1270 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
1271 const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr);
1272 const crc_bytes = gnu_debuglink[crc_offset..][0..4];
1273 separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian);
1274 separate_debug_filename = debug_filename;
1275 continue;
1276 }
1277
1278 var section_index: ?usize = null;
1279 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1280 if (mem.eql(u8, "." ++ section.name, name)) section_index = i;
1281 }
1282 if (section_index == null) continue;
1283 if (sections[section_index.?] != null) continue;
1284
1285 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1286 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
1287 var section_stream = io.fixedBufferStream(section_bytes);
1288 var section_reader = section_stream.reader();
1289 const chdr = section_reader.readStruct(elf.Chdr) catch continue;
1290 if (chdr.ch_type != .ZLIB) continue;
1291
1292 var zlib_stream = std.compress.zlib.decompressor(section_stream.reader());
1293
1294 const decompressed_section = try allocator.alloc(u8, chdr.ch_size);
1295 errdefer allocator.free(decompressed_section);
1296
1297 const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
1298 assert(read == decompressed_section.len);
1299
1300 break :blk .{
1301 .data = decompressed_section,
1302 .virtual_address = shdr.sh_addr,
1303 .owned = true,
1304 };
1305 } else .{
1306 .data = section_bytes,
1307 .virtual_address = shdr.sh_addr,
1308 .owned = false,
1309 };
1310 }
1311
1312 const missing_debug_info =
1313 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
1314 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
1315 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
1316 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
1317
1318 // Attempt to load debug info from an external file
1319 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
1320 if (missing_debug_info) {
1321
1322 // Only allow one level of debug info nesting
1323 if (parent_mapped_mem) |_| {
1324 return error.MissingDebugInfo;
1325 }
1326
1327 const global_debug_directories = [_][]const u8{
1328 "/usr/lib/debug",
1329 };
1330
1331 // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
1332 if (build_id) |id| blk: {
1333 if (id.len < 3) break :blk;
1334
1335 // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
1336 const extension = ".debug";
1337 var id_prefix_buf: [2]u8 = undefined;
1338 var filename_buf: [38 + extension.len]u8 = undefined;
1339
1340 _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable;
1341 const filename = std.fmt.bufPrint(
1342 &filename_buf,
1343 "{s}" ++ extension,
1344 .{std.fmt.fmtSliceHexLower(id[1..])},
1345 ) catch break :blk;
1346
1347 for (global_debug_directories) |global_directory| {
1348 const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename });
1349 defer allocator.free(path);
1350
1351 return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
1352 }
1353 }
1354
1355 // use the path from .gnu_debuglink, in the same search order as gdb
1356 if (separate_debug_filename) |separate_filename| blk: {
1357 if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo;
1358
1359 // <cwd>/<gnu_debuglink>
1360 if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1361
1362 // <cwd>/.debug/<gnu_debuglink>
1363 {
1364 const path = try fs.path.join(allocator, &.{ ".debug", separate_filename });
1365 defer allocator.free(path);
1366
1367 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1368 }
1369
1370 var cwd_buf: [fs.max_path_bytes]u8 = undefined;
1371 const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk;
1372
1373 // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
1374 for (global_debug_directories) |global_directory| {
1375 const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename });
1376 defer allocator.free(path);
1377 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1378 }
1379 }
1380
1381 return error.MissingDebugInfo;
1382 }
1383
1384 var di = Dwarf{
1385 .endian = endian,
1386 .sections = sections,
1387 .is_macho = false,
1388 };
1389
1390 try Dwarf.open(&di, allocator);
1391
1392 return ModuleDebugInfo{
1393 .base_address = undefined,
1394 .dwarf = di,
1395 .mapped_memory = parent_mapped_mem orelse mapped_mem,
1396 .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
1397 };
1398 }
1399}
1400
1401/// This takes ownership of macho_file: users of this function should not close
1402/// it themselves, even on error.
1403/// TODO it's weird to take ownership even on error, rework this code.
1404fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugInfo {
1405 const mapped_mem = try mapWholeFile(macho_file);
1406
1407 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
1408 if (hdr.magic != macho.MH_MAGIC_64)
1409 return error.InvalidDebugInfo;
1410
1411 var it = macho.LoadCommandIterator{
1412 .ncmds = hdr.ncmds,
1413 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
1414 };
1415 const symtab = while (it.next()) |cmd| switch (cmd.cmd()) {
1416 .SYMTAB => break cmd.cast(macho.symtab_command).?,
1417 else => {},
1418 } else return error.MissingDebugInfo;
1419
1420 const syms = @as(
1421 [*]const macho.nlist_64,
1422 @ptrCast(@alignCast(&mapped_mem[symtab.symoff])),
1423 )[0..symtab.nsyms];
1424 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
1425
1426 const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
1427
1428 var ofile: u32 = undefined;
1429 var last_sym: MachoSymbol = undefined;
1430 var symbol_index: usize = 0;
1431 var state: enum {
1432 init,
1433 oso_open,
1434 oso_close,
1435 bnsym,
1436 fun_strx,
1437 fun_size,
1438 ensym,
1439 } = .init;
1440
1441 for (syms) |*sym| {
1442 if (!sym.stab()) continue;
1443
1444 // TODO handle globals N_GSYM, and statics N_STSYM
1445 switch (sym.n_type) {
1446 macho.N_OSO => {
1447 switch (state) {
1448 .init, .oso_close => {
1449 state = .oso_open;
1450 ofile = sym.n_strx;
1451 },
1452 else => return error.InvalidDebugInfo,
1453 }
1454 },
1455 macho.N_BNSYM => {
1456 switch (state) {
1457 .oso_open, .ensym => {
1458 state = .bnsym;
1459 last_sym = .{
1460 .strx = 0,
1461 .addr = sym.n_value,
1462 .size = 0,
1463 .ofile = ofile,
1464 };
1465 },
1466 else => return error.InvalidDebugInfo,
1467 }
1468 },
1469 macho.N_FUN => {
1470 switch (state) {
1471 .bnsym => {
1472 state = .fun_strx;
1473 last_sym.strx = sym.n_strx;
1474 },
1475 .fun_strx => {
1476 state = .fun_size;
1477 last_sym.size = @as(u32, @intCast(sym.n_value));
1478 },
1479 else => return error.InvalidDebugInfo,
1480 }
1481 },
1482 macho.N_ENSYM => {
1483 switch (state) {
1484 .fun_size => {
1485 state = .ensym;
1486 symbols_buf[symbol_index] = last_sym;
1487 symbol_index += 1;
1488 },
1489 else => return error.InvalidDebugInfo,
1490 }
1491 },
1492 macho.N_SO => {
1493 switch (state) {
1494 .init, .oso_close => {},
1495 .oso_open, .ensym => {
1496 state = .oso_close;
1497 },
1498 else => return error.InvalidDebugInfo,
1499 }
1500 },
1501 else => {},
1502 }
1503 }
1504
1505 switch (state) {
1506 .init => return error.MissingDebugInfo,
1507 .oso_close => {},
1508 else => return error.InvalidDebugInfo,
1509 }
1510
1511 const symbols = try allocator.realloc(symbols_buf, symbol_index);
1512
1513 // Even though lld emits symbols in ascending order, this debug code
1514 // should work for programs linked in any valid way.
1515 // This sort is so that we can binary search later.
1516 mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan);
1517
1518 return ModuleDebugInfo{
1519 .base_address = undefined,
1520 .vmaddr_slide = undefined,
1521 .mapped_memory = mapped_mem,
1522 .ofiles = ModuleDebugInfo.OFileTable.init(allocator),
1523 .symbols = symbols,
1524 .strings = strings,
1525 };
1526}
1527
1528fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void {
935fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void {
1529936 // Need this to always block even in async I/O mode, because this could potentially
1530937 // be called from e.g. the event loop code crashing.
1531938 var f = try fs.cwd().openFile(line_info.file_name, .{});
......@@ -1591,7 +998,7 @@ test printLineFromFileAnyOs {
1591998
1592999 var test_dir = std.testing.tmpDir(.{});
15931000 defer test_dir.cleanup();
1594 // Relies on testing.tmpDir internals which is not ideal, but LineInfo requires paths.
1001 // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths.
15951002 const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
15961003 defer allocator.free(test_dir_path);
15971004
......@@ -1702,871 +1109,6 @@ test printLineFromFileAnyOs {
17021109 }
17031110}
17041111
1705const MachoSymbol = struct {
1706 strx: u32,
1707 addr: u64,
1708 size: u32,
1709 ofile: u32,
1710
1711 /// Returns the address from the macho file
1712 fn address(self: MachoSymbol) u64 {
1713 return self.addr;
1714 }
1715
1716 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
1717 _ = context;
1718 return lhs.addr < rhs.addr;
1719 }
1720};
1721
1722/// Takes ownership of file, even on error.
1723/// TODO it's weird to take ownership even on error, rework this code.
1724fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {
1725 nosuspend {
1726 defer file.close();
1727
1728 const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize);
1729 const mapped_mem = try posix.mmap(
1730 null,
1731 file_len,
1732 posix.PROT.READ,
1733 .{ .TYPE = .SHARED },
1734 file.handle,
1735 0,
1736 );
1737 errdefer posix.munmap(mapped_mem);
1738
1739 return mapped_mem;
1740 }
1741}
1742
1743pub const WindowsModuleInfo = struct {
1744 base_address: usize,
1745 size: u32,
1746 name: []const u8,
1747 handle: windows.HMODULE,
1748
1749 // Set when the image file needed to be mapped from disk
1750 mapped_file: ?struct {
1751 file: File,
1752 section_handle: windows.HANDLE,
1753 section_view: []const u8,
1754
1755 pub fn deinit(self: @This()) void {
1756 const process_handle = windows.GetCurrentProcess();
1757 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS);
1758 windows.CloseHandle(self.section_handle);
1759 self.file.close();
1760 }
1761 } = null,
1762};
1763
1764pub const Info = struct {
1765 allocator: mem.Allocator,
1766 address_map: std.AutoHashMap(usize, *ModuleDebugInfo),
1767 modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModuleInfo) else void,
1768
1769 pub fn init(allocator: mem.Allocator) !Info {
1770 var debug_info = Info{
1771 .allocator = allocator,
1772 .address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator),
1773 .modules = if (native_os == .windows) .{} else {},
1774 };
1775
1776 if (native_os == .windows) {
1777 errdefer debug_info.modules.deinit(allocator);
1778
1779 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
1780 if (handle == windows.INVALID_HANDLE_VALUE) {
1781 switch (windows.GetLastError()) {
1782 else => |err| return windows.unexpectedError(err),
1783 }
1784 }
1785 defer windows.CloseHandle(handle);
1786
1787 var module_entry: windows.MODULEENTRY32 = undefined;
1788 module_entry.dwSize = @sizeOf(windows.MODULEENTRY32);
1789 if (windows.kernel32.Module32First(handle, &module_entry) == 0) {
1790 return error.MissingDebugInfo;
1791 }
1792
1793 var module_valid = true;
1794 while (module_valid) {
1795 const module_info = try debug_info.modules.addOne(allocator);
1796 const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{};
1797 errdefer allocator.free(name);
1798
1799 module_info.* = .{
1800 .base_address = @intFromPtr(module_entry.modBaseAddr),
1801 .size = module_entry.modBaseSize,
1802 .name = name,
1803 .handle = module_entry.hModule,
1804 };
1805
1806 module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1;
1807 }
1808 }
1809
1810 return debug_info;
1811 }
1812
1813 pub fn deinit(self: *Info) void {
1814 var it = self.address_map.iterator();
1815 while (it.next()) |entry| {
1816 const mdi = entry.value_ptr.*;
1817 mdi.deinit(self.allocator);
1818 self.allocator.destroy(mdi);
1819 }
1820 self.address_map.deinit();
1821 if (native_os == .windows) {
1822 for (self.modules.items) |module| {
1823 self.allocator.free(module.name);
1824 if (module.mapped_file) |mapped_file| mapped_file.deinit();
1825 }
1826 self.modules.deinit(self.allocator);
1827 }
1828 }
1829
1830 pub fn getModuleForAddress(self: *Info, address: usize) !*ModuleDebugInfo {
1831 if (comptime builtin.target.isDarwin()) {
1832 return self.lookupModuleDyld(address);
1833 } else if (native_os == .windows) {
1834 return self.lookupModuleWin32(address);
1835 } else if (native_os == .haiku) {
1836 return self.lookupModuleHaiku(address);
1837 } else if (comptime builtin.target.isWasm()) {
1838 return self.lookupModuleWasm(address);
1839 } else {
1840 return self.lookupModuleDl(address);
1841 }
1842 }
1843
1844 // Returns the module name for a given address.
1845 // This can be called when getModuleForAddress fails, so implementations should provide
1846 // a path that doesn't rely on any side-effects of a prior successful module lookup.
1847 pub fn getModuleNameForAddress(self: *Info, address: usize) ?[]const u8 {
1848 if (comptime builtin.target.isDarwin()) {
1849 return self.lookupModuleNameDyld(address);
1850 } else if (native_os == .windows) {
1851 return self.lookupModuleNameWin32(address);
1852 } else if (native_os == .haiku) {
1853 return null;
1854 } else if (comptime builtin.target.isWasm()) {
1855 return null;
1856 } else {
1857 return self.lookupModuleNameDl(address);
1858 }
1859 }
1860
1861 fn lookupModuleDyld(self: *Info, address: usize) !*ModuleDebugInfo {
1862 const image_count = std.c._dyld_image_count();
1863
1864 var i: u32 = 0;
1865 while (i < image_count) : (i += 1) {
1866 const header = std.c._dyld_get_image_header(i) orelse continue;
1867 const base_address = @intFromPtr(header);
1868 if (address < base_address) continue;
1869 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
1870
1871 var it = macho.LoadCommandIterator{
1872 .ncmds = header.ncmds,
1873 .buffer = @alignCast(@as(
1874 [*]u8,
1875 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
1876 )[0..header.sizeofcmds]),
1877 };
1878
1879 var unwind_info: ?[]const u8 = null;
1880 var eh_frame: ?[]const u8 = null;
1881 while (it.next()) |cmd| switch (cmd.cmd()) {
1882 .SEGMENT_64 => {
1883 const segment_cmd = cmd.cast(macho.segment_command_64).?;
1884 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
1885
1886 const seg_start = segment_cmd.vmaddr + vmaddr_slide;
1887 const seg_end = seg_start + segment_cmd.vmsize;
1888 if (address >= seg_start and address < seg_end) {
1889 if (self.address_map.get(base_address)) |obj_di| {
1890 return obj_di;
1891 }
1892
1893 for (cmd.getSections()) |sect| {
1894 if (mem.eql(u8, "__unwind_info", sect.sectName())) {
1895 unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
1896 } else if (mem.eql(u8, "__eh_frame", sect.sectName())) {
1897 eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
1898 }
1899 }
1900
1901 const obj_di = try self.allocator.create(ModuleDebugInfo);
1902 errdefer self.allocator.destroy(obj_di);
1903
1904 const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0);
1905 const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) {
1906 error.FileNotFound => return error.MissingDebugInfo,
1907 else => return err,
1908 };
1909 obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
1910 obj_di.base_address = base_address;
1911 obj_di.vmaddr_slide = vmaddr_slide;
1912 obj_di.unwind_info = unwind_info;
1913 obj_di.eh_frame = eh_frame;
1914
1915 try self.address_map.putNoClobber(base_address, obj_di);
1916
1917 return obj_di;
1918 }
1919 },
1920 else => {},
1921 };
1922 }
1923
1924 return error.MissingDebugInfo;
1925 }
1926
1927 fn lookupModuleNameDyld(self: *Info, address: usize) ?[]const u8 {
1928 _ = self;
1929 const image_count = std.c._dyld_image_count();
1930
1931 var i: u32 = 0;
1932 while (i < image_count) : (i += 1) {
1933 const header = std.c._dyld_get_image_header(i) orelse continue;
1934 const base_address = @intFromPtr(header);
1935 if (address < base_address) continue;
1936 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
1937
1938 var it = macho.LoadCommandIterator{
1939 .ncmds = header.ncmds,
1940 .buffer = @alignCast(@as(
1941 [*]u8,
1942 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
1943 )[0..header.sizeofcmds]),
1944 };
1945
1946 while (it.next()) |cmd| switch (cmd.cmd()) {
1947 .SEGMENT_64 => {
1948 const segment_cmd = cmd.cast(macho.segment_command_64).?;
1949 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
1950
1951 const original_address = address - vmaddr_slide;
1952 const seg_start = segment_cmd.vmaddr;
1953 const seg_end = seg_start + segment_cmd.vmsize;
1954 if (original_address >= seg_start and original_address < seg_end) {
1955 return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0));
1956 }
1957 },
1958 else => {},
1959 };
1960 }
1961
1962 return null;
1963 }
1964
1965 fn lookupModuleWin32(self: *Info, address: usize) !*ModuleDebugInfo {
1966 for (self.modules.items) |*module| {
1967 if (address >= module.base_address and address < module.base_address + module.size) {
1968 if (self.address_map.get(module.base_address)) |obj_di| {
1969 return obj_di;
1970 }
1971
1972 const obj_di = try self.allocator.create(ModuleDebugInfo);
1973 errdefer self.allocator.destroy(obj_di);
1974
1975 const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size];
1976 var coff_obj = try coff.Coff.init(mapped_module, true);
1977
1978 // The string table is not mapped into memory by the loader, so if a section name is in the
1979 // string table then we have to map the full image file from disk. This can happen when
1980 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
1981 if (coff_obj.strtabRequired()) {
1982 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
1983 // openFileAbsoluteW requires the prefix to be present
1984 @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' });
1985
1986 const process_handle = windows.GetCurrentProcess();
1987 const len = windows.kernel32.GetModuleFileNameExW(
1988 process_handle,
1989 module.handle,
1990 @ptrCast(&name_buffer[4]),
1991 windows.PATH_MAX_WIDE,
1992 );
1993
1994 if (len == 0) return error.MissingDebugInfo;
1995 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
1996 error.FileNotFound => return error.MissingDebugInfo,
1997 else => return err,
1998 };
1999 errdefer coff_file.close();
2000
2001 var section_handle: windows.HANDLE = undefined;
2002 const create_section_rc = windows.ntdll.NtCreateSection(
2003 &section_handle,
2004 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
2005 null,
2006 null,
2007 windows.PAGE_READONLY,
2008 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
2009 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
2010 windows.SEC_COMMIT,
2011 coff_file.handle,
2012 );
2013 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
2014 errdefer windows.CloseHandle(section_handle);
2015
2016 var coff_len: usize = 0;
2017 var base_ptr: usize = 0;
2018 const map_section_rc = windows.ntdll.NtMapViewOfSection(
2019 section_handle,
2020 process_handle,
2021 @ptrCast(&base_ptr),
2022 null,
2023 0,
2024 null,
2025 &coff_len,
2026 .ViewUnmap,
2027 0,
2028 windows.PAGE_READONLY,
2029 );
2030 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
2031 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS);
2032
2033 const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len];
2034 coff_obj = try coff.Coff.init(section_view, false);
2035
2036 module.mapped_file = .{
2037 .file = coff_file,
2038 .section_handle = section_handle,
2039 .section_view = section_view,
2040 };
2041 }
2042 errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit();
2043
2044 obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj);
2045 obj_di.base_address = module.base_address;
2046
2047 try self.address_map.putNoClobber(module.base_address, obj_di);
2048 return obj_di;
2049 }
2050 }
2051
2052 return error.MissingDebugInfo;
2053 }
2054
2055 fn lookupModuleNameWin32(self: *Info, address: usize) ?[]const u8 {
2056 for (self.modules.items) |module| {
2057 if (address >= module.base_address and address < module.base_address + module.size) {
2058 return module.name;
2059 }
2060 }
2061 return null;
2062 }
2063
2064 fn lookupModuleNameDl(self: *Info, address: usize) ?[]const u8 {
2065 _ = self;
2066
2067 var ctx: struct {
2068 // Input
2069 address: usize,
2070 // Output
2071 name: []const u8 = "",
2072 } = .{ .address = address };
2073 const CtxTy = @TypeOf(ctx);
2074
2075 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
2076 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
2077 _ = size;
2078 if (context.address < info.addr) return;
2079 const phdrs = info.phdr[0..info.phnum];
2080 for (phdrs) |*phdr| {
2081 if (phdr.p_type != elf.PT_LOAD) continue;
2082
2083 const seg_start = info.addr +% phdr.p_vaddr;
2084 const seg_end = seg_start + phdr.p_memsz;
2085 if (context.address >= seg_start and context.address < seg_end) {
2086 context.name = mem.sliceTo(info.name, 0) orelse "";
2087 break;
2088 }
2089 } else return;
2090
2091 return error.Found;
2092 }
2093 }.callback)) {
2094 return null;
2095 } else |err| switch (err) {
2096 error.Found => return fs.path.basename(ctx.name),
2097 }
2098
2099 return null;
2100 }
2101
2102 fn lookupModuleDl(self: *Info, address: usize) !*ModuleDebugInfo {
2103 var ctx: struct {
2104 // Input
2105 address: usize,
2106 // Output
2107 base_address: usize = undefined,
2108 name: []const u8 = undefined,
2109 build_id: ?[]const u8 = null,
2110 gnu_eh_frame: ?[]const u8 = null,
2111 } = .{ .address = address };
2112 const CtxTy = @TypeOf(ctx);
2113
2114 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
2115 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
2116 _ = size;
2117 // The base address is too high
2118 if (context.address < info.addr)
2119 return;
2120
2121 const phdrs = info.phdr[0..info.phnum];
2122 for (phdrs) |*phdr| {
2123 if (phdr.p_type != elf.PT_LOAD) continue;
2124
2125 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
2126 const seg_start = info.addr +% phdr.p_vaddr;
2127 const seg_end = seg_start + phdr.p_memsz;
2128 if (context.address >= seg_start and context.address < seg_end) {
2129 // Android libc uses NULL instead of an empty string to mark the
2130 // main program
2131 context.name = mem.sliceTo(info.name, 0) orelse "";
2132 context.base_address = info.addr;
2133 break;
2134 }
2135 } else return;
2136
2137 for (info.phdr[0..info.phnum]) |phdr| {
2138 switch (phdr.p_type) {
2139 elf.PT_NOTE => {
2140 // Look for .note.gnu.build-id
2141 const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
2142 const name_size = mem.readInt(u32, note_bytes[0..4], native_endian);
2143 if (name_size != 4) continue;
2144 const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian);
2145 const note_type = mem.readInt(u32, note_bytes[8..12], native_endian);
2146 if (note_type != elf.NT_GNU_BUILD_ID) continue;
2147 if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
2148 context.build_id = note_bytes[16..][0..desc_size];
2149 },
2150 elf.PT_GNU_EH_FRAME => {
2151 context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
2152 },
2153 else => {},
2154 }
2155 }
2156
2157 // Stop the iteration
2158 return error.Found;
2159 }
2160 }.callback)) {
2161 return error.MissingDebugInfo;
2162 } else |err| switch (err) {
2163 error.Found => {},
2164 }
2165
2166 if (self.address_map.get(ctx.base_address)) |obj_di| {
2167 return obj_di;
2168 }
2169
2170 const obj_di = try self.allocator.create(ModuleDebugInfo);
2171 errdefer self.allocator.destroy(obj_di);
2172
2173 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
2174 if (ctx.gnu_eh_frame) |eh_frame_hdr| {
2175 // This is a special case - pointer offsets inside .eh_frame_hdr
2176 // are encoded relative to its base address, so we must use the
2177 // version that is already memory mapped, and not the one that
2178 // will be mapped separately from the ELF file.
2179 sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{
2180 .data = eh_frame_hdr,
2181 .owned = false,
2182 };
2183 }
2184
2185 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
2186 obj_di.base_address = ctx.base_address;
2187
2188 // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding
2189 obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {};
2190
2191 try self.address_map.putNoClobber(ctx.base_address, obj_di);
2192
2193 return obj_di;
2194 }
2195
2196 fn lookupModuleHaiku(self: *Info, address: usize) !*ModuleDebugInfo {
2197 _ = self;
2198 _ = address;
2199 @panic("TODO implement lookup module for Haiku");
2200 }
2201
2202 fn lookupModuleWasm(self: *Info, address: usize) !*ModuleDebugInfo {
2203 _ = self;
2204 _ = address;
2205 @panic("TODO implement lookup module for Wasm");
2206 }
2207};
2208
2209pub const ModuleDebugInfo = switch (native_os) {
2210 .macos, .ios, .watchos, .tvos, .visionos => struct {
2211 base_address: usize,
2212 vmaddr_slide: usize,
2213 mapped_memory: []align(mem.page_size) const u8,
2214 symbols: []const MachoSymbol,
2215 strings: [:0]const u8,
2216 ofiles: OFileTable,
2217
2218 // Backed by the in-memory sections mapped by the loader
2219 unwind_info: ?[]const u8 = null,
2220 eh_frame: ?[]const u8 = null,
2221
2222 const OFileTable = std.StringHashMap(OFileInfo);
2223 const OFileInfo = struct {
2224 di: Dwarf,
2225 addr_table: std.StringHashMap(u64),
2226 };
2227
2228 pub fn deinit(self: *@This(), allocator: mem.Allocator) void {
2229 var it = self.ofiles.iterator();
2230 while (it.next()) |entry| {
2231 const ofile = entry.value_ptr;
2232 ofile.di.deinit(allocator);
2233 ofile.addr_table.deinit();
2234 }
2235 self.ofiles.deinit();
2236 allocator.free(self.symbols);
2237 posix.munmap(self.mapped_memory);
2238 }
2239
2240 fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !*OFileInfo {
2241 const o_file = try fs.cwd().openFile(o_file_path, .{});
2242 const mapped_mem = try mapWholeFile(o_file);
2243
2244 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
2245 if (hdr.magic != std.macho.MH_MAGIC_64)
2246 return error.InvalidDebugInfo;
2247
2248 var segcmd: ?macho.LoadCommandIterator.LoadCommand = null;
2249 var symtabcmd: ?macho.symtab_command = null;
2250 var it = macho.LoadCommandIterator{
2251 .ncmds = hdr.ncmds,
2252 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
2253 };
2254 while (it.next()) |cmd| switch (cmd.cmd()) {
2255 .SEGMENT_64 => segcmd = cmd,
2256 .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?,
2257 else => {},
2258 };
2259
2260 if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo;
2261
2262 // Parse symbols
2263 const strtab = @as(
2264 [*]const u8,
2265 @ptrCast(&mapped_mem[symtabcmd.?.stroff]),
2266 )[0 .. symtabcmd.?.strsize - 1 :0];
2267 const symtab = @as(
2268 [*]const macho.nlist_64,
2269 @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])),
2270 )[0..symtabcmd.?.nsyms];
2271
2272 // TODO handle tentative (common) symbols
2273 var addr_table = std.StringHashMap(u64).init(allocator);
2274 try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len)));
2275 for (symtab) |sym| {
2276 if (sym.n_strx == 0) continue;
2277 if (sym.undf() or sym.tentative() or sym.abs()) continue;
2278 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
2279 // TODO is it possible to have a symbol collision?
2280 addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);
2281 }
2282
2283 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
2284 if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{
2285 .data = eh_frame,
2286 .owned = false,
2287 };
2288
2289 for (segcmd.?.getSections()) |sect| {
2290 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
2291
2292 var section_index: ?usize = null;
2293 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
2294 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i;
2295 }
2296 if (section_index == null) continue;
2297
2298 const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size);
2299 sections[section_index.?] = .{
2300 .data = section_bytes,
2301 .virtual_address = sect.addr,
2302 .owned = false,
2303 };
2304 }
2305
2306 const missing_debug_info =
2307 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
2308 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
2309 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
2310 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
2311 if (missing_debug_info) return error.MissingDebugInfo;
2312
2313 var di = Dwarf{
2314 .endian = .little,
2315 .sections = sections,
2316 .is_macho = true,
2317 };
2318
2319 try Dwarf.open(&di, allocator);
2320 const info = OFileInfo{
2321 .di = di,
2322 .addr_table = addr_table,
2323 };
2324
2325 // Add the debug info to the cache
2326 const result = try self.ofiles.getOrPut(o_file_path);
2327 assert(!result.found_existing);
2328 result.value_ptr.* = info;
2329
2330 return result.value_ptr;
2331 }
2332
2333 pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
2334 nosuspend {
2335 const result = try self.getOFileInfoForAddress(allocator, address);
2336 if (result.symbol == null) return .{};
2337
2338 // Take the symbol name from the N_FUN STAB entry, we're going to
2339 // use it if we fail to find the DWARF infos
2340 const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0);
2341 if (result.o_file_info == null) return .{ .symbol_name = stab_symbol };
2342
2343 // Translate again the address, this time into an address inside the
2344 // .o file
2345 const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{
2346 .symbol_name = "???",
2347 };
2348
2349 const addr_off = result.relocated_address - result.symbol.?.addr;
2350 const o_file_di = &result.o_file_info.?.di;
2351 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
2352 return SymbolInfo{
2353 .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
2354 .compile_unit_name = compile_unit.die.getAttrString(
2355 o_file_di,
2356 DW.AT.name,
2357 o_file_di.section(.debug_str),
2358 compile_unit.*,
2359 ) catch |err| switch (err) {
2360 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
2361 },
2362 .line_info = o_file_di.getLineNumberInfo(
2363 allocator,
2364 compile_unit.*,
2365 relocated_address_o + addr_off,
2366 ) catch |err| switch (err) {
2367 error.MissingDebugInfo, error.InvalidDebugInfo => null,
2368 else => return err,
2369 },
2370 };
2371 } else |err| switch (err) {
2372 error.MissingDebugInfo, error.InvalidDebugInfo => {
2373 return SymbolInfo{ .symbol_name = stab_symbol };
2374 },
2375 else => return err,
2376 }
2377 }
2378 }
2379
2380 pub fn getOFileInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !struct {
2381 relocated_address: usize,
2382 symbol: ?*const MachoSymbol = null,
2383 o_file_info: ?*OFileInfo = null,
2384 } {
2385 nosuspend {
2386 // Translate the VA into an address into this object
2387 const relocated_address = address - self.vmaddr_slide;
2388
2389 // Find the .o file where this symbol is defined
2390 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{
2391 .relocated_address = relocated_address,
2392 };
2393
2394 // Check if its debug infos are already in the cache
2395 const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
2396 const o_file_info = self.ofiles.getPtr(o_file_path) orelse
2397 (self.loadOFile(allocator, o_file_path) catch |err| switch (err) {
2398 error.FileNotFound,
2399 error.MissingDebugInfo,
2400 error.InvalidDebugInfo,
2401 => return .{
2402 .relocated_address = relocated_address,
2403 .symbol = symbol,
2404 },
2405 else => return err,
2406 });
2407
2408 return .{
2409 .relocated_address = relocated_address,
2410 .symbol = symbol,
2411 .o_file_info = o_file_info,
2412 };
2413 }
2414 }
2415
2416 pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf {
2417 return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;
2418 }
2419 },
2420 .uefi, .windows => struct {
2421 base_address: usize,
2422 pdb: ?pdb.Pdb = null,
2423 dwarf: ?Dwarf = null,
2424 coff_image_base: u64,
2425
2426 /// Only used if pdb is non-null
2427 coff_section_headers: []coff.SectionHeader,
2428
2429 pub fn deinit(self: *@This(), allocator: mem.Allocator) void {
2430 if (self.dwarf) |*dwarf| {
2431 dwarf.deinit(allocator);
2432 }
2433
2434 if (self.pdb) |*p| {
2435 p.deinit();
2436 allocator.free(self.coff_section_headers);
2437 }
2438 }
2439
2440 fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo {
2441 var coff_section: *align(1) const coff.SectionHeader = undefined;
2442 const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| {
2443 if (sect_contrib.Section > self.coff_section_headers.len) continue;
2444 // Remember that SectionContribEntry.Section is 1-based.
2445 coff_section = &self.coff_section_headers[sect_contrib.Section - 1];
2446
2447 const vaddr_start = coff_section.virtual_address + sect_contrib.Offset;
2448 const vaddr_end = vaddr_start + sect_contrib.Size;
2449 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
2450 break sect_contrib.ModuleIndex;
2451 }
2452 } else {
2453 // we have no information to add to the address
2454 return null;
2455 };
2456
2457 const module = (try self.pdb.?.getModule(mod_index)) orelse
2458 return error.InvalidDebugInfo;
2459 const obj_basename = fs.path.basename(module.obj_file_name);
2460
2461 const symbol_name = self.pdb.?.getSymbolName(
2462 module,
2463 relocated_address - coff_section.virtual_address,
2464 ) orelse "???";
2465 const opt_line_info = try self.pdb.?.getLineNumberInfo(
2466 module,
2467 relocated_address - coff_section.virtual_address,
2468 );
2469
2470 return SymbolInfo{
2471 .symbol_name = symbol_name,
2472 .compile_unit_name = obj_basename,
2473 .line_info = opt_line_info,
2474 };
2475 }
2476
2477 pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
2478 // Translate the VA into an address into this object
2479 const relocated_address = address - self.base_address;
2480
2481 if (self.pdb != null) {
2482 if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol;
2483 }
2484
2485 if (self.dwarf) |*dwarf| {
2486 const dwarf_address = relocated_address + self.coff_image_base;
2487 return getSymbolFromDwarf(allocator, dwarf_address, dwarf);
2488 }
2489
2490 return SymbolInfo{};
2491 }
2492
2493 pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf {
2494 _ = allocator;
2495 _ = address;
2496
2497 return switch (self.debug_data) {
2498 .dwarf => |*dwarf| dwarf,
2499 else => null,
2500 };
2501 }
2502 },
2503 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
2504 base_address: usize,
2505 dwarf: Dwarf,
2506 mapped_memory: []align(mem.page_size) const u8,
2507 external_mapped_memory: ?[]align(mem.page_size) const u8,
2508
2509 pub fn deinit(self: *@This(), allocator: mem.Allocator) void {
2510 self.dwarf.deinit(allocator);
2511 posix.munmap(self.mapped_memory);
2512 if (self.external_mapped_memory) |m| posix.munmap(m);
2513 }
2514
2515 pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
2516 // Translate the VA into an address into this object
2517 const relocated_address = address - self.base_address;
2518 return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf);
2519 }
2520
2521 pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf {
2522 _ = allocator;
2523 _ = address;
2524 return &self.dwarf;
2525 }
2526 },
2527 .wasi, .emscripten => struct {
2528 pub fn deinit(self: *@This(), allocator: mem.Allocator) void {
2529 _ = self;
2530 _ = allocator;
2531 }
2532
2533 pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
2534 _ = self;
2535 _ = allocator;
2536 _ = address;
2537 return SymbolInfo{};
2538 }
2539
2540 pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf {
2541 _ = self;
2542 _ = allocator;
2543 _ = address;
2544 return null;
2545 }
2546 },
2547 else => Dwarf,
2548};
2549
2550fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *Dwarf) !SymbolInfo {
2551 if (nosuspend di.findCompileUnit(address)) |compile_unit| {
2552 return SymbolInfo{
2553 .symbol_name = nosuspend di.getSymbolName(address) orelse "???",
2554 .compile_unit_name = compile_unit.die.getAttrString(di, DW.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) {
2555 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
2556 },
2557 .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) {
2558 error.MissingDebugInfo, error.InvalidDebugInfo => null,
2559 else => return err,
2560 },
2561 };
2562 } else |err| switch (err) {
2563 error.MissingDebugInfo, error.InvalidDebugInfo => {
2564 return SymbolInfo{};
2565 },
2566 else => return err,
2567 }
2568}
2569
25701112/// TODO multithreaded awareness
25711113var debug_info_allocator: ?mem.Allocator = null;
25721114var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;
......@@ -2687,7 +1229,7 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa
26871229 posix.abort();
26881230}
26891231
2690fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*const anyopaque) void {
1232fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void {
26911233 const stderr = io.getStdErr().writer();
26921234 _ = switch (sig) {
26931235 posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL
......@@ -2713,7 +1255,7 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*const anyo
27131255 .arm,
27141256 .aarch64,
27151257 => {
2716 const ctx: *const posix.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
1258 const ctx: *posix.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
27171259 dumpStackTraceFromBase(ctx);
27181260 },
27191261 else => {},
......@@ -2802,7 +1344,7 @@ test "manage resources correctly" {
28021344 }
28031345
28041346 const writer = std.io.null_writer;
2805 var di = try openSelfDebugInfo(testing.allocator);
1347 var di = try SelfInfo.open(testing.allocator);
28061348 defer di.deinit();
28071349 try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.io.getStdErr()));
28081350}
......@@ -2939,6 +1481,99 @@ pub const SafetyLock = struct {
29391481 }
29401482};
29411483
1484/// Deprecated. Don't use this, just read from your memory directly.
1485///
1486/// This only exists because someone was too lazy to rework logic that used to
1487/// operate on an open file to operate on a memory buffer instead.
1488pub const DeprecatedFixedBufferReader = struct {
1489 buf: []const u8,
1490 pos: usize = 0,
1491 endian: std.builtin.Endian,
1492
1493 pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer };
1494
1495 pub fn seekTo(fbr: *DeprecatedFixedBufferReader, pos: u64) Error!void {
1496 if (pos > fbr.buf.len) return error.EndOfBuffer;
1497 fbr.pos = @intCast(pos);
1498 }
1499
1500 pub fn seekForward(fbr: *DeprecatedFixedBufferReader, amount: u64) Error!void {
1501 if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer;
1502 fbr.pos += @intCast(amount);
1503 }
1504
1505 pub inline fn readByte(fbr: *DeprecatedFixedBufferReader) Error!u8 {
1506 if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer;
1507 defer fbr.pos += 1;
1508 return fbr.buf[fbr.pos];
1509 }
1510
1511 pub fn readByteSigned(fbr: *DeprecatedFixedBufferReader) Error!i8 {
1512 return @bitCast(try fbr.readByte());
1513 }
1514
1515 pub fn readInt(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T {
1516 const size = @divExact(@typeInfo(T).Int.bits, 8);
1517 if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer;
1518 defer fbr.pos += size;
1519 return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian);
1520 }
1521
1522 pub fn readIntChecked(
1523 fbr: *DeprecatedFixedBufferReader,
1524 comptime T: type,
1525 ma: *MemoryAccessor,
1526 ) Error!T {
1527 if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null)
1528 return error.InvalidBuffer;
1529
1530 return fbr.readInt(T);
1531 }
1532
1533 pub fn readUleb128(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T {
1534 return std.leb.readUleb128(T, fbr);
1535 }
1536
1537 pub fn readIleb128(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T {
1538 return std.leb.readIleb128(T, fbr);
1539 }
1540
1541 pub fn readAddress(fbr: *DeprecatedFixedBufferReader, format: std.dwarf.Format) Error!u64 {
1542 return switch (format) {
1543 .@"32" => try fbr.readInt(u32),
1544 .@"64" => try fbr.readInt(u64),
1545 };
1546 }
1547
1548 pub fn readAddressChecked(
1549 fbr: *DeprecatedFixedBufferReader,
1550 format: std.dwarf.Format,
1551 ma: *MemoryAccessor,
1552 ) Error!u64 {
1553 return switch (format) {
1554 .@"32" => try fbr.readIntChecked(u32, ma),
1555 .@"64" => try fbr.readIntChecked(u64, ma),
1556 };
1557 }
1558
1559 pub fn readBytes(fbr: *DeprecatedFixedBufferReader, len: usize) Error![]const u8 {
1560 if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer;
1561 defer fbr.pos += len;
1562 return fbr.buf[fbr.pos..][0..len];
1563 }
1564
1565 pub fn readBytesTo(fbr: *DeprecatedFixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 {
1566 const end = @call(.always_inline, std.mem.indexOfScalarPos, .{
1567 u8,
1568 fbr.buf,
1569 fbr.pos,
1570 sentinel,
1571 }) orelse return error.EndOfBuffer;
1572 defer fbr.pos = end + 1;
1573 return fbr.buf[fbr.pos..end :sentinel];
1574 }
1575};
1576
29421577/// Detect whether the program is being executed in the Valgrind virtual machine.
29431578///
29441579/// When Valgrind integrations are disabled, this returns comptime-known false.
......@@ -2950,5 +1585,9 @@ pub inline fn inValgrind() bool {
29501585}
29511586
29521587test {
1588 _ = &Dwarf;
1589 _ = &MemoryAccessor;
1590 _ = &Pdb;
1591 _ = &SelfInfo;
29531592 _ = &dumpHex;
29541593}
lib/std/debug/Dwarf.zig+119-803
......@@ -1,23 +1,32 @@
11//! Implements parsing, decoding, and caching of DWARF information.
22//!
3//! This API does not assume the current executable is itself the thing being
4//! debugged, however, it does assume the debug info has the same CPU
5//! architecture and OS as the current executable. It is planned to remove this
6//! limitation.
7//!
38//! For unopinionated types and bits, see `std.dwarf`.
49
510const builtin = @import("builtin");
11const native_endian = builtin.cpu.arch.endian();
12
613const std = @import("../std.zig");
7const AT = DW.AT;
814const Allocator = std.mem.Allocator;
915const DW = std.dwarf;
16const AT = DW.AT;
1017const EH = DW.EH;
1118const FORM = DW.FORM;
1219const Format = DW.Format;
1320const RLE = DW.RLE;
14const StackIterator = std.debug.StackIterator;
1521const UT = DW.UT;
1622const assert = std.debug.assert;
1723const cast = std.math.cast;
1824const maxInt = std.math.maxInt;
19const native_endian = builtin.cpu.arch.endian();
2025const readInt = std.mem.readInt;
26const MemoryAccessor = std.debug.MemoryAccessor;
27
28/// Did I mention this is deprecated?
29const DeprecatedFixedBufferReader = std.debug.DeprecatedFixedBufferReader;
2130
2231const Dwarf = @This();
2332
......@@ -153,7 +162,7 @@ pub const FormValue = union(enum) {
153162 .string => |s| return s,
154163 .strp => |off| return di.getString(off),
155164 .line_strp => |off| return di.getLineString(off),
156 else => return badDwarf(),
165 else => return bad(),
157166 }
158167 }
159168
......@@ -162,8 +171,8 @@ pub const FormValue = union(enum) {
162171 inline .udata,
163172 .sdata,
164173 .sec_offset,
165 => |c| cast(U, c) orelse badDwarf(),
166 else => badDwarf(),
174 => |c| cast(U, c) orelse bad(),
175 else => bad(),
167176 };
168177 }
169178};
......@@ -237,25 +246,25 @@ pub const Die = struct {
237246 .string => |value| return value,
238247 .strp => |offset| return di.getString(offset),
239248 .strx => |index| {
240 const debug_str_offsets = di.section(.debug_str_offsets) orelse return badDwarf();
241 if (compile_unit.str_offsets_base == 0) return badDwarf();
249 const debug_str_offsets = di.section(.debug_str_offsets) orelse return bad();
250 if (compile_unit.str_offsets_base == 0) return bad();
242251 switch (compile_unit.format) {
243252 .@"32" => {
244253 const byte_offset = compile_unit.str_offsets_base + 4 * index;
245 if (byte_offset + 4 > debug_str_offsets.len) return badDwarf();
254 if (byte_offset + 4 > debug_str_offsets.len) return bad();
246255 const offset = readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian);
247256 return getStringGeneric(opt_str, offset);
248257 },
249258 .@"64" => {
250259 const byte_offset = compile_unit.str_offsets_base + 8 * index;
251 if (byte_offset + 8 > debug_str_offsets.len) return badDwarf();
260 if (byte_offset + 8 > debug_str_offsets.len) return bad();
252261 const offset = readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian);
253262 return getStringGeneric(opt_str, offset);
254263 },
255264 }
256265 },
257266 .line_strp => |offset| return di.getLineString(offset),
258 else => return badDwarf(),
267 else => return bad(),
259268 }
260269 }
261270};
......@@ -279,7 +288,7 @@ pub const ExceptionFrameHeader = struct {
279288 EH.PE.sdata8,
280289 => 16,
281290 // This is a binary search table, so all entries must be the same length
282 else => return badDwarf(),
291 else => return bad(),
283292 };
284293 }
285294
......@@ -287,7 +296,7 @@ pub const ExceptionFrameHeader = struct {
287296 self: ExceptionFrameHeader,
288297 comptime T: type,
289298 ptr: usize,
290 ma: *StackIterator.MemoryAccessor,
299 ma: *MemoryAccessor,
291300 eh_frame_len: ?usize,
292301 ) bool {
293302 if (eh_frame_len) |len| {
......@@ -304,7 +313,7 @@ pub const ExceptionFrameHeader = struct {
304313 /// If `eh_frame_len` is provided, then these checks can be skipped.
305314 pub fn findEntry(
306315 self: ExceptionFrameHeader,
307 ma: *StackIterator.MemoryAccessor,
316 ma: *MemoryAccessor,
308317 eh_frame_len: ?usize,
309318 eh_frame_hdr_ptr: usize,
310319 pc: usize,
......@@ -316,7 +325,7 @@ pub const ExceptionFrameHeader = struct {
316325 var left: usize = 0;
317326 var len: usize = self.fde_count;
318327
319 var fbr: FixedBufferReader = .{ .buf = self.entries, .endian = native_endian };
328 var fbr: DeprecatedFixedBufferReader = .{ .buf = self.entries, .endian = native_endian };
320329
321330 while (len > 1) {
322331 const mid = left + len / 2;
......@@ -326,7 +335,7 @@ pub const ExceptionFrameHeader = struct {
326335 .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]),
327336 .follow_indirect = true,
328337 .data_rel_base = eh_frame_hdr_ptr,
329 }) orelse return badDwarf();
338 }) orelse return bad();
330339
331340 if (pc < pc_begin) {
332341 len /= 2;
......@@ -337,7 +346,7 @@ pub const ExceptionFrameHeader = struct {
337346 }
338347 }
339348
340 if (len == 0) return badDwarf();
349 if (len == 0) return bad();
341350 fbr.pos = left * entry_size;
342351
343352 // Read past the pc_begin field of the entry
......@@ -345,36 +354,36 @@ pub const ExceptionFrameHeader = struct {
345354 .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]),
346355 .follow_indirect = true,
347356 .data_rel_base = eh_frame_hdr_ptr,
348 }) orelse return badDwarf();
357 }) orelse return bad();
349358
350359 const fde_ptr = cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
351360 .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]),
352361 .follow_indirect = true,
353362 .data_rel_base = eh_frame_hdr_ptr,
354 }) orelse return badDwarf()) orelse return badDwarf();
363 }) orelse return bad()) orelse return bad();
355364
356 if (fde_ptr < self.eh_frame_ptr) return badDwarf();
365 if (fde_ptr < self.eh_frame_ptr) return bad();
357366
358367 // Even if eh_frame_len is not specified, all ranges accssed are checked via MemoryAccessor
359368 const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse maxInt(u32)];
360369
361370 const fde_offset = fde_ptr - self.eh_frame_ptr;
362 var eh_frame_fbr: FixedBufferReader = .{
371 var eh_frame_fbr: DeprecatedFixedBufferReader = .{
363372 .buf = eh_frame,
364373 .pos = fde_offset,
365374 .endian = native_endian,
366375 };
367376
368377 const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame);
369 if (!self.isValidPtr(u8, @intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf();
370 if (fde_entry_header.type != .fde) return badDwarf();
378 if (!self.isValidPtr(u8, @intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return bad();
379 if (fde_entry_header.type != .fde) return bad();
371380
372381 // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable
373382 const cie_offset = fde_entry_header.type.fde;
374383 try eh_frame_fbr.seekTo(cie_offset);
375384 const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame);
376 if (!self.isValidPtr(u8, @intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf();
377 if (cie_entry_header.type != .cie) return badDwarf();
385 if (!self.isValidPtr(u8, @intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return bad();
386 if (cie_entry_header.type != .cie) return bad();
378387
379388 cie.* = try CommonInformationEntry.parse(
380389 cie_entry_header.entry_bytes,
......@@ -417,17 +426,17 @@ pub const EntryHeader = struct {
417426 }
418427
419428 /// Reads a header for either an FDE or a CIE, then advances the fbr to the position after the trailing structure.
420 /// `fbr` must be a FixedBufferReader backed by either the .eh_frame or .debug_frame sections.
429 /// `fbr` must be a DeprecatedFixedBufferReader backed by either the .eh_frame or .debug_frame sections.
421430 pub fn read(
422 fbr: *FixedBufferReader,
423 opt_ma: ?*StackIterator.MemoryAccessor,
431 fbr: *DeprecatedFixedBufferReader,
432 opt_ma: ?*MemoryAccessor,
424433 dwarf_section: Section.Id,
425434 ) !EntryHeader {
426435 assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame);
427436
428437 const length_offset = fbr.pos;
429438 const unit_header = try readUnitHeader(fbr, opt_ma);
430 const unit_length = cast(usize, unit_header.unit_length) orelse return badDwarf();
439 const unit_length = cast(usize, unit_header.unit_length) orelse return bad();
431440 if (unit_length == 0) return .{
432441 .length_offset = length_offset,
433442 .format = unit_header.format,
......@@ -532,7 +541,7 @@ pub const CommonInformationEntry = struct {
532541 ) !CommonInformationEntry {
533542 if (addr_size_bytes > 8) return error.UnsupportedAddrSize;
534543
535 var fbr: FixedBufferReader = .{ .buf = cie_bytes, .endian = endian };
544 var fbr: DeprecatedFixedBufferReader = .{ .buf = cie_bytes, .endian = endian };
536545
537546 const version = try fbr.readByte();
538547 switch (dwarf_section) {
......@@ -550,15 +559,15 @@ pub const CommonInformationEntry = struct {
550559 while (aug_byte != 0) : (aug_byte = try fbr.readByte()) {
551560 switch (aug_byte) {
552561 'z' => {
553 if (aug_str_len != 0) return badDwarf();
562 if (aug_str_len != 0) return bad();
554563 has_aug_data = true;
555564 },
556565 'e' => {
557 if (has_aug_data or aug_str_len != 0) return badDwarf();
558 if (try fbr.readByte() != 'h') return badDwarf();
566 if (has_aug_data or aug_str_len != 0) return bad();
567 if (try fbr.readByte() != 'h') return bad();
559568 has_eh_data = true;
560569 },
561 else => if (has_eh_data) return badDwarf(),
570 else => if (has_eh_data) return bad(),
562571 }
563572
564573 aug_str_len += 1;
......@@ -604,7 +613,7 @@ pub const CommonInformationEntry = struct {
604613 fde_pointer_enc = try fbr.readByte();
605614 },
606615 'S', 'B', 'G' => {},
607 else => return badDwarf(),
616 else => return bad(),
608617 }
609618 }
610619
......@@ -666,17 +675,17 @@ pub const FrameDescriptionEntry = struct {
666675 ) !FrameDescriptionEntry {
667676 if (addr_size_bytes > 8) return error.InvalidAddrSize;
668677
669 var fbr: FixedBufferReader = .{ .buf = fde_bytes, .endian = endian };
678 var fbr: DeprecatedFixedBufferReader = .{ .buf = fde_bytes, .endian = endian };
670679
671680 const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{
672681 .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset),
673682 .follow_indirect = is_runtime,
674 }) orelse return badDwarf();
683 }) orelse return bad();
675684
676685 const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{
677686 .pc_rel_base = 0,
678687 .follow_indirect = false,
679 }) orelse return badDwarf();
688 }) orelse return bad();
680689
681690 var aug_data: []const u8 = &[_]u8{};
682691 const lsda_pointer = if (cie.aug_str.len > 0) blk: {
......@@ -708,54 +717,6 @@ pub const FrameDescriptionEntry = struct {
708717 }
709718};
710719
711pub const UnwindContext = struct {
712 allocator: Allocator,
713 cfa: ?usize,
714 pc: usize,
715 thread_context: *std.debug.ThreadContext,
716 reg_context: abi.RegisterContext,
717 vm: call_frame.VirtualMachine,
718 stack_machine: expression.StackMachine(.{ .call_frame_context = true }),
719
720 pub fn init(
721 allocator: Allocator,
722 thread_context: *const std.debug.ThreadContext,
723 ) !UnwindContext {
724 const pc = abi.stripInstructionPtrAuthCode(
725 (try abi.regValueNative(
726 usize,
727 thread_context,
728 abi.ipRegNum(),
729 null,
730 )).*,
731 );
732
733 const context_copy = try allocator.create(std.debug.ThreadContext);
734 std.debug.copyContext(thread_context, context_copy);
735
736 return .{
737 .allocator = allocator,
738 .cfa = null,
739 .pc = pc,
740 .thread_context = context_copy,
741 .reg_context = undefined,
742 .vm = .{},
743 .stack_machine = .{},
744 };
745 }
746
747 pub fn deinit(self: *UnwindContext) void {
748 self.vm.deinit(self.allocator);
749 self.stack_machine.deinit(self.allocator);
750 self.allocator.destroy(self.thread_context);
751 self.* = undefined;
752 }
753
754 pub fn getFp(self: *const UnwindContext) !usize {
755 return (try abi.regValueNative(usize, self.thread_context, abi.fpRegNum(self.reg_context), self.reg_context)).*;
756 }
757};
758
759720const num_sections = std.enums.directEnumArrayLen(Section.Id, 0);
760721pub const SectionArray = [num_sections]?Section;
761722pub const null_section_array = [_]?Section{null} ** num_sections;
......@@ -817,7 +778,7 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 {
817778}
818779
819780fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void {
820 var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian };
781 var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian };
821782 var this_unit_offset: u64 = 0;
822783
823784 while (this_unit_offset < fbr.buf.len) {
......@@ -828,20 +789,20 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void {
828789 const next_offset = unit_header.header_length + unit_header.unit_length;
829790
830791 const version = try fbr.readInt(u16);
831 if (version < 2 or version > 5) return badDwarf();
792 if (version < 2 or version > 5) return bad();
832793
833794 var address_size: u8 = undefined;
834795 var debug_abbrev_offset: u64 = undefined;
835796 if (version >= 5) {
836797 const unit_type = try fbr.readInt(u8);
837 if (unit_type != DW.UT.compile) return badDwarf();
798 if (unit_type != DW.UT.compile) return bad();
838799 address_size = try fbr.readByte();
839800 debug_abbrev_offset = try fbr.readAddress(unit_header.format);
840801 } else {
841802 debug_abbrev_offset = try fbr.readAddress(unit_header.format);
842803 address_size = try fbr.readByte();
843804 }
844 if (address_size != @sizeOf(usize)) return badDwarf();
805 if (address_size != @sizeOf(usize)) return bad();
845806
846807 const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset);
847808
......@@ -915,28 +876,28 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void {
915876
916877 // Follow the DIE it points to and repeat
917878 const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin);
918 if (ref_offset > next_offset) return badDwarf();
879 if (ref_offset > next_offset) return bad();
919880 try fbr.seekTo(this_unit_offset + ref_offset);
920881 this_die_obj = (try parseDie(
921882 &fbr,
922883 attrs_bufs[2],
923884 abbrev_table,
924885 unit_header.format,
925 )) orelse return badDwarf();
886 )) orelse return bad();
926887 } else if (this_die_obj.getAttr(AT.specification)) |_| {
927888 const after_die_offset = fbr.pos;
928889 defer fbr.pos = after_die_offset;
929890
930891 // Follow the DIE it points to and repeat
931892 const ref_offset = try this_die_obj.getAttrRef(AT.specification);
932 if (ref_offset > next_offset) return badDwarf();
893 if (ref_offset > next_offset) return bad();
933894 try fbr.seekTo(this_unit_offset + ref_offset);
934895 this_die_obj = (try parseDie(
935896 &fbr,
936897 attrs_bufs[2],
937898 abbrev_table,
938899 unit_header.format,
939 )) orelse return badDwarf();
900 )) orelse return bad();
940901 } else {
941902 break :x null;
942903 }
......@@ -950,7 +911,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void {
950911 const pc_end = switch (high_pc_value.*) {
951912 .addr => |value| value,
952913 .udata => |offset| low_pc + offset,
953 else => return badDwarf(),
914 else => return bad(),
954915 };
955916
956917 try di.func_list.append(allocator, .{
......@@ -1004,7 +965,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void {
1004965}
1005966
1006967fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void {
1007 var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian };
968 var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian };
1008969 var this_unit_offset: u64 = 0;
1009970
1010971 var attrs_buf = std.ArrayList(Die.Attr).init(allocator);
......@@ -1018,20 +979,20 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void {
1018979 const next_offset = unit_header.header_length + unit_header.unit_length;
1019980
1020981 const version = try fbr.readInt(u16);
1021 if (version < 2 or version > 5) return badDwarf();
982 if (version < 2 or version > 5) return bad();
1022983
1023984 var address_size: u8 = undefined;
1024985 var debug_abbrev_offset: u64 = undefined;
1025986 if (version >= 5) {
1026987 const unit_type = try fbr.readInt(u8);
1027 if (unit_type != UT.compile) return badDwarf();
988 if (unit_type != UT.compile) return bad();
1028989 address_size = try fbr.readByte();
1029990 debug_abbrev_offset = try fbr.readAddress(unit_header.format);
1030991 } else {
1031992 debug_abbrev_offset = try fbr.readAddress(unit_header.format);
1032993 address_size = try fbr.readByte();
1033994 }
1034 if (address_size != @sizeOf(usize)) return badDwarf();
995 if (address_size != @sizeOf(usize)) return bad();
1035996
1036997 const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset);
1037998
......@@ -1046,9 +1007,9 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void {
10461007 attrs_buf.items,
10471008 abbrev_table,
10481009 unit_header.format,
1049 )) orelse return badDwarf();
1010 )) orelse return bad();
10501011
1051 if (compile_unit_die.tag_id != DW.TAG.compile_unit) return badDwarf();
1012 if (compile_unit_die.tag_id != DW.TAG.compile_unit) return bad();
10521013
10531014 compile_unit_die.attrs = try allocator.dupe(Die.Attr, compile_unit_die.attrs);
10541015
......@@ -1070,7 +1031,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void {
10701031 const pc_end = switch (high_pc_value.*) {
10711032 .addr => |value| value,
10721033 .udata => |offset| low_pc + offset,
1073 else => return badDwarf(),
1034 else => return bad(),
10741035 };
10751036 break :x PcRange{
10761037 .start = low_pc,
......@@ -1096,7 +1057,7 @@ const DebugRangeIterator = struct {
10961057 section_type: Section.Id,
10971058 di: *const Dwarf,
10981059 compile_unit: *const CompileUnit,
1099 fbr: FixedBufferReader,
1060 fbr: DeprecatedFixedBufferReader,
11001061
11011062 pub fn init(ranges_value: *const FormValue, di: *const Dwarf, compile_unit: *const CompileUnit) !@This() {
11021063 const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges;
......@@ -1108,19 +1069,19 @@ const DebugRangeIterator = struct {
11081069 switch (compile_unit.format) {
11091070 .@"32" => {
11101071 const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx));
1111 if (offset_loc + 4 > debug_ranges.len) return badDwarf();
1072 if (offset_loc + 4 > debug_ranges.len) return bad();
11121073 const offset = readInt(u32, debug_ranges[offset_loc..][0..4], di.endian);
11131074 break :off compile_unit.rnglists_base + offset;
11141075 },
11151076 .@"64" => {
11161077 const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx));
1117 if (offset_loc + 8 > debug_ranges.len) return badDwarf();
1078 if (offset_loc + 8 > debug_ranges.len) return bad();
11181079 const offset = readInt(u64, debug_ranges[offset_loc..][0..8], di.endian);
11191080 break :off compile_unit.rnglists_base + offset;
11201081 },
11211082 }
11221083 },
1123 else => return badDwarf(),
1084 else => return bad(),
11241085 };
11251086
11261087 // All the addresses in the list are relative to the value
......@@ -1139,7 +1100,7 @@ const DebugRangeIterator = struct {
11391100 .compile_unit = compile_unit,
11401101 .fbr = .{
11411102 .buf = debug_ranges,
1142 .pos = cast(usize, ranges_offset) orelse return badDwarf(),
1103 .pos = cast(usize, ranges_offset) orelse return bad(),
11431104 .endian = di.endian,
11441105 },
11451106 };
......@@ -1214,7 +1175,7 @@ const DebugRangeIterator = struct {
12141175 .end_addr = end_addr,
12151176 };
12161177 },
1217 else => return badDwarf(),
1178 else => return bad(),
12181179 }
12191180 },
12201181 .debug_ranges => {
......@@ -1251,7 +1212,7 @@ pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*const CompileUni
12511212 }
12521213 }
12531214
1254 return missingDwarf();
1215 return missing();
12551216}
12561217
12571218/// Gets an already existing AbbrevTable given the abbrev_offset, or if not found,
......@@ -1270,9 +1231,9 @@ fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const
12701231}
12711232
12721233fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table {
1273 var fbr: FixedBufferReader = .{
1234 var fbr: DeprecatedFixedBufferReader = .{
12741235 .buf = di.section(.debug_abbrev).?,
1275 .pos = cast(usize, offset) orelse return badDwarf(),
1236 .pos = cast(usize, offset) orelse return bad(),
12761237 .endian = di.endian,
12771238 };
12781239
......@@ -1322,14 +1283,14 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table
13221283}
13231284
13241285fn parseDie(
1325 fbr: *FixedBufferReader,
1286 fbr: *DeprecatedFixedBufferReader,
13261287 attrs_buf: []Die.Attr,
13271288 abbrev_table: *const Abbrev.Table,
13281289 format: Format,
13291290) !?Die {
13301291 const abbrev_code = try fbr.readUleb128(u64);
13311292 if (abbrev_code == 0) return null;
1332 const table_entry = abbrev_table.get(abbrev_code) orelse return badDwarf();
1293 const table_entry = abbrev_table.get(abbrev_code) orelse return bad();
13331294
13341295 const attrs = attrs_buf[0..table_entry.attrs.len];
13351296 for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = Die.Attr{
......@@ -1353,19 +1314,19 @@ pub fn getLineNumberInfo(
13531314 allocator: Allocator,
13541315 compile_unit: CompileUnit,
13551316 target_address: u64,
1356) !std.debug.LineInfo {
1317) !std.debug.SourceLocation {
13571318 const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit);
13581319 const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
13591320
1360 var fbr: FixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian };
1321 var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian };
13611322 try fbr.seekTo(line_info_offset);
13621323
13631324 const unit_header = try readUnitHeader(&fbr, null);
1364 if (unit_header.unit_length == 0) return missingDwarf();
1325 if (unit_header.unit_length == 0) return missing();
13651326 const next_offset = unit_header.header_length + unit_header.unit_length;
13661327
13671328 const version = try fbr.readInt(u16);
1368 if (version < 2) return badDwarf();
1329 if (version < 2) return bad();
13691330
13701331 var addr_size: u8 = switch (unit_header.format) {
13711332 .@"32" => 4,
......@@ -1381,7 +1342,7 @@ pub fn getLineNumberInfo(
13811342 const prog_start_offset = fbr.pos + prologue_length;
13821343
13831344 const minimum_instruction_length = try fbr.readByte();
1384 if (minimum_instruction_length == 0) return badDwarf();
1345 if (minimum_instruction_length == 0) return bad();
13851346
13861347 if (version >= 4) {
13871348 // maximum_operations_per_instruction
......@@ -1392,7 +1353,7 @@ pub fn getLineNumberInfo(
13921353 const line_base = try fbr.readByteSigned();
13931354
13941355 const line_range = try fbr.readByte();
1395 if (line_range == 0) return badDwarf();
1356 if (line_range == 0) return bad();
13961357
13971358 const opcode_base = try fbr.readByte();
13981359
......@@ -1433,7 +1394,7 @@ pub fn getLineNumberInfo(
14331394 {
14341395 var dir_ent_fmt_buf: [10]FileEntFmt = undefined;
14351396 const directory_entry_format_count = try fbr.readByte();
1436 if (directory_entry_format_count > dir_ent_fmt_buf.len) return badDwarf();
1397 if (directory_entry_format_count > dir_ent_fmt_buf.len) return bad();
14371398 for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| {
14381399 ent_fmt.* = .{
14391400 .content_type_code = try fbr.readUleb128(u8),
......@@ -1461,7 +1422,7 @@ pub fn getLineNumberInfo(
14611422 DW.LNCT.size => e.size = try form_value.getUInt(u64),
14621423 DW.LNCT.MD5 => e.md5 = switch (form_value) {
14631424 .data16 => |data16| data16.*,
1464 else => return badDwarf(),
1425 else => return bad(),
14651426 },
14661427 else => continue,
14671428 }
......@@ -1473,7 +1434,7 @@ pub fn getLineNumberInfo(
14731434
14741435 var file_ent_fmt_buf: [10]FileEntFmt = undefined;
14751436 const file_name_entry_format_count = try fbr.readByte();
1476 if (file_name_entry_format_count > file_ent_fmt_buf.len) return badDwarf();
1437 if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad();
14771438 for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| {
14781439 ent_fmt.* = .{
14791440 .content_type_code = try fbr.readUleb128(u8),
......@@ -1501,7 +1462,7 @@ pub fn getLineNumberInfo(
15011462 DW.LNCT.size => e.size = try form_value.getUInt(u64),
15021463 DW.LNCT.MD5 => e.md5 = switch (form_value) {
15031464 .data16 => |data16| data16.*,
1504 else => return badDwarf(),
1465 else => return bad(),
15051466 },
15061467 else => continue,
15071468 }
......@@ -1527,7 +1488,7 @@ pub fn getLineNumberInfo(
15271488
15281489 if (opcode == DW.LNS.extended_op) {
15291490 const op_size = try fbr.readUleb128(u64);
1530 if (op_size < 1) return badDwarf();
1491 if (op_size < 1) return bad();
15311492 const sub_op = try fbr.readByte();
15321493 switch (sub_op) {
15331494 DW.LNE.end_sequence => {
......@@ -1600,14 +1561,14 @@ pub fn getLineNumberInfo(
16001561 },
16011562 DW.LNS.set_prologue_end => {},
16021563 else => {
1603 if (opcode - 1 >= standard_opcode_lengths.len) return badDwarf();
1564 if (opcode - 1 >= standard_opcode_lengths.len) return bad();
16041565 try fbr.seekForward(standard_opcode_lengths[opcode - 1]);
16051566 },
16061567 }
16071568 }
16081569 }
16091570
1610 return missingDwarf();
1571 return missing();
16111572}
16121573
16131574fn getString(di: Dwarf, offset: u64) ![:0]const u8 {
......@@ -1619,28 +1580,28 @@ fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 {
16191580}
16201581
16211582fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {
1622 const debug_addr = di.section(.debug_addr) orelse return badDwarf();
1583 const debug_addr = di.section(.debug_addr) orelse return bad();
16231584
16241585 // addr_base points to the first item after the header, however we
16251586 // need to read the header to know the size of each item. Empirically,
16261587 // it may disagree with is_64 on the compile unit.
16271588 // The header is 8 or 12 bytes depending on is_64.
1628 if (compile_unit.addr_base < 8) return badDwarf();
1589 if (compile_unit.addr_base < 8) return bad();
16291590
16301591 const version = readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian);
1631 if (version != 5) return badDwarf();
1592 if (version != 5) return bad();
16321593
16331594 const addr_size = debug_addr[compile_unit.addr_base - 2];
16341595 const seg_size = debug_addr[compile_unit.addr_base - 1];
16351596
16361597 const byte_offset = @as(usize, @intCast(compile_unit.addr_base + (addr_size + seg_size) * index));
1637 if (byte_offset + addr_size > debug_addr.len) return badDwarf();
1598 if (byte_offset + addr_size > debug_addr.len) return bad();
16381599 return switch (addr_size) {
16391600 1 => debug_addr[byte_offset],
16401601 2 => readInt(u16, debug_addr[byte_offset..][0..2], di.endian),
16411602 4 => readInt(u32, debug_addr[byte_offset..][0..4], di.endian),
16421603 8 => readInt(u64, debug_addr[byte_offset..][0..8], di.endian),
1643 else => badDwarf(),
1604 else => bad(),
16441605 };
16451606}
16461607
......@@ -1650,7 +1611,7 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {
16501611/// of FDEs is built for binary searching during unwinding.
16511612pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void {
16521613 if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {
1653 var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian };
1614 var fbr: DeprecatedFixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian };
16541615
16551616 const version = try fbr.readByte();
16561617 if (version != 1) break :blk;
......@@ -1665,16 +1626,16 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize)
16651626 const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{
16661627 .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]),
16671628 .follow_indirect = true,
1668 }) orelse return badDwarf()) orelse return badDwarf();
1629 }) orelse return bad()) orelse return bad();
16691630
16701631 const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{
16711632 .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]),
16721633 .follow_indirect = true,
1673 }) orelse return badDwarf()) orelse return badDwarf();
1634 }) orelse return bad()) orelse return bad();
16741635
16751636 const entry_size = try ExceptionFrameHeader.entrySize(table_enc);
16761637 const entries_len = fde_count * entry_size;
1677 if (entries_len > eh_frame_hdr.len - fbr.pos) return badDwarf();
1638 if (entries_len > eh_frame_hdr.len - fbr.pos) return bad();
16781639
16791640 di.eh_frame_hdr = .{
16801641 .eh_frame_ptr = eh_frame_ptr,
......@@ -1690,7 +1651,7 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize)
16901651 const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame };
16911652 for (frame_sections) |frame_section| {
16921653 if (di.section(frame_section)) |section_data| {
1693 var fbr: FixedBufferReader = .{ .buf = section_data, .endian = di.endian };
1654 var fbr: DeprecatedFixedBufferReader = .{ .buf = section_data, .endian = di.endian };
16941655 while (fbr.pos < fbr.buf.len) {
16951656 const entry_header = try EntryHeader.read(&fbr, null, frame_section);
16961657 switch (entry_header.type) {
......@@ -1708,7 +1669,7 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize)
17081669 try di.cie_map.put(allocator, entry_header.length_offset, cie);
17091670 },
17101671 .fde => |cie_offset| {
1711 const cie = di.cie_map.get(cie_offset) orelse return badDwarf();
1672 const cie = di.cie_map.get(cie_offset) orelse return bad();
17121673 const fde = try FrameDescriptionEntry.parse(
17131674 entry_header.entry_bytes,
17141675 di.sectionVirtualOffset(frame_section, base_address).?,
......@@ -1733,205 +1694,8 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize)
17331694 }
17341695}
17351696
1736/// Unwind a stack frame using DWARF unwinding info, updating the register context.
1737///
1738/// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE.
1739/// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE.
1740///
1741/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
1742/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
1743pub fn unwindFrame(di: *const Dwarf, context: *UnwindContext, ma: *StackIterator.MemoryAccessor, explicit_fde_offset: ?usize) !usize {
1744 if (!comptime abi.supportsUnwinding(builtin.target)) return error.UnsupportedCpuArchitecture;
1745 if (context.pc == 0) return 0;
1746
1747 // Find the FDE and CIE
1748 var cie: CommonInformationEntry = undefined;
1749 var fde: FrameDescriptionEntry = undefined;
1750
1751 if (explicit_fde_offset) |fde_offset| {
1752 const dwarf_section: Section.Id = .eh_frame;
1753 const frame_section = di.section(dwarf_section) orelse return error.MissingFDE;
1754 if (fde_offset >= frame_section.len) return error.MissingFDE;
1755
1756 var fbr: FixedBufferReader = .{
1757 .buf = frame_section,
1758 .pos = fde_offset,
1759 .endian = di.endian,
1760 };
1761
1762 const fde_entry_header = try EntryHeader.read(&fbr, null, dwarf_section);
1763 if (fde_entry_header.type != .fde) return error.MissingFDE;
1764
1765 const cie_offset = fde_entry_header.type.fde;
1766 try fbr.seekTo(cie_offset);
1767
1768 fbr.endian = native_endian;
1769 const cie_entry_header = try EntryHeader.read(&fbr, null, dwarf_section);
1770 if (cie_entry_header.type != .cie) return badDwarf();
1771
1772 cie = try CommonInformationEntry.parse(
1773 cie_entry_header.entry_bytes,
1774 0,
1775 true,
1776 cie_entry_header.format,
1777 dwarf_section,
1778 cie_entry_header.length_offset,
1779 @sizeOf(usize),
1780 native_endian,
1781 );
1782
1783 fde = try FrameDescriptionEntry.parse(
1784 fde_entry_header.entry_bytes,
1785 0,
1786 true,
1787 cie,
1788 @sizeOf(usize),
1789 native_endian,
1790 );
1791 } else if (di.eh_frame_hdr) |header| {
1792 const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null;
1793 try header.findEntry(
1794 ma,
1795 eh_frame_len,
1796 @intFromPtr(di.section(.eh_frame_hdr).?.ptr),
1797 context.pc,
1798 &cie,
1799 &fde,
1800 );
1801 } else {
1802 const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct {
1803 pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order {
1804 if (pc < mid_item.pc_begin) return .lt;
1805
1806 const range_end = mid_item.pc_begin + mid_item.pc_range;
1807 if (pc < range_end) return .eq;
1808
1809 return .gt;
1810 }
1811 }.compareFn);
1812
1813 fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE;
1814 cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;
1815 }
1816
1817 var expression_context: expression.Context = .{
1818 .format = cie.format,
1819 .memory_accessor = ma,
1820 .compile_unit = di.findCompileUnit(fde.pc_begin) catch null,
1821 .thread_context = context.thread_context,
1822 .reg_context = context.reg_context,
1823 .cfa = context.cfa,
1824 };
1825
1826 context.vm.reset();
1827 context.reg_context.eh_frame = cie.version != 4;
1828 context.reg_context.is_macho = di.is_macho;
1829
1830 const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde);
1831 context.cfa = switch (row.cfa.rule) {
1832 .val_offset => |offset| blk: {
1833 const register = row.cfa.register orelse return error.InvalidCFARule;
1834 const value = readInt(usize, (try abi.regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian);
1835 break :blk try call_frame.applyOffset(value, offset);
1836 },
1837 .expression => |expr| blk: {
1838 context.stack_machine.reset();
1839 const value = try context.stack_machine.run(
1840 expr,
1841 context.allocator,
1842 expression_context,
1843 context.cfa,
1844 );
1845
1846 if (value) |v| {
1847 if (v != .generic) return error.InvalidExpressionValue;
1848 break :blk v.generic;
1849 } else return error.NoExpressionValue;
1850 },
1851 else => return error.InvalidCFARule,
1852 };
1853
1854 if (ma.load(usize, context.cfa.?) == null) return error.InvalidCFA;
1855 expression_context.cfa = context.cfa;
1856
1857 // Buffering the modifications is done because copying the thread context is not portable,
1858 // some implementations (ie. darwin) use internal pointers to the mcontext.
1859 var arena = std.heap.ArenaAllocator.init(context.allocator);
1860 defer arena.deinit();
1861 const update_allocator = arena.allocator();
1862
1863 const RegisterUpdate = struct {
1864 // Backed by thread_context
1865 dest: []u8,
1866 // Backed by arena
1867 src: []const u8,
1868 prev: ?*@This(),
1869 };
1870
1871 var update_tail: ?*RegisterUpdate = null;
1872 var has_return_address = true;
1873 for (context.vm.rowColumns(row)) |column| {
1874 if (column.register) |register| {
1875 if (register == cie.return_address_register) {
1876 has_return_address = column.rule != .undefined;
1877 }
1878
1879 const dest = try abi.regBytes(context.thread_context, register, context.reg_context);
1880 const src = try update_allocator.alloc(u8, dest.len);
1881
1882 const prev = update_tail;
1883 update_tail = try update_allocator.create(RegisterUpdate);
1884 update_tail.?.* = .{
1885 .dest = dest,
1886 .src = src,
1887 .prev = prev,
1888 };
1889
1890 try column.resolveValue(
1891 context,
1892 expression_context,
1893 ma,
1894 src,
1895 );
1896 }
1897 }
1898
1899 // On all implemented architectures, the CFA is defined as being the previous frame's SP
1900 (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?;
1901
1902 while (update_tail) |tail| {
1903 @memcpy(tail.dest, tail.src);
1904 update_tail = tail.prev;
1905 }
1906
1907 if (has_return_address) {
1908 context.pc = abi.stripInstructionPtrAuthCode(readInt(usize, (try abi.regBytes(
1909 context.thread_context,
1910 cie.return_address_register,
1911 context.reg_context,
1912 ))[0..@sizeOf(usize)], native_endian));
1913 } else {
1914 context.pc = 0;
1915 }
1916
1917 (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), context.reg_context)).* = context.pc;
1918
1919 // The call instruction will have pushed the address of the instruction that follows the call as the return address.
1920 // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in
1921 // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up
1922 // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this,
1923 // we subtract one so that the next lookup is guaranteed to land inside the
1924 //
1925 // The exception to this rule is signal frames, where we return execution would be returned to the instruction
1926 // that triggered the handler.
1927 const return_address = context.pc;
1928 if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1;
1929
1930 return return_address;
1931}
1932
19331697fn parseFormValue(
1934 fbr: *FixedBufferReader,
1698 fbr: *DeprecatedFixedBufferReader,
19351699 form_id: u64,
19361700 format: Format,
19371701 implicit_const: ?i64,
......@@ -1990,12 +1754,12 @@ fn parseFormValue(
19901754 FORM.strx => .{ .strx = try fbr.readUleb128(usize) },
19911755 FORM.line_strp => .{ .line_strp = try fbr.readAddress(format) },
19921756 FORM.indirect => parseFormValue(fbr, try fbr.readUleb128(u64), format, implicit_const),
1993 FORM.implicit_const => .{ .sdata = implicit_const orelse return badDwarf() },
1757 FORM.implicit_const => .{ .sdata = implicit_const orelse return bad() },
19941758 FORM.loclistx => .{ .loclistx = try fbr.readUleb128(u64) },
19951759 FORM.rnglistx => .{ .rnglistx = try fbr.readUleb128(u64) },
19961760 else => {
19971761 //debug.print("unrecognized form id: {x}\n", .{form_id});
1998 return badDwarf();
1762 return bad();
19991763 },
20001764 };
20011765}
......@@ -2084,27 +1848,27 @@ const LineNumberProgram = struct {
20841848 self: *LineNumberProgram,
20851849 allocator: Allocator,
20861850 file_entries: []const FileEntry,
2087 ) !?std.debug.LineInfo {
1851 ) !?std.debug.SourceLocation {
20881852 if (self.prev_valid and
20891853 self.target_address >= self.prev_address and
20901854 self.target_address < self.address)
20911855 {
20921856 const file_index = if (self.version >= 5) self.prev_file else i: {
2093 if (self.prev_file == 0) return missingDwarf();
1857 if (self.prev_file == 0) return missing();
20941858 break :i self.prev_file - 1;
20951859 };
20961860
2097 if (file_index >= file_entries.len) return badDwarf();
1861 if (file_index >= file_entries.len) return bad();
20981862 const file_entry = &file_entries[file_index];
20991863
2100 if (file_entry.dir_index >= self.include_dirs.len) return badDwarf();
1864 if (file_entry.dir_index >= self.include_dirs.len) return bad();
21011865 const dir_name = self.include_dirs[file_entry.dir_index].path;
21021866
21031867 const file_name = try std.fs.path.join(allocator, &[_][]const u8{
21041868 dir_name, file_entry.path,
21051869 });
21061870
2107 return std.debug.LineInfo{
1871 return std.debug.SourceLocation{
21081872 .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0,
21091873 .column = self.prev_column,
21101874 .file_name = file_name,
......@@ -2128,14 +1892,14 @@ const UnitHeader = struct {
21281892 header_length: u4,
21291893 unit_length: u64,
21301894};
2131fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*StackIterator.MemoryAccessor) !UnitHeader {
1895fn readUnitHeader(fbr: *DeprecatedFixedBufferReader, opt_ma: ?*MemoryAccessor) !UnitHeader {
21321896 return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) {
21331897 0...0xfffffff0 - 1 => |unit_length| .{
21341898 .format = .@"32",
21351899 .header_length = 4,
21361900 .unit_length = unit_length,
21371901 },
2138 0xfffffff0...0xffffffff - 1 => badDwarf(),
1902 0xfffffff0...0xffffffff - 1 => bad(),
21391903 0xffffffff => .{
21401904 .format = .@"64",
21411905 .header_length = 12,
......@@ -2145,7 +1909,7 @@ fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*StackIterator.MemoryAccesso
21451909}
21461910
21471911/// Returns the DWARF register number for an x86_64 register number found in compact unwind info
2148fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {
1912pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {
21491913 return switch (unwind_reg_number) {
21501914 1 => 3, // RBX
21511915 2 => 12, // R12
......@@ -2159,473 +1923,25 @@ fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {
21591923
21601924/// This function is to make it handy to comment out the return and make it
21611925/// into a crash when working on this file.
2162fn badDwarf() error{InvalidDebugInfo} {
2163 //if (true) @panic("badDwarf"); // can be handy to uncomment when working on this file
1926pub fn bad() error{InvalidDebugInfo} {
1927 //if (true) @panic("bad dwarf"); // can be handy to uncomment when working on this file
21641928 return error.InvalidDebugInfo;
21651929}
21661930
2167fn missingDwarf() error{MissingDebugInfo} {
2168 //if (true) @panic("missingDwarf"); // can be handy to uncomment when working on this file
1931fn missing() error{MissingDebugInfo} {
1932 //if (true) @panic("missing dwarf"); // can be handy to uncomment when working on this file
21691933 return error.MissingDebugInfo;
21701934}
21711935
21721936fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
2173 const str = opt_str orelse return badDwarf();
2174 if (offset > str.len) return badDwarf();
2175 const casted_offset = cast(usize, offset) orelse return badDwarf();
1937 const str = opt_str orelse return bad();
1938 if (offset > str.len) return bad();
1939 const casted_offset = cast(usize, offset) orelse return bad();
21761940 // Valid strings always have a terminating zero byte
2177 const last = std.mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return badDwarf();
1941 const last = std.mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return bad();
21781942 return str[casted_offset..last :0];
21791943}
21801944
2181// Reading debug info needs to be fast, even when compiled in debug mode,
2182// so avoid using a `std.io.FixedBufferStream` which is too slow.
2183pub const FixedBufferReader = struct {
2184 buf: []const u8,
2185 pos: usize = 0,
2186 endian: std.builtin.Endian,
2187
2188 pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer };
2189
2190 fn seekTo(fbr: *FixedBufferReader, pos: u64) Error!void {
2191 if (pos > fbr.buf.len) return error.EndOfBuffer;
2192 fbr.pos = @intCast(pos);
2193 }
2194
2195 fn seekForward(fbr: *FixedBufferReader, amount: u64) Error!void {
2196 if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer;
2197 fbr.pos += @intCast(amount);
2198 }
2199
2200 pub inline fn readByte(fbr: *FixedBufferReader) Error!u8 {
2201 if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer;
2202 defer fbr.pos += 1;
2203 return fbr.buf[fbr.pos];
2204 }
2205
2206 fn readByteSigned(fbr: *FixedBufferReader) Error!i8 {
2207 return @bitCast(try fbr.readByte());
2208 }
2209
2210 fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T {
2211 const size = @divExact(@typeInfo(T).Int.bits, 8);
2212 if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer;
2213 defer fbr.pos += size;
2214 return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian);
2215 }
2216
2217 fn readIntChecked(
2218 fbr: *FixedBufferReader,
2219 comptime T: type,
2220 ma: *std.debug.StackIterator.MemoryAccessor,
2221 ) Error!T {
2222 if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null)
2223 return error.InvalidBuffer;
2224
2225 return fbr.readInt(T);
2226 }
2227
2228 fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T {
2229 return std.leb.readUleb128(T, fbr);
2230 }
2231
2232 fn readIleb128(fbr: *FixedBufferReader, comptime T: type) Error!T {
2233 return std.leb.readIleb128(T, fbr);
2234 }
2235
2236 fn readAddress(fbr: *FixedBufferReader, format: Format) Error!u64 {
2237 return switch (format) {
2238 .@"32" => try fbr.readInt(u32),
2239 .@"64" => try fbr.readInt(u64),
2240 };
2241 }
2242
2243 fn readAddressChecked(
2244 fbr: *FixedBufferReader,
2245 format: Format,
2246 ma: *std.debug.StackIterator.MemoryAccessor,
2247 ) Error!u64 {
2248 return switch (format) {
2249 .@"32" => try fbr.readIntChecked(u32, ma),
2250 .@"64" => try fbr.readIntChecked(u64, ma),
2251 };
2252 }
2253
2254 fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 {
2255 if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer;
2256 defer fbr.pos += len;
2257 return fbr.buf[fbr.pos..][0..len];
2258 }
2259
2260 fn readBytesTo(fbr: *FixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 {
2261 const end = @call(.always_inline, std.mem.indexOfScalarPos, .{
2262 u8,
2263 fbr.buf,
2264 fbr.pos,
2265 sentinel,
2266 }) orelse return error.EndOfBuffer;
2267 defer fbr.pos = end + 1;
2268 return fbr.buf[fbr.pos..end :sentinel];
2269 }
2270};
2271
2272/// Unwind a frame using MachO compact unwind info (from __unwind_info).
2273/// If the compact encoding can't encode a way to unwind a frame, it will
2274/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
2275pub fn unwindFrameMachO(
2276 context: *UnwindContext,
2277 ma: *StackIterator.MemoryAccessor,
2278 unwind_info: []const u8,
2279 eh_frame: ?[]const u8,
2280 module_base_address: usize,
2281) !usize {
2282 const macho = std.macho;
2283
2284 const header = std.mem.bytesAsValue(
2285 macho.unwind_info_section_header,
2286 unwind_info[0..@sizeOf(macho.unwind_info_section_header)],
2287 );
2288 const indices = std.mem.bytesAsSlice(
2289 macho.unwind_info_section_header_index_entry,
2290 unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)],
2291 );
2292 if (indices.len == 0) return error.MissingUnwindInfo;
2293
2294 const mapped_pc = context.pc - module_base_address;
2295 const second_level_index = blk: {
2296 var left: usize = 0;
2297 var len: usize = indices.len;
2298
2299 while (len > 1) {
2300 const mid = left + len / 2;
2301 const offset = indices[mid].functionOffset;
2302 if (mapped_pc < offset) {
2303 len /= 2;
2304 } else {
2305 left = mid;
2306 if (mapped_pc == offset) break;
2307 len -= len / 2;
2308 }
2309 }
2310
2311 // Last index is a sentinel containing the highest address as its functionOffset
2312 if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo;
2313 break :blk &indices[left];
2314 };
2315
2316 const common_encodings = std.mem.bytesAsSlice(
2317 macho.compact_unwind_encoding_t,
2318 unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)],
2319 );
2320
2321 const start_offset = second_level_index.secondLevelPagesSectionOffset;
2322 const kind = std.mem.bytesAsValue(
2323 macho.UNWIND_SECOND_LEVEL,
2324 unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)],
2325 );
2326
2327 const entry: struct {
2328 function_offset: usize,
2329 raw_encoding: u32,
2330 } = switch (kind.*) {
2331 .REGULAR => blk: {
2332 const page_header = std.mem.bytesAsValue(
2333 macho.unwind_info_regular_second_level_page_header,
2334 unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)],
2335 );
2336
2337 const entries = std.mem.bytesAsSlice(
2338 macho.unwind_info_regular_second_level_entry,
2339 unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)],
2340 );
2341 if (entries.len == 0) return error.InvalidUnwindInfo;
2342
2343 var left: usize = 0;
2344 var len: usize = entries.len;
2345 while (len > 1) {
2346 const mid = left + len / 2;
2347 const offset = entries[mid].functionOffset;
2348 if (mapped_pc < offset) {
2349 len /= 2;
2350 } else {
2351 left = mid;
2352 if (mapped_pc == offset) break;
2353 len -= len / 2;
2354 }
2355 }
2356
2357 break :blk .{
2358 .function_offset = entries[left].functionOffset,
2359 .raw_encoding = entries[left].encoding,
2360 };
2361 },
2362 .COMPRESSED => blk: {
2363 const page_header = std.mem.bytesAsValue(
2364 macho.unwind_info_compressed_second_level_page_header,
2365 unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)],
2366 );
2367
2368 const entries = std.mem.bytesAsSlice(
2369 macho.UnwindInfoCompressedEntry,
2370 unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)],
2371 );
2372 if (entries.len == 0) return error.InvalidUnwindInfo;
2373
2374 var left: usize = 0;
2375 var len: usize = entries.len;
2376 while (len > 1) {
2377 const mid = left + len / 2;
2378 const offset = second_level_index.functionOffset + entries[mid].funcOffset;
2379 if (mapped_pc < offset) {
2380 len /= 2;
2381 } else {
2382 left = mid;
2383 if (mapped_pc == offset) break;
2384 len -= len / 2;
2385 }
2386 }
2387
2388 const entry = entries[left];
2389 const function_offset = second_level_index.functionOffset + entry.funcOffset;
2390 if (entry.encodingIndex < header.commonEncodingsArrayCount) {
2391 if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo;
2392 break :blk .{
2393 .function_offset = function_offset,
2394 .raw_encoding = common_encodings[entry.encodingIndex],
2395 };
2396 } else {
2397 const local_index = try std.math.sub(
2398 u8,
2399 entry.encodingIndex,
2400 cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo,
2401 );
2402 const local_encodings = std.mem.bytesAsSlice(
2403 macho.compact_unwind_encoding_t,
2404 unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)],
2405 );
2406 if (local_index >= local_encodings.len) return error.InvalidUnwindInfo;
2407 break :blk .{
2408 .function_offset = function_offset,
2409 .raw_encoding = local_encodings[local_index],
2410 };
2411 }
2412 },
2413 else => return error.InvalidUnwindInfo,
2414 };
2415
2416 if (entry.raw_encoding == 0) return error.NoUnwindInfo;
2417 const reg_context = abi.RegisterContext{
2418 .eh_frame = false,
2419 .is_macho = true,
2420 };
2421
2422 const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
2423 const new_ip = switch (builtin.cpu.arch) {
2424 .x86_64 => switch (encoding.mode.x86_64) {
2425 .OLD => return error.UnimplementedUnwindEncoding,
2426 .RBP_FRAME => blk: {
2427 const regs: [5]u3 = .{
2428 encoding.value.x86_64.frame.reg0,
2429 encoding.value.x86_64.frame.reg1,
2430 encoding.value.x86_64.frame.reg2,
2431 encoding.value.x86_64.frame.reg3,
2432 encoding.value.x86_64.frame.reg4,
2433 };
2434
2435 const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize);
2436 var max_reg: usize = 0;
2437 inline for (regs, 0..) |reg, i| {
2438 if (reg > 0) max_reg = i;
2439 }
2440
2441 const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*;
2442 const new_sp = fp + 2 * @sizeOf(usize);
2443
2444 // Verify the stack range we're about to read register values from
2445 if (ma.load(usize, new_sp) == null or ma.load(usize, fp - frame_offset + max_reg * @sizeOf(usize)) == null) return error.InvalidUnwindInfo;
2446
2447 const ip_ptr = fp + @sizeOf(usize);
2448 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
2449 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
2450
2451 (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp;
2452 (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
2453 (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
2454
2455 for (regs, 0..) |reg, i| {
2456 if (reg == 0) continue;
2457 const addr = fp - frame_offset + i * @sizeOf(usize);
2458 const reg_number = try compactUnwindToDwarfRegNumber(reg);
2459 (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
2460 }
2461
2462 break :blk new_ip;
2463 },
2464 .STACK_IMMD,
2465 .STACK_IND,
2466 => blk: {
2467 const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*;
2468 const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD)
2469 @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize)
2470 else stack_size: {
2471 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
2472 const sub_offset_addr =
2473 module_base_address +
2474 entry.function_offset +
2475 encoding.value.x86_64.frameless.stack.indirect.sub_offset;
2476 if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo;
2477
2478 // `sub_offset_addr` points to the offset of the literal within the instruction
2479 const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
2480 break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust);
2481 };
2482
2483 // Decode the Lehmer-coded sequence of registers.
2484 // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
2485
2486 // Decode the variable-based permutation number into its digits. Each digit represents
2487 // an index into the list of register numbers that weren't yet used in the sequence at
2488 // the time the digit was added.
2489 const reg_count = encoding.value.x86_64.frameless.stack_reg_count;
2490 const ip_ptr = if (reg_count > 0) reg_blk: {
2491 var digits: [6]u3 = undefined;
2492 var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation;
2493 var base: usize = 2;
2494 for (0..reg_count) |i| {
2495 const div = accumulator / base;
2496 digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
2497 accumulator = div;
2498 base += 1;
2499 }
2500
2501 const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 };
2502 var registers: [reg_numbers.len]u3 = undefined;
2503 var used_indices = [_]bool{false} ** reg_numbers.len;
2504 for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
2505 var unused_count: u8 = 0;
2506 const unused_index = for (used_indices, 0..) |used, index| {
2507 if (!used) {
2508 if (target_unused_index == unused_count) break index;
2509 unused_count += 1;
2510 }
2511 } else unreachable;
2512
2513 registers[i] = reg_numbers[unused_index];
2514 used_indices[unused_index] = true;
2515 }
2516
2517 var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
2518 if (ma.load(usize, reg_addr) == null) return error.InvalidUnwindInfo;
2519 for (0..reg_count) |i| {
2520 const reg_number = try compactUnwindToDwarfRegNumber(registers[i]);
2521 (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
2522 reg_addr += @sizeOf(usize);
2523 }
2524
2525 break :reg_blk reg_addr;
2526 } else sp + stack_size - @sizeOf(usize);
2527
2528 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
2529 const new_sp = ip_ptr + @sizeOf(usize);
2530 if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo;
2531
2532 (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
2533 (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
2534
2535 break :blk new_ip;
2536 },
2537 .DWARF => {
2538 return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));
2539 },
2540 },
2541 .aarch64 => switch (encoding.mode.arm64) {
2542 .OLD => return error.UnimplementedUnwindEncoding,
2543 .FRAMELESS => blk: {
2544 const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*;
2545 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
2546 const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*;
2547 if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo;
2548 (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
2549 break :blk new_ip;
2550 },
2551 .DWARF => {
2552 return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));
2553 },
2554 .FRAME => blk: {
2555 const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*;
2556 const new_sp = fp + 16;
2557 const ip_ptr = fp + @sizeOf(usize);
2558
2559 const num_restored_pairs: usize =
2560 @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) +
2561 @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs)));
2562 const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize);
2563
2564 if (ma.load(usize, new_sp) == null or ma.load(usize, min_reg_addr) == null) return error.InvalidUnwindInfo;
2565
2566 var reg_addr = fp - @sizeOf(usize);
2567 inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| {
2568 if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) {
2569 (try abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
2570 reg_addr += @sizeOf(usize);
2571 (try abi.regValueNative(usize, context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
2572 reg_addr += @sizeOf(usize);
2573 }
2574 }
2575
2576 inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| {
2577 if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) {
2578 // Only the lower half of the 128-bit V registers are restored during unwinding
2579 @memcpy(
2580 try abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context),
2581 std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
2582 );
2583 reg_addr += @sizeOf(usize);
2584 @memcpy(
2585 try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context),
2586 std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
2587 );
2588 reg_addr += @sizeOf(usize);
2589 }
2590 }
2591
2592 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
2593 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
2594
2595 (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp;
2596 (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
2597
2598 break :blk new_ip;
2599 },
2600 },
2601 else => return error.UnimplementedArch,
2602 };
2603
2604 context.pc = abi.stripInstructionPtrAuthCode(new_ip);
2605 if (context.pc > 0) context.pc -= 1;
2606 return new_ip;
2607}
2608
2609fn unwindFrameMachODwarf(
2610 context: *UnwindContext,
2611 ma: *std.debug.StackIterator.MemoryAccessor,
2612 eh_frame: []const u8,
2613 fde_offset: usize,
2614) !usize {
2615 var di = Dwarf{
2616 .endian = native_endian,
2617 .is_macho = true,
2618 };
2619 defer di.deinit(context.allocator);
2620
2621 di.sections[@intFromEnum(Section.Id.eh_frame)] = .{
2622 .data = eh_frame,
2623 .owned = false,
2624 };
2625
2626 return di.unwindFrame(context, ma, fde_offset);
2627}
2628
26291945const EhPointerContext = struct {
26301946 // The address of the pointer field itself
26311947 pc_rel_base: u64,
......@@ -2641,7 +1957,7 @@ const EhPointerContext = struct {
26411957 text_rel_base: ?u64 = null,
26421958 function_rel_base: ?u64 = null,
26431959};
2644fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 {
1960fn readEhPointer(fbr: *DeprecatedFixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 {
26451961 if (enc == EH.PE.omit) return null;
26461962
26471963 const value: union(enum) {
......@@ -2664,7 +1980,7 @@ fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhP
26641980 EH.PE.sdata2 => .{ .signed = try fbr.readInt(i16) },
26651981 EH.PE.sdata4 => .{ .signed = try fbr.readInt(i32) },
26661982 EH.PE.sdata8 => .{ .signed = try fbr.readInt(i64) },
2667 else => return badDwarf(),
1983 else => return bad(),
26681984 };
26691985
26701986 const base = switch (enc & EH.PE.rel_mask) {
lib/std/debug/Dwarf/abi.zig+55-112
......@@ -1,44 +1,50 @@
11const builtin = @import("builtin");
2
23const std = @import("../../std.zig");
34const mem = std.mem;
4const native_os = builtin.os.tag;
55const posix = std.posix;
6const Arch = std.Target.Cpu.Arch;
67
8/// Tells whether unwinding for this target is supported by the Dwarf standard.
9///
10/// See also `std.debug.SelfInfo.supportsUnwinding` which tells whether the Zig
11/// standard library has a working implementation of unwinding for this target.
712pub fn supportsUnwinding(target: std.Target) bool {
813 return switch (target.cpu.arch) {
9 .x86 => switch (target.os.tag) {
10 .linux, .netbsd, .solaris, .illumos => true,
11 else => false,
12 },
13 .x86_64 => switch (target.os.tag) {
14 .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true,
15 else => false,
16 },
17 .arm => switch (target.os.tag) {
18 .linux => true,
19 else => false,
20 },
21 .aarch64 => switch (target.os.tag) {
22 .linux, .netbsd, .freebsd, .macos, .ios => true,
23 else => false,
24 },
25 else => false,
14 .amdgcn,
15 .nvptx,
16 .nvptx64,
17 .spirv,
18 .spirv32,
19 .spirv64,
20 .spu_2,
21 => false,
22
23 // Enabling this causes relocation errors such as:
24 // error: invalid relocation type R_RISCV_SUB32 at offset 0x20
25 .riscv64, .riscv32 => false,
26
27 // Conservative guess. Feel free to update this logic with any targets
28 // that are known to not support Dwarf unwinding.
29 else => true,
2630 };
2731}
2832
29pub fn ipRegNum() u8 {
30 return switch (builtin.cpu.arch) {
33/// Returns `null` for CPU architectures without an instruction pointer register.
34pub fn ipRegNum(arch: Arch) ?u8 {
35 return switch (arch) {
3136 .x86 => 8,
3237 .x86_64 => 16,
3338 .arm => 15,
3439 .aarch64 => 32,
35 else => unreachable,
40 else => null,
3641 };
3742}
3843
39pub fn fpRegNum(reg_context: RegisterContext) u8 {
40 return switch (builtin.cpu.arch) {
41 // GCC on OS X historically did the opposite of ELF for these registers (only in .eh_frame), and that is now the convention for MachO
44pub fn fpRegNum(arch: Arch, reg_context: RegisterContext) u8 {
45 return switch (arch) {
46 // GCC on OS X historically did the opposite of ELF for these registers
47 // (only in .eh_frame), and that is now the convention for MachO
4248 .x86 => if (reg_context.eh_frame and reg_context.is_macho) 4 else 5,
4349 .x86_64 => 6,
4450 .arm => 11,
......@@ -47,8 +53,8 @@ pub fn fpRegNum(reg_context: RegisterContext) u8 {
4753 };
4854}
4955
50pub fn spRegNum(reg_context: RegisterContext) u8 {
51 return switch (builtin.cpu.arch) {
56pub fn spRegNum(arch: Arch, reg_context: RegisterContext) u8 {
57 return switch (arch) {
5258 .x86 => if (reg_context.eh_frame and reg_context.is_macho) 5 else 4,
5359 .x86_64 => 7,
5460 .arm => 13,
......@@ -57,33 +63,12 @@ pub fn spRegNum(reg_context: RegisterContext) u8 {
5763 };
5864}
5965
60/// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature.
61/// This function clears these signature bits to make the pointer usable.
62pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
63 if (builtin.cpu.arch == .aarch64) {
64 // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
65 // The save / restore is because `xpaclri` operates on x30 (LR)
66 return asm (
67 \\mov x16, x30
68 \\mov x30, x15
69 \\hint 0x07
70 \\mov x15, x30
71 \\mov x30, x16
72 : [ret] "={x15}" (-> usize),
73 : [ptr] "{x15}" (ptr),
74 : "x16"
75 );
76 }
77
78 return ptr;
79}
80
8166pub const RegisterContext = struct {
8267 eh_frame: bool,
8368 is_macho: bool,
8469};
8570
86pub const AbiError = error{
71pub const RegBytesError = error{
8772 InvalidRegister,
8873 UnimplementedArch,
8974 UnimplementedOs,
......@@ -91,55 +76,21 @@ pub const AbiError = error{
9176 ThreadContextNotSupported,
9277};
9378
94fn RegValueReturnType(comptime ContextPtrType: type, comptime T: type) type {
95 const reg_bytes_type = comptime RegBytesReturnType(ContextPtrType);
96 const info = @typeInfo(reg_bytes_type).Pointer;
97 return @Type(.{
98 .Pointer = .{
99 .size = .One,
100 .is_const = info.is_const,
101 .is_volatile = info.is_volatile,
102 .is_allowzero = info.is_allowzero,
103 .alignment = info.alignment,
104 .address_space = info.address_space,
105 .child = T,
106 .sentinel = null,
107 },
108 });
109}
110
111/// Returns a pointer to a register stored in a ThreadContext, preserving the pointer attributes of the context.
112pub fn regValueNative(
113 comptime T: type,
114 thread_context_ptr: anytype,
115 reg_number: u8,
116 reg_context: ?RegisterContext,
117) !RegValueReturnType(@TypeOf(thread_context_ptr), T) {
118 const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context);
119 if (@sizeOf(T) != reg_bytes.len) return error.IncompatibleRegisterSize;
120 return mem.bytesAsValue(T, reg_bytes[0..@sizeOf(T)]);
121}
122
123fn RegBytesReturnType(comptime ContextPtrType: type) type {
124 const info = @typeInfo(ContextPtrType);
125 if (info != .Pointer or info.Pointer.child != std.debug.ThreadContext) {
126 @compileError("Expected a pointer to std.debug.ThreadContext, got " ++ @typeName(@TypeOf(ContextPtrType)));
127 }
128
129 return if (info.Pointer.is_const) return []const u8 else []u8;
130}
131
13279/// Returns a slice containing the backing storage for `reg_number`.
13380///
81/// This function assumes the Dwarf information corresponds not necessarily to
82/// the current executable, but at least with a matching CPU architecture and
83/// OS. It is planned to lift this limitation with a future enhancement.
84///
13485/// `reg_context` describes in what context the register number is used, as it can have different
13586/// meanings depending on the DWARF container. It is only required when getting the stack or
13687/// frame pointer register on some architectures.
13788pub fn regBytes(
138 thread_context_ptr: anytype,
89 thread_context_ptr: *std.debug.ThreadContext,
13990 reg_number: u8,
14091 reg_context: ?RegisterContext,
141) AbiError!RegBytesReturnType(@TypeOf(thread_context_ptr)) {
142 if (native_os == .windows) {
92) RegBytesError![]u8 {
93 if (builtin.os.tag == .windows) {
14394 return switch (builtin.cpu.arch) {
14495 .x86 => switch (reg_number) {
14596 0 => mem.asBytes(&thread_context_ptr.Eax),
......@@ -194,7 +145,7 @@ pub fn regBytes(
194145
195146 const ucontext_ptr = thread_context_ptr;
196147 return switch (builtin.cpu.arch) {
197 .x86 => switch (native_os) {
148 .x86 => switch (builtin.os.tag) {
198149 .linux, .netbsd, .solaris, .illumos => switch (reg_number) {
199150 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EAX]),
200151 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ECX]),
......@@ -229,7 +180,7 @@ pub fn regBytes(
229180 },
230181 else => error.UnimplementedOs,
231182 },
232 .x86_64 => switch (native_os) {
183 .x86_64 => switch (builtin.os.tag) {
233184 .linux, .solaris, .illumos => switch (reg_number) {
234185 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RAX]),
235186 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDX]),
......@@ -248,7 +199,7 @@ pub fn regBytes(
248199 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R14]),
249200 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R15]),
250201 16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RIP]),
251 17...32 => |i| if (native_os.isSolarish())
202 17...32 => |i| if (builtin.os.tag.isSolarish())
252203 mem.asBytes(&ucontext_ptr.mcontext.fpregs.chip_state.xmm[i - 17])
253204 else
254205 mem.asBytes(&ucontext_ptr.mcontext.fpregs.xmm[i - 17]),
......@@ -318,7 +269,7 @@ pub fn regBytes(
318269 },
319270 else => error.UnimplementedOs,
320271 },
321 .arm => switch (native_os) {
272 .arm => switch (builtin.os.tag) {
322273 .linux => switch (reg_number) {
323274 0 => mem.asBytes(&ucontext_ptr.mcontext.arm_r0),
324275 1 => mem.asBytes(&ucontext_ptr.mcontext.arm_r1),
......@@ -341,7 +292,7 @@ pub fn regBytes(
341292 },
342293 else => error.UnimplementedOs,
343294 },
344 .aarch64 => switch (native_os) {
295 .aarch64 => switch (builtin.os.tag) {
345296 .macos, .ios => switch (reg_number) {
346297 0...28 => mem.asBytes(&ucontext_ptr.mcontext.ss.regs[reg_number]),
347298 29 => mem.asBytes(&ucontext_ptr.mcontext.ss.fp),
......@@ -389,22 +340,14 @@ pub fn regBytes(
389340 };
390341}
391342
392/// Returns the ABI-defined default value this register has in the unwinding table
393/// before running any of the CIE instructions. The DWARF spec defines these as having
394/// the .undefined rule by default, but allows ABI authors to override that.
395pub fn getRegDefaultValue(reg_number: u8, context: *std.debug.Dwarf.UnwindContext, out: []u8) !void {
396 switch (builtin.cpu.arch) {
397 .aarch64 => {
398 // Callee-saved registers are initialized as if they had the .same_value rule
399 if (reg_number >= 19 and reg_number <= 28) {
400 const src = try regBytes(context.thread_context, reg_number, context.reg_context);
401 if (src.len != out.len) return error.RegisterSizeMismatch;
402 @memcpy(out, src);
403 return;
404 }
405 },
406 else => {},
407 }
408
409 @memset(out, undefined);
343/// Returns a pointer to a register stored in a ThreadContext, preserving the
344/// pointer attributes of the context.
345pub fn regValueNative(
346 thread_context_ptr: *std.debug.ThreadContext,
347 reg_number: u8,
348 reg_context: ?RegisterContext,
349) !*align(1) usize {
350 const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context);
351 if (@sizeOf(usize) != reg_bytes.len) return error.IncompatibleRegisterSize;
352 return mem.bytesAsValue(usize, reg_bytes[0..@sizeOf(usize)]);
410353}
lib/std/debug/Dwarf/call_frame.zig-388
......@@ -297,391 +297,3 @@ pub const Instruction = union(Opcode) {
297297 }
298298 }
299299};
300
301/// Since register rules are applied (usually) during a panic,
302/// checked addition / subtraction is used so that we can return
303/// an error and fall back to FP-based unwinding.
304pub fn applyOffset(base: usize, offset: i64) !usize {
305 return if (offset >= 0)
306 try std.math.add(usize, base, @as(usize, @intCast(offset)))
307 else
308 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
309}
310
311/// This is a virtual machine that runs DWARF call frame instructions.
312pub const VirtualMachine = struct {
313 /// See section 6.4.1 of the DWARF5 specification for details on each
314 const RegisterRule = union(enum) {
315 // The spec says that the default rule for each column is the undefined rule.
316 // However, it also allows ABI / compiler authors to specify alternate defaults, so
317 // there is a distinction made here.
318 default: void,
319
320 undefined: void,
321 same_value: void,
322
323 // offset(N)
324 offset: i64,
325
326 // val_offset(N)
327 val_offset: i64,
328
329 // register(R)
330 register: u8,
331
332 // expression(E)
333 expression: []const u8,
334
335 // val_expression(E)
336 val_expression: []const u8,
337
338 // Augmenter-defined rule
339 architectural: void,
340 };
341
342 /// Each row contains unwinding rules for a set of registers.
343 pub const Row = struct {
344 /// Offset from `FrameDescriptionEntry.pc_begin`
345 offset: u64 = 0,
346
347 /// Special-case column that defines the CFA (Canonical Frame Address) rule.
348 /// The register field of this column defines the register that CFA is derived from.
349 cfa: Column = .{},
350
351 /// The register fields in these columns define the register the rule applies to.
352 columns: ColumnRange = .{},
353
354 /// Indicates that the next write to any column in this row needs to copy
355 /// the backing column storage first, as it may be referenced by previous rows.
356 copy_on_write: bool = false,
357 };
358
359 pub const Column = struct {
360 register: ?u8 = null,
361 rule: RegisterRule = .{ .default = {} },
362
363 /// Resolves the register rule and places the result into `out` (see dwarf.abi.regBytes)
364 pub fn resolveValue(
365 self: Column,
366 context: *std.debug.Dwarf.UnwindContext,
367 expression_context: std.debug.Dwarf.expression.Context,
368 ma: *debug.StackIterator.MemoryAccessor,
369 out: []u8,
370 ) !void {
371 switch (self.rule) {
372 .default => {
373 const register = self.register orelse return error.InvalidRegister;
374 try abi.getRegDefaultValue(register, context, out);
375 },
376 .undefined => {
377 @memset(out, undefined);
378 },
379 .same_value => {
380 // TODO: This copy could be eliminated if callers always copy the state then call this function to update it
381 const register = self.register orelse return error.InvalidRegister;
382 const src = try abi.regBytes(context.thread_context, register, context.reg_context);
383 if (src.len != out.len) return error.RegisterSizeMismatch;
384 @memcpy(out, src);
385 },
386 .offset => |offset| {
387 if (context.cfa) |cfa| {
388 const addr = try applyOffset(cfa, offset);
389 if (ma.load(usize, addr) == null) return error.InvalidAddress;
390 const ptr: *const usize = @ptrFromInt(addr);
391 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
392 } else return error.InvalidCFA;
393 },
394 .val_offset => |offset| {
395 if (context.cfa) |cfa| {
396 mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian);
397 } else return error.InvalidCFA;
398 },
399 .register => |register| {
400 const src = try abi.regBytes(context.thread_context, register, context.reg_context);
401 if (src.len != out.len) return error.RegisterSizeMismatch;
402 @memcpy(out, try abi.regBytes(context.thread_context, register, context.reg_context));
403 },
404 .expression => |expression| {
405 context.stack_machine.reset();
406 const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?);
407 const addr = if (value) |v| blk: {
408 if (v != .generic) return error.InvalidExpressionValue;
409 break :blk v.generic;
410 } else return error.NoExpressionValue;
411
412 if (ma.load(usize, addr) == null) return error.InvalidExpressionAddress;
413 const ptr: *usize = @ptrFromInt(addr);
414 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
415 },
416 .val_expression => |expression| {
417 context.stack_machine.reset();
418 const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?);
419 if (value) |v| {
420 if (v != .generic) return error.InvalidExpressionValue;
421 mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian);
422 } else return error.NoExpressionValue;
423 },
424 .architectural => return error.UnimplementedRegisterRule,
425 }
426 }
427 };
428
429 const ColumnRange = struct {
430 /// Index into `columns` of the first column in this row.
431 start: usize = undefined,
432 len: u8 = 0,
433 };
434
435 columns: std.ArrayListUnmanaged(Column) = .{},
436 stack: std.ArrayListUnmanaged(ColumnRange) = .{},
437 current_row: Row = .{},
438
439 /// The result of executing the CIE's initial_instructions
440 cie_row: ?Row = null,
441
442 pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void {
443 self.stack.deinit(allocator);
444 self.columns.deinit(allocator);
445 self.* = undefined;
446 }
447
448 pub fn reset(self: *VirtualMachine) void {
449 self.stack.clearRetainingCapacity();
450 self.columns.clearRetainingCapacity();
451 self.current_row = .{};
452 self.cie_row = null;
453 }
454
455 /// Return a slice backed by the row's non-CFA columns
456 pub fn rowColumns(self: VirtualMachine, row: Row) []Column {
457 if (row.columns.len == 0) return &.{};
458 return self.columns.items[row.columns.start..][0..row.columns.len];
459 }
460
461 /// Either retrieves or adds a column for `register` (non-CFA) in the current row.
462 fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column {
463 for (self.rowColumns(self.current_row)) |*c| {
464 if (c.register == register) return c;
465 }
466
467 if (self.current_row.columns.len == 0) {
468 self.current_row.columns.start = self.columns.items.len;
469 }
470 self.current_row.columns.len += 1;
471
472 const column = try self.columns.addOne(allocator);
473 column.* = .{
474 .register = register,
475 };
476
477 return column;
478 }
479
480 /// Runs the CIE instructions, then the FDE instructions. Execution halts
481 /// once the row that corresponds to `pc` is known, and the row is returned.
482 pub fn runTo(
483 self: *VirtualMachine,
484 allocator: std.mem.Allocator,
485 pc: u64,
486 cie: std.debug.Dwarf.CommonInformationEntry,
487 fde: std.debug.Dwarf.FrameDescriptionEntry,
488 addr_size_bytes: u8,
489 endian: std.builtin.Endian,
490 ) !Row {
491 assert(self.cie_row == null);
492 if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange;
493
494 var prev_row: Row = self.current_row;
495
496 var cie_stream = std.io.fixedBufferStream(cie.initial_instructions);
497 var fde_stream = std.io.fixedBufferStream(fde.instructions);
498 var streams = [_]*std.io.FixedBufferStream([]const u8){
499 &cie_stream,
500 &fde_stream,
501 };
502
503 for (&streams, 0..) |stream, i| {
504 while (stream.pos < stream.buffer.len) {
505 const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian);
506 prev_row = try self.step(allocator, cie, i == 0, instruction);
507 if (pc < fde.pc_begin + self.current_row.offset) return prev_row;
508 }
509 }
510
511 return self.current_row;
512 }
513
514 pub fn runToNative(
515 self: *VirtualMachine,
516 allocator: std.mem.Allocator,
517 pc: u64,
518 cie: std.debug.Dwarf.CommonInformationEntry,
519 fde: std.debug.Dwarf.FrameDescriptionEntry,
520 ) !Row {
521 return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), builtin.target.cpu.arch.endian());
522 }
523
524 fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void {
525 if (!self.current_row.copy_on_write) return;
526
527 const new_start = self.columns.items.len;
528 if (self.current_row.columns.len > 0) {
529 try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len);
530 self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row));
531 self.current_row.columns.start = new_start;
532 }
533 }
534
535 /// Executes a single instruction.
536 /// If this instruction is from the CIE, `is_initial` should be set.
537 /// Returns the value of `current_row` before executing this instruction.
538 pub fn step(
539 self: *VirtualMachine,
540 allocator: std.mem.Allocator,
541 cie: std.debug.Dwarf.CommonInformationEntry,
542 is_initial: bool,
543 instruction: Instruction,
544 ) !Row {
545 // CIE instructions must be run before FDE instructions
546 assert(!is_initial or self.cie_row == null);
547 if (!is_initial and self.cie_row == null) {
548 self.cie_row = self.current_row;
549 self.current_row.copy_on_write = true;
550 }
551
552 const prev_row = self.current_row;
553 switch (instruction) {
554 .set_loc => |i| {
555 if (i.address <= self.current_row.offset) return error.InvalidOperation;
556 // TODO: Check cie.segment_selector_size != 0 for DWARFV4
557 self.current_row.offset = i.address;
558 },
559 inline .advance_loc,
560 .advance_loc1,
561 .advance_loc2,
562 .advance_loc4,
563 => |i| {
564 self.current_row.offset += i.delta * cie.code_alignment_factor;
565 self.current_row.copy_on_write = true;
566 },
567 inline .offset,
568 .offset_extended,
569 .offset_extended_sf,
570 => |i| {
571 try self.resolveCopyOnWrite(allocator);
572 const column = try self.getOrAddColumn(allocator, i.register);
573 column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor };
574 },
575 inline .restore,
576 .restore_extended,
577 => |i| {
578 try self.resolveCopyOnWrite(allocator);
579 if (self.cie_row) |cie_row| {
580 const column = try self.getOrAddColumn(allocator, i.register);
581 column.rule = for (self.rowColumns(cie_row)) |cie_column| {
582 if (cie_column.register == i.register) break cie_column.rule;
583 } else .{ .default = {} };
584 } else return error.InvalidOperation;
585 },
586 .nop => {},
587 .undefined => |i| {
588 try self.resolveCopyOnWrite(allocator);
589 const column = try self.getOrAddColumn(allocator, i.register);
590 column.rule = .{ .undefined = {} };
591 },
592 .same_value => |i| {
593 try self.resolveCopyOnWrite(allocator);
594 const column = try self.getOrAddColumn(allocator, i.register);
595 column.rule = .{ .same_value = {} };
596 },
597 .register => |i| {
598 try self.resolveCopyOnWrite(allocator);
599 const column = try self.getOrAddColumn(allocator, i.register);
600 column.rule = .{ .register = i.target_register };
601 },
602 .remember_state => {
603 try self.stack.append(allocator, self.current_row.columns);
604 self.current_row.copy_on_write = true;
605 },
606 .restore_state => {
607 const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation;
608 self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len);
609 try self.columns.ensureUnusedCapacity(allocator, restored_columns.len);
610
611 self.current_row.columns.start = self.columns.items.len;
612 self.current_row.columns.len = restored_columns.len;
613 self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]);
614 },
615 .def_cfa => |i| {
616 try self.resolveCopyOnWrite(allocator);
617 self.current_row.cfa = .{
618 .register = i.register,
619 .rule = .{ .val_offset = @intCast(i.offset) },
620 };
621 },
622 .def_cfa_sf => |i| {
623 try self.resolveCopyOnWrite(allocator);
624 self.current_row.cfa = .{
625 .register = i.register,
626 .rule = .{ .val_offset = i.offset * cie.data_alignment_factor },
627 };
628 },
629 .def_cfa_register => |i| {
630 try self.resolveCopyOnWrite(allocator);
631 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
632 self.current_row.cfa.register = i.register;
633 },
634 .def_cfa_offset => |i| {
635 try self.resolveCopyOnWrite(allocator);
636 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
637 self.current_row.cfa.rule = .{
638 .val_offset = @intCast(i.offset),
639 };
640 },
641 .def_cfa_offset_sf => |i| {
642 try self.resolveCopyOnWrite(allocator);
643 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
644 self.current_row.cfa.rule = .{
645 .val_offset = i.offset * cie.data_alignment_factor,
646 };
647 },
648 .def_cfa_expression => |i| {
649 try self.resolveCopyOnWrite(allocator);
650 self.current_row.cfa.register = undefined;
651 self.current_row.cfa.rule = .{
652 .expression = i.block,
653 };
654 },
655 .expression => |i| {
656 try self.resolveCopyOnWrite(allocator);
657 const column = try self.getOrAddColumn(allocator, i.register);
658 column.rule = .{
659 .expression = i.block,
660 };
661 },
662 .val_offset => |i| {
663 try self.resolveCopyOnWrite(allocator);
664 const column = try self.getOrAddColumn(allocator, i.register);
665 column.rule = .{
666 .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor,
667 };
668 },
669 .val_offset_sf => |i| {
670 try self.resolveCopyOnWrite(allocator);
671 const column = try self.getOrAddColumn(allocator, i.register);
672 column.rule = .{
673 .val_offset = i.offset * cie.data_alignment_factor,
674 };
675 },
676 .val_expression => |i| {
677 try self.resolveCopyOnWrite(allocator);
678 const column = try self.getOrAddColumn(allocator, i.register);
679 column.rule = .{
680 .val_expression = i.block,
681 };
682 },
683 }
684
685 return prev_row;
686 }
687};
lib/std/debug/Dwarf/expression.zig+14-12
......@@ -1,11 +1,13 @@
1const std = @import("std");
21const builtin = @import("builtin");
2const native_arch = builtin.cpu.arch;
3const native_endian = native_arch.endian();
4
5const std = @import("std");
36const leb = std.leb;
47const OP = std.dwarf.OP;
58const abi = std.debug.Dwarf.abi;
69const mem = std.mem;
710const assert = std.debug.assert;
8const native_endian = builtin.cpu.arch.endian();
911
1012/// Expressions can be evaluated in different contexts, each requiring its own set of inputs.
1113/// Callers should specify all the fields relevant to their context. If a field is required
......@@ -14,7 +16,7 @@ pub const Context = struct {
1416 /// The dwarf format of the section this expression is in
1517 format: std.dwarf.Format = .@"32",
1618 /// If specified, any addresses will pass through before being accessed
17 memory_accessor: ?*std.debug.StackIterator.MemoryAccessor = null,
19 memory_accessor: ?*std.debug.MemoryAccessor = null,
1820 /// The compilation unit this expression relates to, if any
1921 compile_unit: ?*const std.debug.Dwarf.CompileUnit = null,
2022 /// When evaluating a user-presented expression, this is the address of the object being evaluated
......@@ -34,7 +36,7 @@ pub const Options = struct {
3436 /// The address size of the target architecture
3537 addr_size: u8 = @sizeOf(usize),
3638 /// Endianness of the target architecture
37 endian: std.builtin.Endian = builtin.target.cpu.arch.endian(),
39 endian: std.builtin.Endian = native_endian,
3840 /// Restrict the stack machine to a subset of opcodes used in call frame instructions
3941 call_frame_context: bool = false,
4042};
......@@ -60,7 +62,7 @@ pub const Error = error{
6062 InvalidTypeLength,
6163
6264 TruncatedIntegralType,
63} || abi.AbiError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero };
65} || abi.RegBytesError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero };
6466
6567/// A stack machine that can decode and run DWARF expressions.
6668/// Expressions can be decoded for non-native address size and endianness,
......@@ -304,7 +306,7 @@ pub fn StackMachine(comptime options: Options) type {
304306 allocator: std.mem.Allocator,
305307 context: Context,
306308 ) Error!bool {
307 if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != comptime builtin.target.cpu.arch.endian())
309 if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != native_endian)
308310 @compileError("Execution of non-native address sizes / endianness is not supported");
309311
310312 const opcode = try stream.reader().readByte();
......@@ -1186,13 +1188,13 @@ test "DWARF expressions" {
11861188 // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it
11871189
11881190 mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
1189 (try abi.regValueNative(usize, &thread_context, abi.fpRegNum(reg_context), reg_context)).* = 1;
1190 (try abi.regValueNative(usize, &thread_context, abi.spRegNum(reg_context), reg_context)).* = 2;
1191 (try abi.regValueNative(usize, &thread_context, abi.ipRegNum(), reg_context)).* = 3;
1191 (try abi.regValueNative(&thread_context, abi.fpRegNum(native_arch, reg_context), reg_context)).* = 1;
1192 (try abi.regValueNative(&thread_context, abi.spRegNum(native_arch, reg_context), reg_context)).* = 2;
1193 (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch).?, reg_context)).* = 3;
11921194
1193 try b.writeBreg(writer, abi.fpRegNum(reg_context), @as(usize, 100));
1194 try b.writeBreg(writer, abi.spRegNum(reg_context), @as(usize, 200));
1195 try b.writeBregx(writer, abi.ipRegNum(), @as(usize, 300));
1195 try b.writeBreg(writer, abi.fpRegNum(native_arch, reg_context), @as(usize, 100));
1196 try b.writeBreg(writer, abi.spRegNum(native_arch, reg_context), @as(usize, 200));
1197 try b.writeBregx(writer, abi.ipRegNum(native_arch).?, @as(usize, 300));
11961198 try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400));
11971199
11981200 _ = try stack_machine.run(program.items, allocator, context, 0);
lib/std/debug/MemoryAccessor.zig created+128
......@@ -0,0 +1,128 @@
1//! Reads memory from any address of the current location using OS-specific
2//! syscalls, bypassing memory page protection. Useful for stack unwinding.
3
4const builtin = @import("builtin");
5const native_os = builtin.os.tag;
6
7const std = @import("../std.zig");
8const posix = std.posix;
9const File = std.fs.File;
10const page_size = std.mem.page_size;
11
12const MemoryAccessor = @This();
13
14var cached_pid: posix.pid_t = -1;
15
16mem: switch (native_os) {
17 .linux => File,
18 else => void,
19},
20
21pub const init: MemoryAccessor = .{
22 .mem = switch (native_os) {
23 .linux => .{ .handle = -1 },
24 else => {},
25 },
26};
27
28fn read(ma: *MemoryAccessor, address: usize, buf: []u8) bool {
29 switch (native_os) {
30 .linux => while (true) switch (ma.mem.handle) {
31 -2 => break,
32 -1 => {
33 const linux = std.os.linux;
34 const pid = switch (@atomicLoad(posix.pid_t, &cached_pid, .monotonic)) {
35 -1 => pid: {
36 const pid = linux.getpid();
37 @atomicStore(posix.pid_t, &cached_pid, pid, .monotonic);
38 break :pid pid;
39 },
40 else => |pid| pid,
41 };
42 const bytes_read = linux.process_vm_readv(
43 pid,
44 &.{.{ .base = buf.ptr, .len = buf.len }},
45 &.{.{ .base = @ptrFromInt(address), .len = buf.len }},
46 0,
47 );
48 switch (linux.E.init(bytes_read)) {
49 .SUCCESS => return bytes_read == buf.len,
50 .FAULT => return false,
51 .INVAL, .PERM, .SRCH => unreachable, // own pid is always valid
52 .NOMEM => {},
53 .NOSYS => {}, // QEMU is known not to implement this syscall.
54 else => unreachable, // unexpected
55 }
56 var path_buf: [
57 std.fmt.count("/proc/{d}/mem", .{std.math.minInt(posix.pid_t)})
58 ]u8 = undefined;
59 const path = std.fmt.bufPrint(&path_buf, "/proc/{d}/mem", .{pid}) catch
60 unreachable;
61 ma.mem = std.fs.openFileAbsolute(path, .{}) catch {
62 ma.mem.handle = -2;
63 break;
64 };
65 },
66 else => return (ma.mem.pread(buf, address) catch return false) == buf.len,
67 },
68 else => {},
69 }
70 if (!isValidMemory(address)) return false;
71 @memcpy(buf, @as([*]const u8, @ptrFromInt(address)));
72 return true;
73}
74
75pub fn load(ma: *MemoryAccessor, comptime Type: type, address: usize) ?Type {
76 var result: Type = undefined;
77 return if (ma.read(address, std.mem.asBytes(&result))) result else null;
78}
79
80pub fn isValidMemory(address: usize) bool {
81 // We are unable to determine validity of memory for freestanding targets
82 if (native_os == .freestanding or native_os == .uefi) return true;
83
84 const aligned_address = address & ~@as(usize, @intCast((page_size - 1)));
85 if (aligned_address == 0) return false;
86 const aligned_memory = @as([*]align(page_size) u8, @ptrFromInt(aligned_address))[0..page_size];
87
88 if (native_os == .windows) {
89 const windows = std.os.windows;
90
91 var memory_info: windows.MEMORY_BASIC_INFORMATION = undefined;
92
93 // The only error this function can throw is ERROR_INVALID_PARAMETER.
94 // supply an address that invalid i'll be thrown.
95 const rc = windows.VirtualQuery(aligned_memory, &memory_info, aligned_memory.len) catch {
96 return false;
97 };
98
99 // Result code has to be bigger than zero (number of bytes written)
100 if (rc == 0) {
101 return false;
102 }
103
104 // Free pages cannot be read, they are unmapped
105 if (memory_info.State == windows.MEM_FREE) {
106 return false;
107 }
108
109 return true;
110 } else if (have_msync) {
111 posix.msync(aligned_memory, posix.MSF.ASYNC) catch |err| {
112 switch (err) {
113 error.UnmappedMemory => return false,
114 else => unreachable,
115 }
116 };
117
118 return true;
119 } else {
120 // We are unable to determine validity of memory on this target.
121 return true;
122 }
123}
124
125const have_msync = switch (native_os) {
126 .wasi, .emscripten, .windows => false,
127 else => true,
128};
lib/std/debug/Pdb.zig created+591
......@@ -0,0 +1,591 @@
1const std = @import("../std.zig");
2const File = std.fs.File;
3const Allocator = std.mem.Allocator;
4const pdb = std.pdb;
5
6const Pdb = @This();
7
8in_file: File,
9msf: Msf,
10allocator: Allocator,
11string_table: ?*MsfStream,
12dbi: ?*MsfStream,
13modules: []Module,
14sect_contribs: []pdb.SectionContribEntry,
15guid: [16]u8,
16age: u32,
17
18pub const Module = struct {
19 mod_info: pdb.ModInfo,
20 module_name: []u8,
21 obj_file_name: []u8,
22 // The fields below are filled on demand.
23 populated: bool,
24 symbols: []u8,
25 subsect_info: []u8,
26 checksum_offset: ?usize,
27
28 pub fn deinit(self: *Module, allocator: Allocator) void {
29 allocator.free(self.module_name);
30 allocator.free(self.obj_file_name);
31 if (self.populated) {
32 allocator.free(self.symbols);
33 allocator.free(self.subsect_info);
34 }
35 }
36};
37
38pub fn init(allocator: Allocator, path: []const u8) !Pdb {
39 const file = try std.fs.cwd().openFile(path, .{});
40 errdefer file.close();
41
42 return .{
43 .in_file = file,
44 .allocator = allocator,
45 .string_table = null,
46 .dbi = null,
47 .msf = try Msf.init(allocator, file),
48 .modules = &[_]Module{},
49 .sect_contribs = &[_]pdb.SectionContribEntry{},
50 .guid = undefined,
51 .age = undefined,
52 };
53}
54
55pub fn deinit(self: *Pdb) void {
56 self.in_file.close();
57 self.msf.deinit(self.allocator);
58 for (self.modules) |*module| {
59 module.deinit(self.allocator);
60 }
61 self.allocator.free(self.modules);
62 self.allocator.free(self.sect_contribs);
63}
64
65pub fn parseDbiStream(self: *Pdb) !void {
66 var stream = self.getStream(pdb.StreamType.Dbi) orelse
67 return error.InvalidDebugInfo;
68 const reader = stream.reader();
69
70 const header = try reader.readStruct(std.pdb.DbiStreamHeader);
71 if (header.VersionHeader != 19990903) // V70, only value observed by LLVM team
72 return error.UnknownPDBVersion;
73 // if (header.Age != age)
74 // return error.UnmatchingPDB;
75
76 const mod_info_size = header.ModInfoSize;
77 const section_contrib_size = header.SectionContributionSize;
78
79 var modules = std.ArrayList(Module).init(self.allocator);
80 errdefer modules.deinit();
81
82 // Module Info Substream
83 var mod_info_offset: usize = 0;
84 while (mod_info_offset != mod_info_size) {
85 const mod_info = try reader.readStruct(pdb.ModInfo);
86 var this_record_len: usize = @sizeOf(pdb.ModInfo);
87
88 const module_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024);
89 errdefer self.allocator.free(module_name);
90 this_record_len += module_name.len + 1;
91
92 const obj_file_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024);
93 errdefer self.allocator.free(obj_file_name);
94 this_record_len += obj_file_name.len + 1;
95
96 if (this_record_len % 4 != 0) {
97 const round_to_next_4 = (this_record_len | 0x3) + 1;
98 const march_forward_bytes = round_to_next_4 - this_record_len;
99 try stream.seekBy(@as(isize, @intCast(march_forward_bytes)));
100 this_record_len += march_forward_bytes;
101 }
102
103 try modules.append(Module{
104 .mod_info = mod_info,
105 .module_name = module_name,
106 .obj_file_name = obj_file_name,
107
108 .populated = false,
109 .symbols = undefined,
110 .subsect_info = undefined,
111 .checksum_offset = null,
112 });
113
114 mod_info_offset += this_record_len;
115 if (mod_info_offset > mod_info_size)
116 return error.InvalidDebugInfo;
117 }
118
119 // Section Contribution Substream
120 var sect_contribs = std.ArrayList(pdb.SectionContribEntry).init(self.allocator);
121 errdefer sect_contribs.deinit();
122
123 var sect_cont_offset: usize = 0;
124 if (section_contrib_size != 0) {
125 const version = reader.readEnum(std.pdb.SectionContrSubstreamVersion, .little) catch |err| switch (err) {
126 error.InvalidValue => return error.InvalidDebugInfo,
127 else => |e| return e,
128 };
129 _ = version;
130 sect_cont_offset += @sizeOf(u32);
131 }
132 while (sect_cont_offset != section_contrib_size) {
133 const entry = try sect_contribs.addOne();
134 entry.* = try reader.readStruct(pdb.SectionContribEntry);
135 sect_cont_offset += @sizeOf(pdb.SectionContribEntry);
136
137 if (sect_cont_offset > section_contrib_size)
138 return error.InvalidDebugInfo;
139 }
140
141 self.modules = try modules.toOwnedSlice();
142 self.sect_contribs = try sect_contribs.toOwnedSlice();
143}
144
145pub fn parseInfoStream(self: *Pdb) !void {
146 var stream = self.getStream(pdb.StreamType.Pdb) orelse
147 return error.InvalidDebugInfo;
148 const reader = stream.reader();
149
150 // Parse the InfoStreamHeader.
151 const version = try reader.readInt(u32, .little);
152 const signature = try reader.readInt(u32, .little);
153 _ = signature;
154 const age = try reader.readInt(u32, .little);
155 const guid = try reader.readBytesNoEof(16);
156
157 if (version != 20000404) // VC70, only value observed by LLVM team
158 return error.UnknownPDBVersion;
159
160 self.guid = guid;
161 self.age = age;
162
163 // Find the string table.
164 const string_table_index = str_tab_index: {
165 const name_bytes_len = try reader.readInt(u32, .little);
166 const name_bytes = try self.allocator.alloc(u8, name_bytes_len);
167 defer self.allocator.free(name_bytes);
168 try reader.readNoEof(name_bytes);
169
170 const HashTableHeader = extern struct {
171 Size: u32,
172 Capacity: u32,
173
174 fn maxLoad(cap: u32) u32 {
175 return cap * 2 / 3 + 1;
176 }
177 };
178 const hash_tbl_hdr = try reader.readStruct(HashTableHeader);
179 if (hash_tbl_hdr.Capacity == 0)
180 return error.InvalidDebugInfo;
181
182 if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity))
183 return error.InvalidDebugInfo;
184
185 const present = try readSparseBitVector(&reader, self.allocator);
186 defer self.allocator.free(present);
187 if (present.len != hash_tbl_hdr.Size)
188 return error.InvalidDebugInfo;
189 const deleted = try readSparseBitVector(&reader, self.allocator);
190 defer self.allocator.free(deleted);
191
192 for (present) |_| {
193 const name_offset = try reader.readInt(u32, .little);
194 const name_index = try reader.readInt(u32, .little);
195 if (name_offset > name_bytes.len)
196 return error.InvalidDebugInfo;
197 const name = std.mem.sliceTo(name_bytes[name_offset..], 0);
198 if (std.mem.eql(u8, name, "/names")) {
199 break :str_tab_index name_index;
200 }
201 }
202 return error.MissingDebugInfo;
203 };
204
205 self.string_table = self.getStreamById(string_table_index) orelse
206 return error.MissingDebugInfo;
207}
208
209pub fn getSymbolName(self: *Pdb, module: *Module, address: u64) ?[]const u8 {
210 _ = self;
211 std.debug.assert(module.populated);
212
213 var symbol_i: usize = 0;
214 while (symbol_i != module.symbols.len) {
215 const prefix = @as(*align(1) pdb.RecordPrefix, @ptrCast(&module.symbols[symbol_i]));
216 if (prefix.RecordLen < 2)
217 return null;
218 switch (prefix.RecordKind) {
219 .S_LPROC32, .S_GPROC32 => {
220 const proc_sym = @as(*align(1) pdb.ProcSym, @ptrCast(&module.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)]));
221 if (address >= proc_sym.CodeOffset and address < proc_sym.CodeOffset + proc_sym.CodeSize) {
222 return std.mem.sliceTo(@as([*:0]u8, @ptrCast(&proc_sym.Name[0])), 0);
223 }
224 },
225 else => {},
226 }
227 symbol_i += prefix.RecordLen + @sizeOf(u16);
228 }
229
230 return null;
231}
232
233pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.SourceLocation {
234 std.debug.assert(module.populated);
235 const subsect_info = module.subsect_info;
236
237 var sect_offset: usize = 0;
238 var skip_len: usize = undefined;
239 const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo;
240 while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {
241 const subsect_hdr = @as(*align(1) pdb.DebugSubsectionHeader, @ptrCast(&subsect_info[sect_offset]));
242 skip_len = subsect_hdr.Length;
243 sect_offset += @sizeOf(pdb.DebugSubsectionHeader);
244
245 switch (subsect_hdr.Kind) {
246 .Lines => {
247 var line_index = sect_offset;
248
249 const line_hdr = @as(*align(1) pdb.LineFragmentHeader, @ptrCast(&subsect_info[line_index]));
250 if (line_hdr.RelocSegment == 0)
251 return error.MissingDebugInfo;
252 line_index += @sizeOf(pdb.LineFragmentHeader);
253 const frag_vaddr_start = line_hdr.RelocOffset;
254 const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize;
255
256 if (address >= frag_vaddr_start and address < frag_vaddr_end) {
257 // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records)
258 // from now on. We will iterate through them, and eventually find a SourceLocation that we're interested in,
259 // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection.
260 const subsection_end_index = sect_offset + subsect_hdr.Length;
261
262 while (line_index < subsection_end_index) {
263 const block_hdr = @as(*align(1) pdb.LineBlockFragmentHeader, @ptrCast(&subsect_info[line_index]));
264 line_index += @sizeOf(pdb.LineBlockFragmentHeader);
265 const start_line_index = line_index;
266
267 const has_column = line_hdr.Flags.LF_HaveColumns;
268
269 // All line entries are stored inside their line block by ascending start address.
270 // Heuristic: we want to find the last line entry
271 // that has a vaddr_start <= address.
272 // This is done with a simple linear search.
273 var line_i: u32 = 0;
274 while (line_i < block_hdr.NumLines) : (line_i += 1) {
275 const line_num_entry = @as(*align(1) pdb.LineNumberEntry, @ptrCast(&subsect_info[line_index]));
276 line_index += @sizeOf(pdb.LineNumberEntry);
277
278 const vaddr_start = frag_vaddr_start + line_num_entry.Offset;
279 if (address < vaddr_start) {
280 break;
281 }
282 }
283
284 // line_i == 0 would mean that no matching pdb.LineNumberEntry was found.
285 if (line_i > 0) {
286 const subsect_index = checksum_offset + block_hdr.NameIndex;
287 const chksum_hdr = @as(*align(1) pdb.FileChecksumEntryHeader, @ptrCast(&module.subsect_info[subsect_index]));
288 const strtab_offset = @sizeOf(pdb.StringTableHeader) + chksum_hdr.FileNameOffset;
289 try self.string_table.?.seekTo(strtab_offset);
290 const source_file_name = try self.string_table.?.reader().readUntilDelimiterAlloc(self.allocator, 0, 1024);
291
292 const line_entry_idx = line_i - 1;
293
294 const column = if (has_column) blk: {
295 const start_col_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.NumLines;
296 const col_index = start_col_index + @sizeOf(pdb.ColumnNumberEntry) * line_entry_idx;
297 const col_num_entry = @as(*align(1) pdb.ColumnNumberEntry, @ptrCast(&subsect_info[col_index]));
298 break :blk col_num_entry.StartColumn;
299 } else 0;
300
301 const found_line_index = start_line_index + line_entry_idx * @sizeOf(pdb.LineNumberEntry);
302 const line_num_entry: *align(1) pdb.LineNumberEntry = @ptrCast(&subsect_info[found_line_index]);
303 const flags: *align(1) pdb.LineNumberEntry.Flags = @ptrCast(&line_num_entry.Flags);
304
305 return .{
306 .file_name = source_file_name,
307 .line = flags.Start,
308 .column = column,
309 };
310 }
311 }
312
313 // Checking that we are not reading garbage after the (possibly) multiple block fragments.
314 if (line_index != subsection_end_index) {
315 return error.InvalidDebugInfo;
316 }
317 }
318 },
319 else => {},
320 }
321
322 if (sect_offset > subsect_info.len)
323 return error.InvalidDebugInfo;
324 }
325
326 return error.MissingDebugInfo;
327}
328
329pub fn getModule(self: *Pdb, index: usize) !?*Module {
330 if (index >= self.modules.len)
331 return null;
332
333 const mod = &self.modules[index];
334 if (mod.populated)
335 return mod;
336
337 // At most one can be non-zero.
338 if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0)
339 return error.InvalidDebugInfo;
340 if (mod.mod_info.C13ByteSize == 0)
341 return error.InvalidDebugInfo;
342
343 const stream = self.getStreamById(mod.mod_info.ModuleSymStream) orelse
344 return error.MissingDebugInfo;
345 const reader = stream.reader();
346
347 const signature = try reader.readInt(u32, .little);
348 if (signature != 4)
349 return error.InvalidDebugInfo;
350
351 mod.symbols = try self.allocator.alloc(u8, mod.mod_info.SymByteSize - 4);
352 errdefer self.allocator.free(mod.symbols);
353 try reader.readNoEof(mod.symbols);
354
355 mod.subsect_info = try self.allocator.alloc(u8, mod.mod_info.C13ByteSize);
356 errdefer self.allocator.free(mod.subsect_info);
357 try reader.readNoEof(mod.subsect_info);
358
359 var sect_offset: usize = 0;
360 var skip_len: usize = undefined;
361 while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) {
362 const subsect_hdr = @as(*align(1) pdb.DebugSubsectionHeader, @ptrCast(&mod.subsect_info[sect_offset]));
363 skip_len = subsect_hdr.Length;
364 sect_offset += @sizeOf(pdb.DebugSubsectionHeader);
365
366 switch (subsect_hdr.Kind) {
367 .FileChecksums => {
368 mod.checksum_offset = sect_offset;
369 break;
370 },
371 else => {},
372 }
373
374 if (sect_offset > mod.subsect_info.len)
375 return error.InvalidDebugInfo;
376 }
377
378 mod.populated = true;
379 return mod;
380}
381
382pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream {
383 if (id >= self.msf.streams.len)
384 return null;
385 return &self.msf.streams[id];
386}
387
388pub fn getStream(self: *Pdb, stream: pdb.StreamType) ?*MsfStream {
389 const id = @intFromEnum(stream);
390 return self.getStreamById(id);
391}
392
393/// https://llvm.org/docs/PDB/MsfFile.html
394const Msf = struct {
395 directory: MsfStream,
396 streams: []MsfStream,
397
398 fn init(allocator: Allocator, file: File) !Msf {
399 const in = file.reader();
400
401 const superblock = try in.readStruct(pdb.SuperBlock);
402
403 // Sanity checks
404 if (!std.mem.eql(u8, &superblock.FileMagic, pdb.SuperBlock.file_magic))
405 return error.InvalidDebugInfo;
406 if (superblock.FreeBlockMapBlock != 1 and superblock.FreeBlockMapBlock != 2)
407 return error.InvalidDebugInfo;
408 const file_len = try file.getEndPos();
409 if (superblock.NumBlocks * superblock.BlockSize != file_len)
410 return error.InvalidDebugInfo;
411 switch (superblock.BlockSize) {
412 // llvm only supports 4096 but we can handle any of these values
413 512, 1024, 2048, 4096 => {},
414 else => return error.InvalidDebugInfo,
415 }
416
417 const dir_block_count = blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize);
418 if (dir_block_count > superblock.BlockSize / @sizeOf(u32))
419 return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment.
420
421 try file.seekTo(superblock.BlockSize * superblock.BlockMapAddr);
422 const dir_blocks = try allocator.alloc(u32, dir_block_count);
423 for (dir_blocks) |*b| {
424 b.* = try in.readInt(u32, .little);
425 }
426 var directory = MsfStream.init(
427 superblock.BlockSize,
428 file,
429 dir_blocks,
430 );
431
432 const begin = directory.pos;
433 const stream_count = try directory.reader().readInt(u32, .little);
434 const stream_sizes = try allocator.alloc(u32, stream_count);
435 defer allocator.free(stream_sizes);
436
437 // Microsoft's implementation uses @as(u32, -1) for inexistent streams.
438 // These streams are not used, but still participate in the file
439 // and must be taken into account when resolving stream indices.
440 const Nil = 0xFFFFFFFF;
441 for (stream_sizes) |*s| {
442 const size = try directory.reader().readInt(u32, .little);
443 s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.BlockSize);
444 }
445
446 const streams = try allocator.alloc(MsfStream, stream_count);
447 for (streams, 0..) |*stream, i| {
448 const size = stream_sizes[i];
449 if (size == 0) {
450 stream.* = MsfStream{
451 .blocks = &[_]u32{},
452 };
453 } else {
454 var blocks = try allocator.alloc(u32, size);
455 var j: u32 = 0;
456 while (j < size) : (j += 1) {
457 const block_id = try directory.reader().readInt(u32, .little);
458 const n = (block_id % superblock.BlockSize);
459 // 0 is for pdb.SuperBlock, 1 and 2 for FPMs.
460 if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > file_len)
461 return error.InvalidBlockIndex;
462 blocks[j] = block_id;
463 }
464
465 stream.* = MsfStream.init(
466 superblock.BlockSize,
467 file,
468 blocks,
469 );
470 }
471 }
472
473 const end = directory.pos;
474 if (end - begin != superblock.NumDirectoryBytes)
475 return error.InvalidStreamDirectory;
476
477 return Msf{
478 .directory = directory,
479 .streams = streams,
480 };
481 }
482
483 fn deinit(self: *Msf, allocator: Allocator) void {
484 allocator.free(self.directory.blocks);
485 for (self.streams) |*stream| {
486 allocator.free(stream.blocks);
487 }
488 allocator.free(self.streams);
489 }
490};
491
492const MsfStream = struct {
493 in_file: File = undefined,
494 pos: u64 = undefined,
495 blocks: []u32 = undefined,
496 block_size: u32 = undefined,
497
498 pub const Error = @typeInfo(@typeInfo(@TypeOf(read)).Fn.return_type.?).ErrorUnion.error_set;
499
500 fn init(block_size: u32, file: File, blocks: []u32) MsfStream {
501 const stream = MsfStream{
502 .in_file = file,
503 .pos = 0,
504 .blocks = blocks,
505 .block_size = block_size,
506 };
507
508 return stream;
509 }
510
511 fn read(self: *MsfStream, buffer: []u8) !usize {
512 var block_id = @as(usize, @intCast(self.pos / self.block_size));
513 if (block_id >= self.blocks.len) return 0; // End of Stream
514 var block = self.blocks[block_id];
515 var offset = self.pos % self.block_size;
516
517 try self.in_file.seekTo(block * self.block_size + offset);
518 const in = self.in_file.reader();
519
520 var size: usize = 0;
521 var rem_buffer = buffer;
522 while (size < buffer.len) {
523 const size_to_read = @min(self.block_size - offset, rem_buffer.len);
524 size += try in.read(rem_buffer[0..size_to_read]);
525 rem_buffer = buffer[size..];
526 offset += size_to_read;
527
528 // If we're at the end of a block, go to the next one.
529 if (offset == self.block_size) {
530 offset = 0;
531 block_id += 1;
532 if (block_id >= self.blocks.len) break; // End of Stream
533 block = self.blocks[block_id];
534 try self.in_file.seekTo(block * self.block_size);
535 }
536 }
537
538 self.pos += buffer.len;
539 return buffer.len;
540 }
541
542 pub fn seekBy(self: *MsfStream, len: i64) !void {
543 self.pos = @as(u64, @intCast(@as(i64, @intCast(self.pos)) + len));
544 if (self.pos >= self.blocks.len * self.block_size)
545 return error.EOF;
546 }
547
548 pub fn seekTo(self: *MsfStream, len: u64) !void {
549 self.pos = len;
550 if (self.pos >= self.blocks.len * self.block_size)
551 return error.EOF;
552 }
553
554 fn getSize(self: *const MsfStream) u64 {
555 return self.blocks.len * self.block_size;
556 }
557
558 fn getFilePos(self: MsfStream) u64 {
559 const block_id = self.pos / self.block_size;
560 const block = self.blocks[block_id];
561 const offset = self.pos % self.block_size;
562
563 return block * self.block_size + offset;
564 }
565
566 pub fn reader(self: *MsfStream) std.io.Reader(*MsfStream, Error, read) {
567 return .{ .context = self };
568 }
569};
570
571fn readSparseBitVector(stream: anytype, allocator: Allocator) ![]u32 {
572 const num_words = try stream.readInt(u32, .little);
573 var list = std.ArrayList(u32).init(allocator);
574 errdefer list.deinit();
575 var word_i: u32 = 0;
576 while (word_i != num_words) : (word_i += 1) {
577 const word = try stream.readInt(u32, .little);
578 var bit_i: u5 = 0;
579 while (true) : (bit_i += 1) {
580 if (word & (@as(u32, 1) << bit_i) != 0) {
581 try list.append(word_i * 32 + bit_i);
582 }
583 if (bit_i == std.math.maxInt(u5)) break;
584 }
585 }
586 return try list.toOwnedSlice();
587}
588
589fn blockCountFromSize(size: u32, block_size: u32) u32 {
590 return (size + block_size - 1) / block_size;
591}
lib/std/debug/SelfInfo.zig created+2438
......@@ -0,0 +1,2438 @@
1//! Cross-platform abstraction for this binary's own debug information, with a
2//! goal of minimal code bloat and compilation speed penalty.
3
4const builtin = @import("builtin");
5const native_os = builtin.os.tag;
6const native_endian = native_arch.endian();
7const native_arch = builtin.cpu.arch;
8
9const std = @import("../std.zig");
10const mem = std.mem;
11const Allocator = std.mem.Allocator;
12const windows = std.os.windows;
13const macho = std.macho;
14const fs = std.fs;
15const coff = std.coff;
16const pdb = std.pdb;
17const assert = std.debug.assert;
18const posix = std.posix;
19const elf = std.elf;
20const Dwarf = std.debug.Dwarf;
21const Pdb = std.debug.Pdb;
22const File = std.fs.File;
23const math = std.math;
24const testing = std.testing;
25const StackIterator = std.debug.StackIterator;
26const regBytes = Dwarf.abi.regBytes;
27const regValueNative = Dwarf.abi.regValueNative;
28
29const SelfInfo = @This();
30
31const root = @import("root");
32
33allocator: Allocator,
34address_map: std.AutoHashMap(usize, *Module),
35modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void,
36
37pub const OpenError = error{
38 MissingDebugInfo,
39 UnsupportedOperatingSystem,
40} || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).Fn.return_type.?).ErrorUnion.error_set;
41
42pub fn open(allocator: Allocator) OpenError!SelfInfo {
43 nosuspend {
44 if (builtin.strip_debug_info)
45 return error.MissingDebugInfo;
46 switch (native_os) {
47 .linux,
48 .freebsd,
49 .netbsd,
50 .dragonfly,
51 .openbsd,
52 .macos,
53 .solaris,
54 .illumos,
55 .windows,
56 => return try SelfInfo.init(allocator),
57 else => return error.UnsupportedOperatingSystem,
58 }
59 }
60}
61
62pub fn init(allocator: Allocator) !SelfInfo {
63 var debug_info: SelfInfo = .{
64 .allocator = allocator,
65 .address_map = std.AutoHashMap(usize, *Module).init(allocator),
66 .modules = if (native_os == .windows) .{} else {},
67 };
68
69 if (native_os == .windows) {
70 errdefer debug_info.modules.deinit(allocator);
71
72 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
73 if (handle == windows.INVALID_HANDLE_VALUE) {
74 switch (windows.GetLastError()) {
75 else => |err| return windows.unexpectedError(err),
76 }
77 }
78 defer windows.CloseHandle(handle);
79
80 var module_entry: windows.MODULEENTRY32 = undefined;
81 module_entry.dwSize = @sizeOf(windows.MODULEENTRY32);
82 if (windows.kernel32.Module32First(handle, &module_entry) == 0) {
83 return error.MissingDebugInfo;
84 }
85
86 var module_valid = true;
87 while (module_valid) {
88 const module_info = try debug_info.modules.addOne(allocator);
89 const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{};
90 errdefer allocator.free(name);
91
92 module_info.* = .{
93 .base_address = @intFromPtr(module_entry.modBaseAddr),
94 .size = module_entry.modBaseSize,
95 .name = name,
96 .handle = module_entry.hModule,
97 };
98
99 module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1;
100 }
101 }
102
103 return debug_info;
104}
105
106pub fn deinit(self: *SelfInfo) void {
107 var it = self.address_map.iterator();
108 while (it.next()) |entry| {
109 const mdi = entry.value_ptr.*;
110 mdi.deinit(self.allocator);
111 self.allocator.destroy(mdi);
112 }
113 self.address_map.deinit();
114 if (native_os == .windows) {
115 for (self.modules.items) |module| {
116 self.allocator.free(module.name);
117 if (module.mapped_file) |mapped_file| mapped_file.deinit();
118 }
119 self.modules.deinit(self.allocator);
120 }
121}
122
123pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module {
124 if (comptime builtin.target.isDarwin()) {
125 return self.lookupModuleDyld(address);
126 } else if (native_os == .windows) {
127 return self.lookupModuleWin32(address);
128 } else if (native_os == .haiku) {
129 return self.lookupModuleHaiku(address);
130 } else if (comptime builtin.target.isWasm()) {
131 return self.lookupModuleWasm(address);
132 } else {
133 return self.lookupModuleDl(address);
134 }
135}
136
137// Returns the module name for a given address.
138// This can be called when getModuleForAddress fails, so implementations should provide
139// a path that doesn't rely on any side-effects of a prior successful module lookup.
140pub fn getModuleNameForAddress(self: *SelfInfo, address: usize) ?[]const u8 {
141 if (comptime builtin.target.isDarwin()) {
142 return self.lookupModuleNameDyld(address);
143 } else if (native_os == .windows) {
144 return self.lookupModuleNameWin32(address);
145 } else if (native_os == .haiku) {
146 return null;
147 } else if (comptime builtin.target.isWasm()) {
148 return null;
149 } else {
150 return self.lookupModuleNameDl(address);
151 }
152}
153
154fn lookupModuleDyld(self: *SelfInfo, address: usize) !*Module {
155 const image_count = std.c._dyld_image_count();
156
157 var i: u32 = 0;
158 while (i < image_count) : (i += 1) {
159 const header = std.c._dyld_get_image_header(i) orelse continue;
160 const base_address = @intFromPtr(header);
161 if (address < base_address) continue;
162 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
163
164 var it = macho.LoadCommandIterator{
165 .ncmds = header.ncmds,
166 .buffer = @alignCast(@as(
167 [*]u8,
168 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
169 )[0..header.sizeofcmds]),
170 };
171
172 var unwind_info: ?[]const u8 = null;
173 var eh_frame: ?[]const u8 = null;
174 while (it.next()) |cmd| switch (cmd.cmd()) {
175 .SEGMENT_64 => {
176 const segment_cmd = cmd.cast(macho.segment_command_64).?;
177 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
178
179 const seg_start = segment_cmd.vmaddr + vmaddr_slide;
180 const seg_end = seg_start + segment_cmd.vmsize;
181 if (address >= seg_start and address < seg_end) {
182 if (self.address_map.get(base_address)) |obj_di| {
183 return obj_di;
184 }
185
186 for (cmd.getSections()) |sect| {
187 if (mem.eql(u8, "__unwind_info", sect.sectName())) {
188 unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
189 } else if (mem.eql(u8, "__eh_frame", sect.sectName())) {
190 eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
191 }
192 }
193
194 const obj_di = try self.allocator.create(Module);
195 errdefer self.allocator.destroy(obj_di);
196
197 const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0);
198 const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) {
199 error.FileNotFound => return error.MissingDebugInfo,
200 else => return err,
201 };
202 obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
203 obj_di.base_address = base_address;
204 obj_di.vmaddr_slide = vmaddr_slide;
205 obj_di.unwind_info = unwind_info;
206 obj_di.eh_frame = eh_frame;
207
208 try self.address_map.putNoClobber(base_address, obj_di);
209
210 return obj_di;
211 }
212 },
213 else => {},
214 };
215 }
216
217 return error.MissingDebugInfo;
218}
219
220fn lookupModuleNameDyld(self: *SelfInfo, address: usize) ?[]const u8 {
221 _ = self;
222 const image_count = std.c._dyld_image_count();
223
224 var i: u32 = 0;
225 while (i < image_count) : (i += 1) {
226 const header = std.c._dyld_get_image_header(i) orelse continue;
227 const base_address = @intFromPtr(header);
228 if (address < base_address) continue;
229 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
230
231 var it = macho.LoadCommandIterator{
232 .ncmds = header.ncmds,
233 .buffer = @alignCast(@as(
234 [*]u8,
235 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
236 )[0..header.sizeofcmds]),
237 };
238
239 while (it.next()) |cmd| switch (cmd.cmd()) {
240 .SEGMENT_64 => {
241 const segment_cmd = cmd.cast(macho.segment_command_64).?;
242 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
243
244 const original_address = address - vmaddr_slide;
245 const seg_start = segment_cmd.vmaddr;
246 const seg_end = seg_start + segment_cmd.vmsize;
247 if (original_address >= seg_start and original_address < seg_end) {
248 return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0));
249 }
250 },
251 else => {},
252 };
253 }
254
255 return null;
256}
257
258fn lookupModuleWin32(self: *SelfInfo, address: usize) !*Module {
259 for (self.modules.items) |*module| {
260 if (address >= module.base_address and address < module.base_address + module.size) {
261 if (self.address_map.get(module.base_address)) |obj_di| {
262 return obj_di;
263 }
264
265 const obj_di = try self.allocator.create(Module);
266 errdefer self.allocator.destroy(obj_di);
267
268 const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size];
269 var coff_obj = try coff.Coff.init(mapped_module, true);
270
271 // The string table is not mapped into memory by the loader, so if a section name is in the
272 // string table then we have to map the full image file from disk. This can happen when
273 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
274 if (coff_obj.strtabRequired()) {
275 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
276 // openFileAbsoluteW requires the prefix to be present
277 @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' });
278
279 const process_handle = windows.GetCurrentProcess();
280 const len = windows.kernel32.GetModuleFileNameExW(
281 process_handle,
282 module.handle,
283 @ptrCast(&name_buffer[4]),
284 windows.PATH_MAX_WIDE,
285 );
286
287 if (len == 0) return error.MissingDebugInfo;
288 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
289 error.FileNotFound => return error.MissingDebugInfo,
290 else => return err,
291 };
292 errdefer coff_file.close();
293
294 var section_handle: windows.HANDLE = undefined;
295 const create_section_rc = windows.ntdll.NtCreateSection(
296 &section_handle,
297 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
298 null,
299 null,
300 windows.PAGE_READONLY,
301 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
302 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
303 windows.SEC_COMMIT,
304 coff_file.handle,
305 );
306 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
307 errdefer windows.CloseHandle(section_handle);
308
309 var coff_len: usize = 0;
310 var base_ptr: usize = 0;
311 const map_section_rc = windows.ntdll.NtMapViewOfSection(
312 section_handle,
313 process_handle,
314 @ptrCast(&base_ptr),
315 null,
316 0,
317 null,
318 &coff_len,
319 .ViewUnmap,
320 0,
321 windows.PAGE_READONLY,
322 );
323 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
324 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS);
325
326 const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len];
327 coff_obj = try coff.Coff.init(section_view, false);
328
329 module.mapped_file = .{
330 .file = coff_file,
331 .section_handle = section_handle,
332 .section_view = section_view,
333 };
334 }
335 errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit();
336
337 obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj);
338 obj_di.base_address = module.base_address;
339
340 try self.address_map.putNoClobber(module.base_address, obj_di);
341 return obj_di;
342 }
343 }
344
345 return error.MissingDebugInfo;
346}
347
348fn lookupModuleNameWin32(self: *SelfInfo, address: usize) ?[]const u8 {
349 for (self.modules.items) |module| {
350 if (address >= module.base_address and address < module.base_address + module.size) {
351 return module.name;
352 }
353 }
354 return null;
355}
356
357fn lookupModuleNameDl(self: *SelfInfo, address: usize) ?[]const u8 {
358 _ = self;
359
360 var ctx: struct {
361 // Input
362 address: usize,
363 // Output
364 name: []const u8 = "",
365 } = .{ .address = address };
366 const CtxTy = @TypeOf(ctx);
367
368 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
369 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
370 _ = size;
371 if (context.address < info.addr) return;
372 const phdrs = info.phdr[0..info.phnum];
373 for (phdrs) |*phdr| {
374 if (phdr.p_type != elf.PT_LOAD) continue;
375
376 const seg_start = info.addr +% phdr.p_vaddr;
377 const seg_end = seg_start + phdr.p_memsz;
378 if (context.address >= seg_start and context.address < seg_end) {
379 context.name = mem.sliceTo(info.name, 0) orelse "";
380 break;
381 }
382 } else return;
383
384 return error.Found;
385 }
386 }.callback)) {
387 return null;
388 } else |err| switch (err) {
389 error.Found => return fs.path.basename(ctx.name),
390 }
391
392 return null;
393}
394
395fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module {
396 var ctx: struct {
397 // Input
398 address: usize,
399 // Output
400 base_address: usize = undefined,
401 name: []const u8 = undefined,
402 build_id: ?[]const u8 = null,
403 gnu_eh_frame: ?[]const u8 = null,
404 } = .{ .address = address };
405 const CtxTy = @TypeOf(ctx);
406
407 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
408 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
409 _ = size;
410 // The base address is too high
411 if (context.address < info.addr)
412 return;
413
414 const phdrs = info.phdr[0..info.phnum];
415 for (phdrs) |*phdr| {
416 if (phdr.p_type != elf.PT_LOAD) continue;
417
418 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
419 const seg_start = info.addr +% phdr.p_vaddr;
420 const seg_end = seg_start + phdr.p_memsz;
421 if (context.address >= seg_start and context.address < seg_end) {
422 // Android libc uses NULL instead of an empty string to mark the
423 // main program
424 context.name = mem.sliceTo(info.name, 0) orelse "";
425 context.base_address = info.addr;
426 break;
427 }
428 } else return;
429
430 for (info.phdr[0..info.phnum]) |phdr| {
431 switch (phdr.p_type) {
432 elf.PT_NOTE => {
433 // Look for .note.gnu.build-id
434 const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
435 const name_size = mem.readInt(u32, note_bytes[0..4], native_endian);
436 if (name_size != 4) continue;
437 const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian);
438 const note_type = mem.readInt(u32, note_bytes[8..12], native_endian);
439 if (note_type != elf.NT_GNU_BUILD_ID) continue;
440 if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
441 context.build_id = note_bytes[16..][0..desc_size];
442 },
443 elf.PT_GNU_EH_FRAME => {
444 context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
445 },
446 else => {},
447 }
448 }
449
450 // Stop the iteration
451 return error.Found;
452 }
453 }.callback)) {
454 return error.MissingDebugInfo;
455 } else |err| switch (err) {
456 error.Found => {},
457 }
458
459 if (self.address_map.get(ctx.base_address)) |obj_di| {
460 return obj_di;
461 }
462
463 const obj_di = try self.allocator.create(Module);
464 errdefer self.allocator.destroy(obj_di);
465
466 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
467 if (ctx.gnu_eh_frame) |eh_frame_hdr| {
468 // This is a special case - pointer offsets inside .eh_frame_hdr
469 // are encoded relative to its base address, so we must use the
470 // version that is already memory mapped, and not the one that
471 // will be mapped separately from the ELF file.
472 sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{
473 .data = eh_frame_hdr,
474 .owned = false,
475 };
476 }
477
478 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
479 obj_di.base_address = ctx.base_address;
480
481 // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding
482 obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {};
483
484 try self.address_map.putNoClobber(ctx.base_address, obj_di);
485
486 return obj_di;
487}
488
489fn lookupModuleHaiku(self: *SelfInfo, address: usize) !*Module {
490 _ = self;
491 _ = address;
492 @panic("TODO implement lookup module for Haiku");
493}
494
495fn lookupModuleWasm(self: *SelfInfo, address: usize) !*Module {
496 _ = self;
497 _ = address;
498 @panic("TODO implement lookup module for Wasm");
499}
500
501pub const Module = switch (native_os) {
502 .macos, .ios, .watchos, .tvos, .visionos => struct {
503 base_address: usize,
504 vmaddr_slide: usize,
505 mapped_memory: []align(mem.page_size) const u8,
506 symbols: []const MachoSymbol,
507 strings: [:0]const u8,
508 ofiles: OFileTable,
509
510 // Backed by the in-memory sections mapped by the loader
511 unwind_info: ?[]const u8 = null,
512 eh_frame: ?[]const u8 = null,
513
514 const OFileTable = std.StringHashMap(OFileInfo);
515 const OFileInfo = struct {
516 di: Dwarf,
517 addr_table: std.StringHashMap(u64),
518 };
519
520 pub fn deinit(self: *@This(), allocator: Allocator) void {
521 var it = self.ofiles.iterator();
522 while (it.next()) |entry| {
523 const ofile = entry.value_ptr;
524 ofile.di.deinit(allocator);
525 ofile.addr_table.deinit();
526 }
527 self.ofiles.deinit();
528 allocator.free(self.symbols);
529 posix.munmap(self.mapped_memory);
530 }
531
532 fn loadOFile(self: *@This(), allocator: Allocator, o_file_path: []const u8) !*OFileInfo {
533 const o_file = try fs.cwd().openFile(o_file_path, .{});
534 const mapped_mem = try mapWholeFile(o_file);
535
536 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
537 if (hdr.magic != std.macho.MH_MAGIC_64)
538 return error.InvalidDebugInfo;
539
540 var segcmd: ?macho.LoadCommandIterator.LoadCommand = null;
541 var symtabcmd: ?macho.symtab_command = null;
542 var it = macho.LoadCommandIterator{
543 .ncmds = hdr.ncmds,
544 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
545 };
546 while (it.next()) |cmd| switch (cmd.cmd()) {
547 .SEGMENT_64 => segcmd = cmd,
548 .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?,
549 else => {},
550 };
551
552 if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo;
553
554 // Parse symbols
555 const strtab = @as(
556 [*]const u8,
557 @ptrCast(&mapped_mem[symtabcmd.?.stroff]),
558 )[0 .. symtabcmd.?.strsize - 1 :0];
559 const symtab = @as(
560 [*]const macho.nlist_64,
561 @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])),
562 )[0..symtabcmd.?.nsyms];
563
564 // TODO handle tentative (common) symbols
565 var addr_table = std.StringHashMap(u64).init(allocator);
566 try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len)));
567 for (symtab) |sym| {
568 if (sym.n_strx == 0) continue;
569 if (sym.undf() or sym.tentative() or sym.abs()) continue;
570 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
571 // TODO is it possible to have a symbol collision?
572 addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);
573 }
574
575 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
576 if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{
577 .data = eh_frame,
578 .owned = false,
579 };
580
581 for (segcmd.?.getSections()) |sect| {
582 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
583
584 var section_index: ?usize = null;
585 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
586 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i;
587 }
588 if (section_index == null) continue;
589
590 const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size);
591 sections[section_index.?] = .{
592 .data = section_bytes,
593 .virtual_address = sect.addr,
594 .owned = false,
595 };
596 }
597
598 const missing_debug_info =
599 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
600 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
601 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
602 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
603 if (missing_debug_info) return error.MissingDebugInfo;
604
605 var di = Dwarf{
606 .endian = .little,
607 .sections = sections,
608 .is_macho = true,
609 };
610
611 try Dwarf.open(&di, allocator);
612 const info = OFileInfo{
613 .di = di,
614 .addr_table = addr_table,
615 };
616
617 // Add the debug info to the cache
618 const result = try self.ofiles.getOrPut(o_file_path);
619 assert(!result.found_existing);
620 result.value_ptr.* = info;
621
622 return result.value_ptr;
623 }
624
625 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
626 nosuspend {
627 const result = try self.getOFileInfoForAddress(allocator, address);
628 if (result.symbol == null) return .{};
629
630 // Take the symbol name from the N_FUN STAB entry, we're going to
631 // use it if we fail to find the DWARF infos
632 const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0);
633 if (result.o_file_info == null) return .{ .symbol_name = stab_symbol };
634
635 // Translate again the address, this time into an address inside the
636 // .o file
637 const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{
638 .symbol_name = "???",
639 };
640
641 const addr_off = result.relocated_address - result.symbol.?.addr;
642 const o_file_di = &result.o_file_info.?.di;
643 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
644 return SymbolInfo{
645 .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
646 .compile_unit_name = compile_unit.die.getAttrString(
647 o_file_di,
648 std.dwarf.AT.name,
649 o_file_di.section(.debug_str),
650 compile_unit.*,
651 ) catch |err| switch (err) {
652 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
653 },
654 .line_info = o_file_di.getLineNumberInfo(
655 allocator,
656 compile_unit.*,
657 relocated_address_o + addr_off,
658 ) catch |err| switch (err) {
659 error.MissingDebugInfo, error.InvalidDebugInfo => null,
660 else => return err,
661 },
662 };
663 } else |err| switch (err) {
664 error.MissingDebugInfo, error.InvalidDebugInfo => {
665 return SymbolInfo{ .symbol_name = stab_symbol };
666 },
667 else => return err,
668 }
669 }
670 }
671
672 pub fn getOFileInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !struct {
673 relocated_address: usize,
674 symbol: ?*const MachoSymbol = null,
675 o_file_info: ?*OFileInfo = null,
676 } {
677 nosuspend {
678 // Translate the VA into an address into this object
679 const relocated_address = address - self.vmaddr_slide;
680
681 // Find the .o file where this symbol is defined
682 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{
683 .relocated_address = relocated_address,
684 };
685
686 // Check if its debug infos are already in the cache
687 const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
688 const o_file_info = self.ofiles.getPtr(o_file_path) orelse
689 (self.loadOFile(allocator, o_file_path) catch |err| switch (err) {
690 error.FileNotFound,
691 error.MissingDebugInfo,
692 error.InvalidDebugInfo,
693 => return .{
694 .relocated_address = relocated_address,
695 .symbol = symbol,
696 },
697 else => return err,
698 });
699
700 return .{
701 .relocated_address = relocated_address,
702 .symbol = symbol,
703 .o_file_info = o_file_info,
704 };
705 }
706 }
707
708 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
709 return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;
710 }
711 },
712 .uefi, .windows => struct {
713 base_address: usize,
714 pdb: ?Pdb = null,
715 dwarf: ?Dwarf = null,
716 coff_image_base: u64,
717
718 /// Only used if pdb is non-null
719 coff_section_headers: []coff.SectionHeader,
720
721 pub fn deinit(self: *@This(), allocator: Allocator) void {
722 if (self.dwarf) |*dwarf| {
723 dwarf.deinit(allocator);
724 }
725
726 if (self.pdb) |*p| {
727 p.deinit();
728 allocator.free(self.coff_section_headers);
729 }
730 }
731
732 fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo {
733 var coff_section: *align(1) const coff.SectionHeader = undefined;
734 const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| {
735 if (sect_contrib.Section > self.coff_section_headers.len) continue;
736 // Remember that SectionContribEntry.Section is 1-based.
737 coff_section = &self.coff_section_headers[sect_contrib.Section - 1];
738
739 const vaddr_start = coff_section.virtual_address + sect_contrib.Offset;
740 const vaddr_end = vaddr_start + sect_contrib.Size;
741 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
742 break sect_contrib.ModuleIndex;
743 }
744 } else {
745 // we have no information to add to the address
746 return null;
747 };
748
749 const module = (try self.pdb.?.getModule(mod_index)) orelse
750 return error.InvalidDebugInfo;
751 const obj_basename = fs.path.basename(module.obj_file_name);
752
753 const symbol_name = self.pdb.?.getSymbolName(
754 module,
755 relocated_address - coff_section.virtual_address,
756 ) orelse "???";
757 const opt_line_info = try self.pdb.?.getLineNumberInfo(
758 module,
759 relocated_address - coff_section.virtual_address,
760 );
761
762 return SymbolInfo{
763 .symbol_name = symbol_name,
764 .compile_unit_name = obj_basename,
765 .line_info = opt_line_info,
766 };
767 }
768
769 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
770 // Translate the VA into an address into this object
771 const relocated_address = address - self.base_address;
772
773 if (self.pdb != null) {
774 if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol;
775 }
776
777 if (self.dwarf) |*dwarf| {
778 const dwarf_address = relocated_address + self.coff_image_base;
779 return getSymbolFromDwarf(allocator, dwarf_address, dwarf);
780 }
781
782 return SymbolInfo{};
783 }
784
785 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
786 _ = allocator;
787 _ = address;
788
789 return switch (self.debug_data) {
790 .dwarf => |*dwarf| dwarf,
791 else => null,
792 };
793 }
794 },
795 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
796 base_address: usize,
797 dwarf: Dwarf,
798 mapped_memory: []align(mem.page_size) const u8,
799 external_mapped_memory: ?[]align(mem.page_size) const u8,
800
801 pub fn deinit(self: *@This(), allocator: Allocator) void {
802 self.dwarf.deinit(allocator);
803 posix.munmap(self.mapped_memory);
804 if (self.external_mapped_memory) |m| posix.munmap(m);
805 }
806
807 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
808 // Translate the VA into an address into this object
809 const relocated_address = address - self.base_address;
810 return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf);
811 }
812
813 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
814 _ = allocator;
815 _ = address;
816 return &self.dwarf;
817 }
818 },
819 .wasi, .emscripten => struct {
820 pub fn deinit(self: *@This(), allocator: Allocator) void {
821 _ = self;
822 _ = allocator;
823 }
824
825 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
826 _ = self;
827 _ = allocator;
828 _ = address;
829 return SymbolInfo{};
830 }
831
832 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
833 _ = self;
834 _ = allocator;
835 _ = address;
836 return null;
837 }
838 },
839 else => Dwarf,
840};
841
842/// How is this different than `Module` when the host is Windows?
843/// Why are both stored in the `SelfInfo` struct?
844/// Boy, it sure would be nice if someone added documentation comments for this
845/// struct explaining it.
846pub const WindowsModule = struct {
847 base_address: usize,
848 size: u32,
849 name: []const u8,
850 handle: windows.HMODULE,
851
852 // Set when the image file needed to be mapped from disk
853 mapped_file: ?struct {
854 file: File,
855 section_handle: windows.HANDLE,
856 section_view: []const u8,
857
858 pub fn deinit(self: @This()) void {
859 const process_handle = windows.GetCurrentProcess();
860 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS);
861 windows.CloseHandle(self.section_handle);
862 self.file.close();
863 }
864 } = null,
865};
866
867/// This takes ownership of macho_file: users of this function should not close
868/// it themselves, even on error.
869/// TODO it's weird to take ownership even on error, rework this code.
870fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
871 const mapped_mem = try mapWholeFile(macho_file);
872
873 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
874 if (hdr.magic != macho.MH_MAGIC_64)
875 return error.InvalidDebugInfo;
876
877 var it = macho.LoadCommandIterator{
878 .ncmds = hdr.ncmds,
879 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
880 };
881 const symtab = while (it.next()) |cmd| switch (cmd.cmd()) {
882 .SYMTAB => break cmd.cast(macho.symtab_command).?,
883 else => {},
884 } else return error.MissingDebugInfo;
885
886 const syms = @as(
887 [*]const macho.nlist_64,
888 @ptrCast(@alignCast(&mapped_mem[symtab.symoff])),
889 )[0..symtab.nsyms];
890 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
891
892 const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
893
894 var ofile: u32 = undefined;
895 var last_sym: MachoSymbol = undefined;
896 var symbol_index: usize = 0;
897 var state: enum {
898 init,
899 oso_open,
900 oso_close,
901 bnsym,
902 fun_strx,
903 fun_size,
904 ensym,
905 } = .init;
906
907 for (syms) |*sym| {
908 if (!sym.stab()) continue;
909
910 // TODO handle globals N_GSYM, and statics N_STSYM
911 switch (sym.n_type) {
912 macho.N_OSO => {
913 switch (state) {
914 .init, .oso_close => {
915 state = .oso_open;
916 ofile = sym.n_strx;
917 },
918 else => return error.InvalidDebugInfo,
919 }
920 },
921 macho.N_BNSYM => {
922 switch (state) {
923 .oso_open, .ensym => {
924 state = .bnsym;
925 last_sym = .{
926 .strx = 0,
927 .addr = sym.n_value,
928 .size = 0,
929 .ofile = ofile,
930 };
931 },
932 else => return error.InvalidDebugInfo,
933 }
934 },
935 macho.N_FUN => {
936 switch (state) {
937 .bnsym => {
938 state = .fun_strx;
939 last_sym.strx = sym.n_strx;
940 },
941 .fun_strx => {
942 state = .fun_size;
943 last_sym.size = @as(u32, @intCast(sym.n_value));
944 },
945 else => return error.InvalidDebugInfo,
946 }
947 },
948 macho.N_ENSYM => {
949 switch (state) {
950 .fun_size => {
951 state = .ensym;
952 symbols_buf[symbol_index] = last_sym;
953 symbol_index += 1;
954 },
955 else => return error.InvalidDebugInfo,
956 }
957 },
958 macho.N_SO => {
959 switch (state) {
960 .init, .oso_close => {},
961 .oso_open, .ensym => {
962 state = .oso_close;
963 },
964 else => return error.InvalidDebugInfo,
965 }
966 },
967 else => {},
968 }
969 }
970
971 switch (state) {
972 .init => return error.MissingDebugInfo,
973 .oso_close => {},
974 else => return error.InvalidDebugInfo,
975 }
976
977 const symbols = try allocator.realloc(symbols_buf, symbol_index);
978
979 // Even though lld emits symbols in ascending order, this debug code
980 // should work for programs linked in any valid way.
981 // This sort is so that we can binary search later.
982 mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan);
983
984 return .{
985 .base_address = undefined,
986 .vmaddr_slide = undefined,
987 .mapped_memory = mapped_mem,
988 .ofiles = Module.OFileTable.init(allocator),
989 .symbols = symbols,
990 .strings = strings,
991 };
992}
993
994fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module {
995 nosuspend {
996 var di: Module = .{
997 .base_address = undefined,
998 .coff_image_base = coff_obj.getImageBase(),
999 .coff_section_headers = undefined,
1000 };
1001
1002 if (coff_obj.getSectionByName(".debug_info")) |_| {
1003 // This coff file has embedded DWARF debug info
1004 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
1005 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1006
1007 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1008 sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
1009 break :blk .{
1010 .data = try coff_obj.getSectionDataAlloc(section_header, allocator),
1011 .virtual_address = section_header.virtual_address,
1012 .owned = true,
1013 };
1014 } else null;
1015 }
1016
1017 var dwarf = Dwarf{
1018 .endian = native_endian,
1019 .sections = sections,
1020 .is_macho = false,
1021 };
1022
1023 try Dwarf.open(&dwarf, allocator);
1024 di.dwarf = dwarf;
1025 }
1026
1027 const raw_path = try coff_obj.getPdbPath() orelse return di;
1028 const path = blk: {
1029 if (fs.path.isAbsolute(raw_path)) {
1030 break :blk raw_path;
1031 } else {
1032 const self_dir = try fs.selfExeDirPathAlloc(allocator);
1033 defer allocator.free(self_dir);
1034 break :blk try fs.path.join(allocator, &.{ self_dir, raw_path });
1035 }
1036 };
1037 defer if (path.ptr != raw_path.ptr) allocator.free(path);
1038
1039 di.pdb = Pdb.init(allocator, path) catch |err| switch (err) {
1040 error.FileNotFound, error.IsDir => {
1041 if (di.dwarf == null) return error.MissingDebugInfo;
1042 return di;
1043 },
1044 else => return err,
1045 };
1046 try di.pdb.?.parseInfoStream();
1047 try di.pdb.?.parseDbiStream();
1048
1049 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
1050 return error.InvalidDebugInfo;
1051
1052 // Only used by the pdb path
1053 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
1054 errdefer allocator.free(di.coff_section_headers);
1055
1056 return di;
1057 }
1058}
1059
1060/// Reads debug info from an ELF file, or the current binary if none in specified.
1061/// If the required sections aren't present but a reference to external debug info is,
1062/// then this this function will recurse to attempt to load the debug sections from
1063/// an external file.
1064pub fn readElfDebugInfo(
1065 allocator: Allocator,
1066 elf_filename: ?[]const u8,
1067 build_id: ?[]const u8,
1068 expected_crc: ?u32,
1069 parent_sections: *Dwarf.SectionArray,
1070 parent_mapped_mem: ?[]align(mem.page_size) const u8,
1071) !Module {
1072 nosuspend {
1073 const elf_file = (if (elf_filename) |filename| blk: {
1074 break :blk fs.cwd().openFile(filename, .{});
1075 } else fs.openSelfExe(.{})) catch |err| switch (err) {
1076 error.FileNotFound => return error.MissingDebugInfo,
1077 else => return err,
1078 };
1079
1080 const mapped_mem = try mapWholeFile(elf_file);
1081 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
1082
1083 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
1084 if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
1085 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
1086
1087 const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {
1088 elf.ELFDATA2LSB => .little,
1089 elf.ELFDATA2MSB => .big,
1090 else => return error.InvalidElfEndian,
1091 };
1092 assert(endian == native_endian); // this is our own debug info
1093
1094 const shoff = hdr.e_shoff;
1095 const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
1096 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow]));
1097 const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
1098 const shdrs = @as(
1099 [*]const elf.Shdr,
1100 @ptrCast(@alignCast(&mapped_mem[shoff])),
1101 )[0..hdr.e_shnum];
1102
1103 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
1104
1105 // Combine section list. This takes ownership over any owned sections from the parent scope.
1106 for (parent_sections, &sections) |*parent, *section| {
1107 if (parent.*) |*p| {
1108 section.* = p.*;
1109 p.owned = false;
1110 }
1111 }
1112 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1113
1114 var separate_debug_filename: ?[]const u8 = null;
1115 var separate_debug_crc: ?u32 = null;
1116
1117 for (shdrs) |*shdr| {
1118 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
1119 const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
1120
1121 if (mem.eql(u8, name, ".gnu_debuglink")) {
1122 const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1123 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
1124 const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr);
1125 const crc_bytes = gnu_debuglink[crc_offset..][0..4];
1126 separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian);
1127 separate_debug_filename = debug_filename;
1128 continue;
1129 }
1130
1131 var section_index: ?usize = null;
1132 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1133 if (mem.eql(u8, "." ++ section.name, name)) section_index = i;
1134 }
1135 if (section_index == null) continue;
1136 if (sections[section_index.?] != null) continue;
1137
1138 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1139 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
1140 var section_stream = std.io.fixedBufferStream(section_bytes);
1141 var section_reader = section_stream.reader();
1142 const chdr = section_reader.readStruct(elf.Chdr) catch continue;
1143 if (chdr.ch_type != .ZLIB) continue;
1144
1145 var zlib_stream = std.compress.zlib.decompressor(section_stream.reader());
1146
1147 const 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(Dwarf.Section.Id.debug_info)] == null or
1167 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
1168 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
1169 sections[@intFromEnum(Dwarf.Section.Id.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 = posix.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 }
1232 }
1233
1234 return error.MissingDebugInfo;
1235 }
1236
1237 var di = Dwarf{
1238 .endian = endian,
1239 .sections = sections,
1240 .is_macho = false,
1241 };
1242
1243 try Dwarf.open(&di, allocator);
1244
1245 return .{
1246 .base_address = undefined,
1247 .dwarf = di,
1248 .mapped_memory = parent_mapped_mem orelse mapped_mem,
1249 .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
1250 };
1251 }
1252}
1253
1254const MachoSymbol = struct {
1255 strx: u32,
1256 addr: u64,
1257 size: u32,
1258 ofile: u32,
1259
1260 /// Returns the address from the macho file
1261 fn address(self: MachoSymbol) u64 {
1262 return self.addr;
1263 }
1264
1265 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
1266 _ = context;
1267 return lhs.addr < rhs.addr;
1268 }
1269};
1270
1271/// Takes ownership of file, even on error.
1272/// TODO it's weird to take ownership even on error, rework this code.
1273fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {
1274 nosuspend {
1275 defer file.close();
1276
1277 const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize);
1278 const mapped_mem = try posix.mmap(
1279 null,
1280 file_len,
1281 posix.PROT.READ,
1282 .{ .TYPE = .SHARED },
1283 file.handle,
1284 0,
1285 );
1286 errdefer posix.munmap(mapped_mem);
1287
1288 return mapped_mem;
1289 }
1290}
1291
1292fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 {
1293 const start = math.cast(usize, offset) orelse return error.Overflow;
1294 const end = start + (math.cast(usize, size) orelse return error.Overflow);
1295 return ptr[start..end];
1296}
1297
1298pub const SymbolInfo = struct {
1299 symbol_name: []const u8 = "???",
1300 compile_unit_name: []const u8 = "???",
1301 line_info: ?std.debug.SourceLocation = null,
1302
1303 pub fn deinit(self: SymbolInfo, allocator: Allocator) void {
1304 if (self.line_info) |li| allocator.free(li.file_name);
1305 }
1306};
1307
1308fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
1309 var min: usize = 0;
1310 var max: usize = symbols.len - 1;
1311 while (min < max) {
1312 const mid = min + (max - min) / 2;
1313 const curr = &symbols[mid];
1314 const next = &symbols[mid + 1];
1315 if (address >= next.address()) {
1316 min = mid + 1;
1317 } else if (address < curr.address()) {
1318 max = mid;
1319 } else {
1320 return curr;
1321 }
1322 }
1323
1324 const max_sym = &symbols[symbols.len - 1];
1325 if (address >= max_sym.address())
1326 return max_sym;
1327
1328 return null;
1329}
1330
1331test machoSearchSymbols {
1332 const symbols = [_]MachoSymbol{
1333 .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
1334 .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
1335 .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
1336 };
1337
1338 try testing.expectEqual(null, machoSearchSymbols(&symbols, 0));
1339 try testing.expectEqual(null, machoSearchSymbols(&symbols, 99));
1340 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?);
1341 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?);
1342 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?);
1343
1344 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?);
1345 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?);
1346 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?);
1347
1348 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?);
1349 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?);
1350 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?);
1351}
1352
1353fn getSymbolFromDwarf(allocator: Allocator, address: u64, di: *Dwarf) !SymbolInfo {
1354 if (nosuspend di.findCompileUnit(address)) |compile_unit| {
1355 return SymbolInfo{
1356 .symbol_name = nosuspend di.getSymbolName(address) orelse "???",
1357 .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) {
1358 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
1359 },
1360 .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) {
1361 error.MissingDebugInfo, error.InvalidDebugInfo => null,
1362 else => return err,
1363 },
1364 };
1365 } else |err| switch (err) {
1366 error.MissingDebugInfo, error.InvalidDebugInfo => {
1367 return SymbolInfo{};
1368 },
1369 else => return err,
1370 }
1371}
1372
1373/// Unwind a frame using MachO compact unwind info (from __unwind_info).
1374/// If the compact encoding can't encode a way to unwind a frame, it will
1375/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
1376pub fn unwindFrameMachO(
1377 context: *UnwindContext,
1378 ma: *std.debug.MemoryAccessor,
1379 unwind_info: []const u8,
1380 eh_frame: ?[]const u8,
1381 module_base_address: usize,
1382) !usize {
1383 const header = std.mem.bytesAsValue(
1384 macho.unwind_info_section_header,
1385 unwind_info[0..@sizeOf(macho.unwind_info_section_header)],
1386 );
1387 const indices = std.mem.bytesAsSlice(
1388 macho.unwind_info_section_header_index_entry,
1389 unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)],
1390 );
1391 if (indices.len == 0) return error.MissingUnwindInfo;
1392
1393 const mapped_pc = context.pc - module_base_address;
1394 const second_level_index = blk: {
1395 var left: usize = 0;
1396 var len: usize = indices.len;
1397
1398 while (len > 1) {
1399 const mid = left + len / 2;
1400 const offset = indices[mid].functionOffset;
1401 if (mapped_pc < offset) {
1402 len /= 2;
1403 } else {
1404 left = mid;
1405 if (mapped_pc == offset) break;
1406 len -= len / 2;
1407 }
1408 }
1409
1410 // Last index is a sentinel containing the highest address as its functionOffset
1411 if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo;
1412 break :blk &indices[left];
1413 };
1414
1415 const common_encodings = std.mem.bytesAsSlice(
1416 macho.compact_unwind_encoding_t,
1417 unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)],
1418 );
1419
1420 const start_offset = second_level_index.secondLevelPagesSectionOffset;
1421 const kind = std.mem.bytesAsValue(
1422 macho.UNWIND_SECOND_LEVEL,
1423 unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)],
1424 );
1425
1426 const entry: struct {
1427 function_offset: usize,
1428 raw_encoding: u32,
1429 } = switch (kind.*) {
1430 .REGULAR => blk: {
1431 const page_header = std.mem.bytesAsValue(
1432 macho.unwind_info_regular_second_level_page_header,
1433 unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)],
1434 );
1435
1436 const entries = std.mem.bytesAsSlice(
1437 macho.unwind_info_regular_second_level_entry,
1438 unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)],
1439 );
1440 if (entries.len == 0) return error.InvalidUnwindInfo;
1441
1442 var left: usize = 0;
1443 var len: usize = entries.len;
1444 while (len > 1) {
1445 const mid = left + len / 2;
1446 const offset = entries[mid].functionOffset;
1447 if (mapped_pc < offset) {
1448 len /= 2;
1449 } else {
1450 left = mid;
1451 if (mapped_pc == offset) break;
1452 len -= len / 2;
1453 }
1454 }
1455
1456 break :blk .{
1457 .function_offset = entries[left].functionOffset,
1458 .raw_encoding = entries[left].encoding,
1459 };
1460 },
1461 .COMPRESSED => blk: {
1462 const page_header = std.mem.bytesAsValue(
1463 macho.unwind_info_compressed_second_level_page_header,
1464 unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)],
1465 );
1466
1467 const entries = std.mem.bytesAsSlice(
1468 macho.UnwindInfoCompressedEntry,
1469 unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)],
1470 );
1471 if (entries.len == 0) return error.InvalidUnwindInfo;
1472
1473 var left: usize = 0;
1474 var len: usize = entries.len;
1475 while (len > 1) {
1476 const mid = left + len / 2;
1477 const offset = second_level_index.functionOffset + entries[mid].funcOffset;
1478 if (mapped_pc < offset) {
1479 len /= 2;
1480 } else {
1481 left = mid;
1482 if (mapped_pc == offset) break;
1483 len -= len / 2;
1484 }
1485 }
1486
1487 const entry = entries[left];
1488 const function_offset = second_level_index.functionOffset + entry.funcOffset;
1489 if (entry.encodingIndex < header.commonEncodingsArrayCount) {
1490 if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo;
1491 break :blk .{
1492 .function_offset = function_offset,
1493 .raw_encoding = common_encodings[entry.encodingIndex],
1494 };
1495 } else {
1496 const local_index = try math.sub(
1497 u8,
1498 entry.encodingIndex,
1499 math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo,
1500 );
1501 const local_encodings = std.mem.bytesAsSlice(
1502 macho.compact_unwind_encoding_t,
1503 unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)],
1504 );
1505 if (local_index >= local_encodings.len) return error.InvalidUnwindInfo;
1506 break :blk .{
1507 .function_offset = function_offset,
1508 .raw_encoding = local_encodings[local_index],
1509 };
1510 }
1511 },
1512 else => return error.InvalidUnwindInfo,
1513 };
1514
1515 if (entry.raw_encoding == 0) return error.NoUnwindInfo;
1516 const reg_context = Dwarf.abi.RegisterContext{
1517 .eh_frame = false,
1518 .is_macho = true,
1519 };
1520
1521 const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
1522 const new_ip = switch (builtin.cpu.arch) {
1523 .x86_64 => switch (encoding.mode.x86_64) {
1524 .OLD => return error.UnimplementedUnwindEncoding,
1525 .RBP_FRAME => blk: {
1526 const regs: [5]u3 = .{
1527 encoding.value.x86_64.frame.reg0,
1528 encoding.value.x86_64.frame.reg1,
1529 encoding.value.x86_64.frame.reg2,
1530 encoding.value.x86_64.frame.reg3,
1531 encoding.value.x86_64.frame.reg4,
1532 };
1533
1534 const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize);
1535 var max_reg: usize = 0;
1536 inline for (regs, 0..) |reg, i| {
1537 if (reg > 0) max_reg = i;
1538 }
1539
1540 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
1541 const new_sp = fp + 2 * @sizeOf(usize);
1542
1543 // Verify the stack range we're about to read register values from
1544 if (ma.load(usize, new_sp) == null or ma.load(usize, fp - frame_offset + max_reg * @sizeOf(usize)) == null) return error.InvalidUnwindInfo;
1545
1546 const ip_ptr = fp + @sizeOf(usize);
1547 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
1548 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
1549
1550 (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
1551 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
1552 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
1553
1554 for (regs, 0..) |reg, i| {
1555 if (reg == 0) continue;
1556 const addr = fp - frame_offset + i * @sizeOf(usize);
1557 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
1558 (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
1559 }
1560
1561 break :blk new_ip;
1562 },
1563 .STACK_IMMD,
1564 .STACK_IND,
1565 => blk: {
1566 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
1567 const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD)
1568 @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize)
1569 else stack_size: {
1570 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
1571 const sub_offset_addr =
1572 module_base_address +
1573 entry.function_offset +
1574 encoding.value.x86_64.frameless.stack.indirect.sub_offset;
1575 if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo;
1576
1577 // `sub_offset_addr` points to the offset of the literal within the instruction
1578 const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
1579 break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust);
1580 };
1581
1582 // Decode the Lehmer-coded sequence of registers.
1583 // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
1584
1585 // Decode the variable-based permutation number into its digits. Each digit represents
1586 // an index into the list of register numbers that weren't yet used in the sequence at
1587 // the time the digit was added.
1588 const reg_count = encoding.value.x86_64.frameless.stack_reg_count;
1589 const ip_ptr = if (reg_count > 0) reg_blk: {
1590 var digits: [6]u3 = undefined;
1591 var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation;
1592 var base: usize = 2;
1593 for (0..reg_count) |i| {
1594 const div = accumulator / base;
1595 digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
1596 accumulator = div;
1597 base += 1;
1598 }
1599
1600 const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 };
1601 var registers: [reg_numbers.len]u3 = undefined;
1602 var used_indices = [_]bool{false} ** reg_numbers.len;
1603 for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
1604 var unused_count: u8 = 0;
1605 const unused_index = for (used_indices, 0..) |used, index| {
1606 if (!used) {
1607 if (target_unused_index == unused_count) break index;
1608 unused_count += 1;
1609 }
1610 } else unreachable;
1611
1612 registers[i] = reg_numbers[unused_index];
1613 used_indices[unused_index] = true;
1614 }
1615
1616 var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
1617 if (ma.load(usize, reg_addr) == null) return error.InvalidUnwindInfo;
1618 for (0..reg_count) |i| {
1619 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
1620 (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1621 reg_addr += @sizeOf(usize);
1622 }
1623
1624 break :reg_blk reg_addr;
1625 } else sp + stack_size - @sizeOf(usize);
1626
1627 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
1628 const new_sp = ip_ptr + @sizeOf(usize);
1629 if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo;
1630
1631 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
1632 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
1633
1634 break :blk new_ip;
1635 },
1636 .DWARF => {
1637 return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));
1638 },
1639 },
1640 .aarch64 => switch (encoding.mode.arm64) {
1641 .OLD => return error.UnimplementedUnwindEncoding,
1642 .FRAMELESS => blk: {
1643 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
1644 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
1645 const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*;
1646 if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo;
1647 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
1648 break :blk new_ip;
1649 },
1650 .DWARF => {
1651 return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));
1652 },
1653 .FRAME => blk: {
1654 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
1655 const new_sp = fp + 16;
1656 const ip_ptr = fp + @sizeOf(usize);
1657
1658 const num_restored_pairs: usize =
1659 @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) +
1660 @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs)));
1661 const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize);
1662
1663 if (ma.load(usize, new_sp) == null or ma.load(usize, min_reg_addr) == null) return error.InvalidUnwindInfo;
1664
1665 var reg_addr = fp - @sizeOf(usize);
1666 inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| {
1667 if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) {
1668 (try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1669 reg_addr += @sizeOf(usize);
1670 (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1671 reg_addr += @sizeOf(usize);
1672 }
1673 }
1674
1675 inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| {
1676 if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) {
1677 // Only the lower half of the 128-bit V registers are restored during unwinding
1678 @memcpy(
1679 try regBytes(context.thread_context, 64 + 8 + i, context.reg_context),
1680 std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
1681 );
1682 reg_addr += @sizeOf(usize);
1683 @memcpy(
1684 try regBytes(context.thread_context, 64 + 9 + i, context.reg_context),
1685 std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
1686 );
1687 reg_addr += @sizeOf(usize);
1688 }
1689 }
1690
1691 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
1692 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
1693
1694 (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
1695 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
1696
1697 break :blk new_ip;
1698 },
1699 },
1700 else => return error.UnimplementedArch,
1701 };
1702
1703 context.pc = stripInstructionPtrAuthCode(new_ip);
1704 if (context.pc > 0) context.pc -= 1;
1705 return new_ip;
1706}
1707
1708pub const UnwindContext = struct {
1709 allocator: Allocator,
1710 cfa: ?usize,
1711 pc: usize,
1712 thread_context: *std.debug.ThreadContext,
1713 reg_context: Dwarf.abi.RegisterContext,
1714 vm: VirtualMachine,
1715 stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
1716
1717 pub fn init(
1718 allocator: Allocator,
1719 thread_context: *std.debug.ThreadContext,
1720 ) !UnwindContext {
1721 comptime assert(supports_unwinding);
1722
1723 const pc = stripInstructionPtrAuthCode(
1724 (try regValueNative(thread_context, ip_reg_num, null)).*,
1725 );
1726
1727 const context_copy = try allocator.create(std.debug.ThreadContext);
1728 std.debug.copyContext(thread_context, context_copy);
1729
1730 return .{
1731 .allocator = allocator,
1732 .cfa = null,
1733 .pc = pc,
1734 .thread_context = context_copy,
1735 .reg_context = undefined,
1736 .vm = .{},
1737 .stack_machine = .{},
1738 };
1739 }
1740
1741 pub fn deinit(self: *UnwindContext) void {
1742 self.vm.deinit(self.allocator);
1743 self.stack_machine.deinit(self.allocator);
1744 self.allocator.destroy(self.thread_context);
1745 self.* = undefined;
1746 }
1747
1748 pub fn getFp(self: *const UnwindContext) !usize {
1749 return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*;
1750 }
1751};
1752
1753/// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature.
1754/// This function clears these signature bits to make the pointer usable.
1755pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
1756 if (native_arch == .aarch64) {
1757 // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
1758 // The save / restore is because `xpaclri` operates on x30 (LR)
1759 return asm (
1760 \\mov x16, x30
1761 \\mov x30, x15
1762 \\hint 0x07
1763 \\mov x15, x30
1764 \\mov x30, x16
1765 : [ret] "={x15}" (-> usize),
1766 : [ptr] "{x15}" (ptr),
1767 : "x16"
1768 );
1769 }
1770
1771 return ptr;
1772}
1773
1774/// Unwind a stack frame using DWARF unwinding info, updating the register context.
1775///
1776/// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE.
1777/// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE.
1778///
1779/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
1780/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
1781pub fn unwindFrameDwarf(
1782 di: *const Dwarf,
1783 context: *UnwindContext,
1784 ma: *std.debug.MemoryAccessor,
1785 explicit_fde_offset: ?usize,
1786) !usize {
1787 if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
1788 if (context.pc == 0) return 0;
1789
1790 // Find the FDE and CIE
1791 var cie: Dwarf.CommonInformationEntry = undefined;
1792 var fde: Dwarf.FrameDescriptionEntry = undefined;
1793
1794 if (explicit_fde_offset) |fde_offset| {
1795 const dwarf_section: Dwarf.Section.Id = .eh_frame;
1796 const frame_section = di.section(dwarf_section) orelse return error.MissingFDE;
1797 if (fde_offset >= frame_section.len) return error.MissingFDE;
1798
1799 var fbr: std.debug.DeprecatedFixedBufferReader = .{
1800 .buf = frame_section,
1801 .pos = fde_offset,
1802 .endian = di.endian,
1803 };
1804
1805 const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section);
1806 if (fde_entry_header.type != .fde) return error.MissingFDE;
1807
1808 const cie_offset = fde_entry_header.type.fde;
1809 try fbr.seekTo(cie_offset);
1810
1811 fbr.endian = native_endian;
1812 const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section);
1813 if (cie_entry_header.type != .cie) return Dwarf.bad();
1814
1815 cie = try Dwarf.CommonInformationEntry.parse(
1816 cie_entry_header.entry_bytes,
1817 0,
1818 true,
1819 cie_entry_header.format,
1820 dwarf_section,
1821 cie_entry_header.length_offset,
1822 @sizeOf(usize),
1823 native_endian,
1824 );
1825
1826 fde = try Dwarf.FrameDescriptionEntry.parse(
1827 fde_entry_header.entry_bytes,
1828 0,
1829 true,
1830 cie,
1831 @sizeOf(usize),
1832 native_endian,
1833 );
1834 } else if (di.eh_frame_hdr) |header| {
1835 const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null;
1836 try header.findEntry(
1837 ma,
1838 eh_frame_len,
1839 @intFromPtr(di.section(.eh_frame_hdr).?.ptr),
1840 context.pc,
1841 &cie,
1842 &fde,
1843 );
1844 } else {
1845 const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct {
1846 pub fn compareFn(_: void, pc: usize, mid_item: Dwarf.FrameDescriptionEntry) std.math.Order {
1847 if (pc < mid_item.pc_begin) return .lt;
1848
1849 const range_end = mid_item.pc_begin + mid_item.pc_range;
1850 if (pc < range_end) return .eq;
1851
1852 return .gt;
1853 }
1854 }.compareFn);
1855
1856 fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE;
1857 cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;
1858 }
1859
1860 var expression_context: Dwarf.expression.Context = .{
1861 .format = cie.format,
1862 .memory_accessor = ma,
1863 .compile_unit = di.findCompileUnit(fde.pc_begin) catch null,
1864 .thread_context = context.thread_context,
1865 .reg_context = context.reg_context,
1866 .cfa = context.cfa,
1867 };
1868
1869 context.vm.reset();
1870 context.reg_context.eh_frame = cie.version != 4;
1871 context.reg_context.is_macho = di.is_macho;
1872
1873 const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde);
1874 context.cfa = switch (row.cfa.rule) {
1875 .val_offset => |offset| blk: {
1876 const register = row.cfa.register orelse return error.InvalidCFARule;
1877 const value = mem.readInt(usize, (try regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian);
1878 break :blk try applyOffset(value, offset);
1879 },
1880 .expression => |expr| blk: {
1881 context.stack_machine.reset();
1882 const value = try context.stack_machine.run(
1883 expr,
1884 context.allocator,
1885 expression_context,
1886 context.cfa,
1887 );
1888
1889 if (value) |v| {
1890 if (v != .generic) return error.InvalidExpressionValue;
1891 break :blk v.generic;
1892 } else return error.NoExpressionValue;
1893 },
1894 else => return error.InvalidCFARule,
1895 };
1896
1897 if (ma.load(usize, context.cfa.?) == null) return error.InvalidCFA;
1898 expression_context.cfa = context.cfa;
1899
1900 // Buffering the modifications is done because copying the thread context is not portable,
1901 // some implementations (ie. darwin) use internal pointers to the mcontext.
1902 var arena = std.heap.ArenaAllocator.init(context.allocator);
1903 defer arena.deinit();
1904 const update_allocator = arena.allocator();
1905
1906 const RegisterUpdate = struct {
1907 // Backed by thread_context
1908 dest: []u8,
1909 // Backed by arena
1910 src: []const u8,
1911 prev: ?*@This(),
1912 };
1913
1914 var update_tail: ?*RegisterUpdate = null;
1915 var has_return_address = true;
1916 for (context.vm.rowColumns(row)) |column| {
1917 if (column.register) |register| {
1918 if (register == cie.return_address_register) {
1919 has_return_address = column.rule != .undefined;
1920 }
1921
1922 const dest = try regBytes(context.thread_context, register, context.reg_context);
1923 const src = try update_allocator.alloc(u8, dest.len);
1924
1925 const prev = update_tail;
1926 update_tail = try update_allocator.create(RegisterUpdate);
1927 update_tail.?.* = .{
1928 .dest = dest,
1929 .src = src,
1930 .prev = prev,
1931 };
1932
1933 try column.resolveValue(
1934 context,
1935 expression_context,
1936 ma,
1937 src,
1938 );
1939 }
1940 }
1941
1942 // On all implemented architectures, the CFA is defined as being the previous frame's SP
1943 (try regValueNative(context.thread_context, spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?;
1944
1945 while (update_tail) |tail| {
1946 @memcpy(tail.dest, tail.src);
1947 update_tail = tail.prev;
1948 }
1949
1950 if (has_return_address) {
1951 context.pc = stripInstructionPtrAuthCode(mem.readInt(usize, (try regBytes(
1952 context.thread_context,
1953 cie.return_address_register,
1954 context.reg_context,
1955 ))[0..@sizeOf(usize)], native_endian));
1956 } else {
1957 context.pc = 0;
1958 }
1959
1960 (try regValueNative(context.thread_context, ip_reg_num, context.reg_context)).* = context.pc;
1961
1962 // The call instruction will have pushed the address of the instruction that follows the call as the return address.
1963 // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in
1964 // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up
1965 // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this,
1966 // we subtract one so that the next lookup is guaranteed to land inside the
1967 //
1968 // The exception to this rule is signal frames, where we return execution would be returned to the instruction
1969 // that triggered the handler.
1970 const return_address = context.pc;
1971 if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1;
1972
1973 return return_address;
1974}
1975
1976fn fpRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
1977 return Dwarf.abi.fpRegNum(native_arch, reg_context);
1978}
1979
1980fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
1981 return Dwarf.abi.spRegNum(native_arch, reg_context);
1982}
1983
1984const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
1985
1986/// Tells whether unwinding for the host is implemented.
1987pub const supports_unwinding = supportsUnwinding(builtin.target);
1988
1989comptime {
1990 if (supports_unwinding) assert(Dwarf.abi.supportsUnwinding(builtin.target));
1991}
1992
1993/// Tells whether unwinding for this target is *implemented* here in the Zig
1994/// standard library.
1995///
1996/// See also `Dwarf.abi.supportsUnwinding` which tells whether Dwarf supports
1997/// unwinding on that target *in theory*.
1998pub fn supportsUnwinding(target: std.Target) bool {
1999 return switch (target.cpu.arch) {
2000 .x86 => switch (target.os.tag) {
2001 .linux, .netbsd, .solaris, .illumos => true,
2002 else => false,
2003 },
2004 .x86_64 => switch (target.os.tag) {
2005 .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true,
2006 else => false,
2007 },
2008 .arm => switch (target.os.tag) {
2009 .linux => true,
2010 else => false,
2011 },
2012 .aarch64 => switch (target.os.tag) {
2013 .linux, .netbsd, .freebsd, .macos, .ios => true,
2014 else => false,
2015 },
2016 // Unwinding is possible on other targets but this implementation does
2017 // not support them...yet!
2018 else => false,
2019 };
2020}
2021
2022fn unwindFrameMachODwarf(
2023 context: *UnwindContext,
2024 ma: *std.debug.MemoryAccessor,
2025 eh_frame: []const u8,
2026 fde_offset: usize,
2027) !usize {
2028 var di: Dwarf = .{
2029 .endian = native_endian,
2030 .is_macho = true,
2031 };
2032 defer di.deinit(context.allocator);
2033
2034 di.sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{
2035 .data = eh_frame,
2036 .owned = false,
2037 };
2038
2039 return unwindFrameDwarf(&di, context, ma, fde_offset);
2040}
2041
2042/// This is a virtual machine that runs DWARF call frame instructions.
2043pub const VirtualMachine = struct {
2044 /// See section 6.4.1 of the DWARF5 specification for details on each
2045 const RegisterRule = union(enum) {
2046 // The spec says that the default rule for each column is the undefined rule.
2047 // However, it also allows ABI / compiler authors to specify alternate defaults, so
2048 // there is a distinction made here.
2049 default: void,
2050 undefined: void,
2051 same_value: void,
2052 // offset(N)
2053 offset: i64,
2054 // val_offset(N)
2055 val_offset: i64,
2056 // register(R)
2057 register: u8,
2058 // expression(E)
2059 expression: []const u8,
2060 // val_expression(E)
2061 val_expression: []const u8,
2062 // Augmenter-defined rule
2063 architectural: void,
2064 };
2065
2066 /// Each row contains unwinding rules for a set of registers.
2067 pub const Row = struct {
2068 /// Offset from `FrameDescriptionEntry.pc_begin`
2069 offset: u64 = 0,
2070 /// Special-case column that defines the CFA (Canonical Frame Address) rule.
2071 /// The register field of this column defines the register that CFA is derived from.
2072 cfa: Column = .{},
2073 /// The register fields in these columns define the register the rule applies to.
2074 columns: ColumnRange = .{},
2075 /// Indicates that the next write to any column in this row needs to copy
2076 /// the backing column storage first, as it may be referenced by previous rows.
2077 copy_on_write: bool = false,
2078 };
2079
2080 pub const Column = struct {
2081 register: ?u8 = null,
2082 rule: RegisterRule = .{ .default = {} },
2083
2084 /// Resolves the register rule and places the result into `out` (see regBytes)
2085 pub fn resolveValue(
2086 self: Column,
2087 context: *SelfInfo.UnwindContext,
2088 expression_context: std.debug.Dwarf.expression.Context,
2089 ma: *std.debug.MemoryAccessor,
2090 out: []u8,
2091 ) !void {
2092 switch (self.rule) {
2093 .default => {
2094 const register = self.register orelse return error.InvalidRegister;
2095 try getRegDefaultValue(register, context, out);
2096 },
2097 .undefined => {
2098 @memset(out, undefined);
2099 },
2100 .same_value => {
2101 // TODO: This copy could be eliminated if callers always copy the state then call this function to update it
2102 const register = self.register orelse return error.InvalidRegister;
2103 const src = try regBytes(context.thread_context, register, context.reg_context);
2104 if (src.len != out.len) return error.RegisterSizeMismatch;
2105 @memcpy(out, src);
2106 },
2107 .offset => |offset| {
2108 if (context.cfa) |cfa| {
2109 const addr = try applyOffset(cfa, offset);
2110 if (ma.load(usize, addr) == null) return error.InvalidAddress;
2111 const ptr: *const usize = @ptrFromInt(addr);
2112 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
2113 } else return error.InvalidCFA;
2114 },
2115 .val_offset => |offset| {
2116 if (context.cfa) |cfa| {
2117 mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian);
2118 } else return error.InvalidCFA;
2119 },
2120 .register => |register| {
2121 const src = try regBytes(context.thread_context, register, context.reg_context);
2122 if (src.len != out.len) return error.RegisterSizeMismatch;
2123 @memcpy(out, try regBytes(context.thread_context, register, context.reg_context));
2124 },
2125 .expression => |expression| {
2126 context.stack_machine.reset();
2127 const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?);
2128 const addr = if (value) |v| blk: {
2129 if (v != .generic) return error.InvalidExpressionValue;
2130 break :blk v.generic;
2131 } else return error.NoExpressionValue;
2132
2133 if (ma.load(usize, addr) == null) return error.InvalidExpressionAddress;
2134 const ptr: *usize = @ptrFromInt(addr);
2135 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
2136 },
2137 .val_expression => |expression| {
2138 context.stack_machine.reset();
2139 const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?);
2140 if (value) |v| {
2141 if (v != .generic) return error.InvalidExpressionValue;
2142 mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian);
2143 } else return error.NoExpressionValue;
2144 },
2145 .architectural => return error.UnimplementedRegisterRule,
2146 }
2147 }
2148 };
2149
2150 const ColumnRange = struct {
2151 /// Index into `columns` of the first column in this row.
2152 start: usize = undefined,
2153 len: u8 = 0,
2154 };
2155
2156 columns: std.ArrayListUnmanaged(Column) = .{},
2157 stack: std.ArrayListUnmanaged(ColumnRange) = .{},
2158 current_row: Row = .{},
2159
2160 /// The result of executing the CIE's initial_instructions
2161 cie_row: ?Row = null,
2162
2163 pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void {
2164 self.stack.deinit(allocator);
2165 self.columns.deinit(allocator);
2166 self.* = undefined;
2167 }
2168
2169 pub fn reset(self: *VirtualMachine) void {
2170 self.stack.clearRetainingCapacity();
2171 self.columns.clearRetainingCapacity();
2172 self.current_row = .{};
2173 self.cie_row = null;
2174 }
2175
2176 /// Return a slice backed by the row's non-CFA columns
2177 pub fn rowColumns(self: VirtualMachine, row: Row) []Column {
2178 if (row.columns.len == 0) return &.{};
2179 return self.columns.items[row.columns.start..][0..row.columns.len];
2180 }
2181
2182 /// Either retrieves or adds a column for `register` (non-CFA) in the current row.
2183 fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column {
2184 for (self.rowColumns(self.current_row)) |*c| {
2185 if (c.register == register) return c;
2186 }
2187
2188 if (self.current_row.columns.len == 0) {
2189 self.current_row.columns.start = self.columns.items.len;
2190 }
2191 self.current_row.columns.len += 1;
2192
2193 const column = try self.columns.addOne(allocator);
2194 column.* = .{
2195 .register = register,
2196 };
2197
2198 return column;
2199 }
2200
2201 /// Runs the CIE instructions, then the FDE instructions. Execution halts
2202 /// once the row that corresponds to `pc` is known, and the row is returned.
2203 pub fn runTo(
2204 self: *VirtualMachine,
2205 allocator: std.mem.Allocator,
2206 pc: u64,
2207 cie: std.debug.Dwarf.CommonInformationEntry,
2208 fde: std.debug.Dwarf.FrameDescriptionEntry,
2209 addr_size_bytes: u8,
2210 endian: std.builtin.Endian,
2211 ) !Row {
2212 assert(self.cie_row == null);
2213 if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange;
2214
2215 var prev_row: Row = self.current_row;
2216
2217 var cie_stream = std.io.fixedBufferStream(cie.initial_instructions);
2218 var fde_stream = std.io.fixedBufferStream(fde.instructions);
2219 var streams = [_]*std.io.FixedBufferStream([]const u8){
2220 &cie_stream,
2221 &fde_stream,
2222 };
2223
2224 for (&streams, 0..) |stream, i| {
2225 while (stream.pos < stream.buffer.len) {
2226 const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian);
2227 prev_row = try self.step(allocator, cie, i == 0, instruction);
2228 if (pc < fde.pc_begin + self.current_row.offset) return prev_row;
2229 }
2230 }
2231
2232 return self.current_row;
2233 }
2234
2235 pub fn runToNative(
2236 self: *VirtualMachine,
2237 allocator: std.mem.Allocator,
2238 pc: u64,
2239 cie: std.debug.Dwarf.CommonInformationEntry,
2240 fde: std.debug.Dwarf.FrameDescriptionEntry,
2241 ) !Row {
2242 return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), native_endian);
2243 }
2244
2245 fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void {
2246 if (!self.current_row.copy_on_write) return;
2247
2248 const new_start = self.columns.items.len;
2249 if (self.current_row.columns.len > 0) {
2250 try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len);
2251 self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row));
2252 self.current_row.columns.start = new_start;
2253 }
2254 }
2255
2256 /// Executes a single instruction.
2257 /// If this instruction is from the CIE, `is_initial` should be set.
2258 /// Returns the value of `current_row` before executing this instruction.
2259 pub fn step(
2260 self: *VirtualMachine,
2261 allocator: std.mem.Allocator,
2262 cie: std.debug.Dwarf.CommonInformationEntry,
2263 is_initial: bool,
2264 instruction: Dwarf.call_frame.Instruction,
2265 ) !Row {
2266 // CIE instructions must be run before FDE instructions
2267 assert(!is_initial or self.cie_row == null);
2268 if (!is_initial and self.cie_row == null) {
2269 self.cie_row = self.current_row;
2270 self.current_row.copy_on_write = true;
2271 }
2272
2273 const prev_row = self.current_row;
2274 switch (instruction) {
2275 .set_loc => |i| {
2276 if (i.address <= self.current_row.offset) return error.InvalidOperation;
2277 // TODO: Check cie.segment_selector_size != 0 for DWARFV4
2278 self.current_row.offset = i.address;
2279 },
2280 inline .advance_loc,
2281 .advance_loc1,
2282 .advance_loc2,
2283 .advance_loc4,
2284 => |i| {
2285 self.current_row.offset += i.delta * cie.code_alignment_factor;
2286 self.current_row.copy_on_write = true;
2287 },
2288 inline .offset,
2289 .offset_extended,
2290 .offset_extended_sf,
2291 => |i| {
2292 try self.resolveCopyOnWrite(allocator);
2293 const column = try self.getOrAddColumn(allocator, i.register);
2294 column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor };
2295 },
2296 inline .restore,
2297 .restore_extended,
2298 => |i| {
2299 try self.resolveCopyOnWrite(allocator);
2300 if (self.cie_row) |cie_row| {
2301 const column = try self.getOrAddColumn(allocator, i.register);
2302 column.rule = for (self.rowColumns(cie_row)) |cie_column| {
2303 if (cie_column.register == i.register) break cie_column.rule;
2304 } else .{ .default = {} };
2305 } else return error.InvalidOperation;
2306 },
2307 .nop => {},
2308 .undefined => |i| {
2309 try self.resolveCopyOnWrite(allocator);
2310 const column = try self.getOrAddColumn(allocator, i.register);
2311 column.rule = .{ .undefined = {} };
2312 },
2313 .same_value => |i| {
2314 try self.resolveCopyOnWrite(allocator);
2315 const column = try self.getOrAddColumn(allocator, i.register);
2316 column.rule = .{ .same_value = {} };
2317 },
2318 .register => |i| {
2319 try self.resolveCopyOnWrite(allocator);
2320 const column = try self.getOrAddColumn(allocator, i.register);
2321 column.rule = .{ .register = i.target_register };
2322 },
2323 .remember_state => {
2324 try self.stack.append(allocator, self.current_row.columns);
2325 self.current_row.copy_on_write = true;
2326 },
2327 .restore_state => {
2328 const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation;
2329 self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len);
2330 try self.columns.ensureUnusedCapacity(allocator, restored_columns.len);
2331
2332 self.current_row.columns.start = self.columns.items.len;
2333 self.current_row.columns.len = restored_columns.len;
2334 self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]);
2335 },
2336 .def_cfa => |i| {
2337 try self.resolveCopyOnWrite(allocator);
2338 self.current_row.cfa = .{
2339 .register = i.register,
2340 .rule = .{ .val_offset = @intCast(i.offset) },
2341 };
2342 },
2343 .def_cfa_sf => |i| {
2344 try self.resolveCopyOnWrite(allocator);
2345 self.current_row.cfa = .{
2346 .register = i.register,
2347 .rule = .{ .val_offset = i.offset * cie.data_alignment_factor },
2348 };
2349 },
2350 .def_cfa_register => |i| {
2351 try self.resolveCopyOnWrite(allocator);
2352 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
2353 self.current_row.cfa.register = i.register;
2354 },
2355 .def_cfa_offset => |i| {
2356 try self.resolveCopyOnWrite(allocator);
2357 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
2358 self.current_row.cfa.rule = .{
2359 .val_offset = @intCast(i.offset),
2360 };
2361 },
2362 .def_cfa_offset_sf => |i| {
2363 try self.resolveCopyOnWrite(allocator);
2364 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
2365 self.current_row.cfa.rule = .{
2366 .val_offset = i.offset * cie.data_alignment_factor,
2367 };
2368 },
2369 .def_cfa_expression => |i| {
2370 try self.resolveCopyOnWrite(allocator);
2371 self.current_row.cfa.register = undefined;
2372 self.current_row.cfa.rule = .{
2373 .expression = i.block,
2374 };
2375 },
2376 .expression => |i| {
2377 try self.resolveCopyOnWrite(allocator);
2378 const column = try self.getOrAddColumn(allocator, i.register);
2379 column.rule = .{
2380 .expression = i.block,
2381 };
2382 },
2383 .val_offset => |i| {
2384 try self.resolveCopyOnWrite(allocator);
2385 const column = try self.getOrAddColumn(allocator, i.register);
2386 column.rule = .{
2387 .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor,
2388 };
2389 },
2390 .val_offset_sf => |i| {
2391 try self.resolveCopyOnWrite(allocator);
2392 const column = try self.getOrAddColumn(allocator, i.register);
2393 column.rule = .{
2394 .val_offset = i.offset * cie.data_alignment_factor,
2395 };
2396 },
2397 .val_expression => |i| {
2398 try self.resolveCopyOnWrite(allocator);
2399 const column = try self.getOrAddColumn(allocator, i.register);
2400 column.rule = .{
2401 .val_expression = i.block,
2402 };
2403 },
2404 }
2405
2406 return prev_row;
2407 }
2408};
2409
2410/// Returns the ABI-defined default value this register has in the unwinding table
2411/// before running any of the CIE instructions. The DWARF spec defines these as having
2412/// the .undefined rule by default, but allows ABI authors to override that.
2413fn getRegDefaultValue(reg_number: u8, context: *UnwindContext, out: []u8) !void {
2414 switch (builtin.cpu.arch) {
2415 .aarch64 => {
2416 // Callee-saved registers are initialized as if they had the .same_value rule
2417 if (reg_number >= 19 and reg_number <= 28) {
2418 const src = try regBytes(context.thread_context, reg_number, context.reg_context);
2419 if (src.len != out.len) return error.RegisterSizeMismatch;
2420 @memcpy(out, src);
2421 return;
2422 }
2423 },
2424 else => {},
2425 }
2426
2427 @memset(out, undefined);
2428}
2429
2430/// Since register rules are applied (usually) during a panic,
2431/// checked addition / subtraction is used so that we can return
2432/// an error and fall back to FP-based unwinding.
2433fn applyOffset(base: usize, offset: i64) !usize {
2434 return if (offset >= 0)
2435 try std.math.add(usize, base, @as(usize, @intCast(offset)))
2436 else
2437 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
2438}
lib/std/pdb.zig+11-596
......@@ -1,3 +1,12 @@
1//! Program Data Base debugging information format.
2//!
3//! This namespace contains unopinionated types and data definitions only. For
4//! an implementation of parsing and caching PDB information, see
5//! `std.debug.Pdb`.
6//!
7//! Most of this is based on information gathered from LLVM source code,
8//! documentation and/or contributors.
9
110const std = @import("std.zig");
211const io = std.io;
312const math = std.math;
......@@ -9,10 +18,7 @@ const debug = std.debug;
918
1019const ArrayList = std.ArrayList;
1120
12// Note: most of this is based on information gathered from LLVM source code,
13// documentation and/or contributors.
14
15// https://llvm.org/docs/PDB/DbiStream.html#stream-header
21/// https://llvm.org/docs/PDB/DbiStream.html#stream-header
1622pub const DbiStreamHeader = extern struct {
1723 VersionSignature: i32,
1824 VersionHeader: u32,
......@@ -415,10 +421,8 @@ pub const ColumnNumberEntry = extern struct {
415421pub const FileChecksumEntryHeader = extern struct {
416422 /// Byte offset of filename in global string table.
417423 FileNameOffset: u32,
418
419424 /// Number of bytes of checksum.
420425 ChecksumSize: u8,
421
422426 /// FileChecksumKind
423427 ChecksumKind: u8,
424428};
......@@ -451,525 +455,15 @@ pub const DebugSubsectionHeader = extern struct {
451455 Length: u32,
452456};
453457
454pub const PDBStringTableHeader = extern struct {
458pub const StringTableHeader = extern struct {
455459 /// PDBStringTableSignature
456460 Signature: u32,
457
458461 /// 1 or 2
459462 HashVersion: u32,
460
461463 /// Number of bytes of names buffer.
462464 ByteSize: u32,
463465};
464466
465fn readSparseBitVector(stream: anytype, allocator: mem.Allocator) ![]u32 {
466 const num_words = try stream.readInt(u32, .little);
467 var list = ArrayList(u32).init(allocator);
468 errdefer list.deinit();
469 var word_i: u32 = 0;
470 while (word_i != num_words) : (word_i += 1) {
471 const word = try stream.readInt(u32, .little);
472 var bit_i: u5 = 0;
473 while (true) : (bit_i += 1) {
474 if (word & (@as(u32, 1) << bit_i) != 0) {
475 try list.append(word_i * 32 + bit_i);
476 }
477 if (bit_i == std.math.maxInt(u5)) break;
478 }
479 }
480 return try list.toOwnedSlice();
481}
482
483pub const Pdb = struct {
484 in_file: File,
485 msf: Msf,
486 allocator: mem.Allocator,
487 string_table: ?*MsfStream,
488 dbi: ?*MsfStream,
489 modules: []Module,
490 sect_contribs: []SectionContribEntry,
491 guid: [16]u8,
492 age: u32,
493
494 pub const Module = struct {
495 mod_info: ModInfo,
496 module_name: []u8,
497 obj_file_name: []u8,
498 // The fields below are filled on demand.
499 populated: bool,
500 symbols: []u8,
501 subsect_info: []u8,
502 checksum_offset: ?usize,
503
504 pub fn deinit(self: *Module, allocator: mem.Allocator) void {
505 allocator.free(self.module_name);
506 allocator.free(self.obj_file_name);
507 if (self.populated) {
508 allocator.free(self.symbols);
509 allocator.free(self.subsect_info);
510 }
511 }
512 };
513
514 pub fn init(allocator: mem.Allocator, path: []const u8) !Pdb {
515 const file = try fs.cwd().openFile(path, .{});
516 errdefer file.close();
517
518 return Pdb{
519 .in_file = file,
520 .allocator = allocator,
521 .string_table = null,
522 .dbi = null,
523 .msf = try Msf.init(allocator, file),
524 .modules = &[_]Module{},
525 .sect_contribs = &[_]SectionContribEntry{},
526 .guid = undefined,
527 .age = undefined,
528 };
529 }
530
531 pub fn deinit(self: *Pdb) void {
532 self.in_file.close();
533 self.msf.deinit(self.allocator);
534 for (self.modules) |*module| {
535 module.deinit(self.allocator);
536 }
537 self.allocator.free(self.modules);
538 self.allocator.free(self.sect_contribs);
539 }
540
541 pub fn parseDbiStream(self: *Pdb) !void {
542 var stream = self.getStream(StreamType.Dbi) orelse
543 return error.InvalidDebugInfo;
544 const reader = stream.reader();
545
546 const header = try reader.readStruct(DbiStreamHeader);
547 if (header.VersionHeader != 19990903) // V70, only value observed by LLVM team
548 return error.UnknownPDBVersion;
549 // if (header.Age != age)
550 // return error.UnmatchingPDB;
551
552 const mod_info_size = header.ModInfoSize;
553 const section_contrib_size = header.SectionContributionSize;
554
555 var modules = ArrayList(Module).init(self.allocator);
556 errdefer modules.deinit();
557
558 // Module Info Substream
559 var mod_info_offset: usize = 0;
560 while (mod_info_offset != mod_info_size) {
561 const mod_info = try reader.readStruct(ModInfo);
562 var this_record_len: usize = @sizeOf(ModInfo);
563
564 const module_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024);
565 errdefer self.allocator.free(module_name);
566 this_record_len += module_name.len + 1;
567
568 const obj_file_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024);
569 errdefer self.allocator.free(obj_file_name);
570 this_record_len += obj_file_name.len + 1;
571
572 if (this_record_len % 4 != 0) {
573 const round_to_next_4 = (this_record_len | 0x3) + 1;
574 const march_forward_bytes = round_to_next_4 - this_record_len;
575 try stream.seekBy(@as(isize, @intCast(march_forward_bytes)));
576 this_record_len += march_forward_bytes;
577 }
578
579 try modules.append(Module{
580 .mod_info = mod_info,
581 .module_name = module_name,
582 .obj_file_name = obj_file_name,
583
584 .populated = false,
585 .symbols = undefined,
586 .subsect_info = undefined,
587 .checksum_offset = null,
588 });
589
590 mod_info_offset += this_record_len;
591 if (mod_info_offset > mod_info_size)
592 return error.InvalidDebugInfo;
593 }
594
595 // Section Contribution Substream
596 var sect_contribs = ArrayList(SectionContribEntry).init(self.allocator);
597 errdefer sect_contribs.deinit();
598
599 var sect_cont_offset: usize = 0;
600 if (section_contrib_size != 0) {
601 const version = reader.readEnum(SectionContrSubstreamVersion, .little) catch |err| switch (err) {
602 error.InvalidValue => return error.InvalidDebugInfo,
603 else => |e| return e,
604 };
605 _ = version;
606 sect_cont_offset += @sizeOf(u32);
607 }
608 while (sect_cont_offset != section_contrib_size) {
609 const entry = try sect_contribs.addOne();
610 entry.* = try reader.readStruct(SectionContribEntry);
611 sect_cont_offset += @sizeOf(SectionContribEntry);
612
613 if (sect_cont_offset > section_contrib_size)
614 return error.InvalidDebugInfo;
615 }
616
617 self.modules = try modules.toOwnedSlice();
618 self.sect_contribs = try sect_contribs.toOwnedSlice();
619 }
620
621 pub fn parseInfoStream(self: *Pdb) !void {
622 var stream = self.getStream(StreamType.Pdb) orelse
623 return error.InvalidDebugInfo;
624 const reader = stream.reader();
625
626 // Parse the InfoStreamHeader.
627 const version = try reader.readInt(u32, .little);
628 const signature = try reader.readInt(u32, .little);
629 _ = signature;
630 const age = try reader.readInt(u32, .little);
631 const guid = try reader.readBytesNoEof(16);
632
633 if (version != 20000404) // VC70, only value observed by LLVM team
634 return error.UnknownPDBVersion;
635
636 self.guid = guid;
637 self.age = age;
638
639 // Find the string table.
640 const string_table_index = str_tab_index: {
641 const name_bytes_len = try reader.readInt(u32, .little);
642 const name_bytes = try self.allocator.alloc(u8, name_bytes_len);
643 defer self.allocator.free(name_bytes);
644 try reader.readNoEof(name_bytes);
645
646 const HashTableHeader = extern struct {
647 Size: u32,
648 Capacity: u32,
649
650 fn maxLoad(cap: u32) u32 {
651 return cap * 2 / 3 + 1;
652 }
653 };
654 const hash_tbl_hdr = try reader.readStruct(HashTableHeader);
655 if (hash_tbl_hdr.Capacity == 0)
656 return error.InvalidDebugInfo;
657
658 if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity))
659 return error.InvalidDebugInfo;
660
661 const present = try readSparseBitVector(&reader, self.allocator);
662 defer self.allocator.free(present);
663 if (present.len != hash_tbl_hdr.Size)
664 return error.InvalidDebugInfo;
665 const deleted = try readSparseBitVector(&reader, self.allocator);
666 defer self.allocator.free(deleted);
667
668 for (present) |_| {
669 const name_offset = try reader.readInt(u32, .little);
670 const name_index = try reader.readInt(u32, .little);
671 if (name_offset > name_bytes.len)
672 return error.InvalidDebugInfo;
673 const name = mem.sliceTo(name_bytes[name_offset..], 0);
674 if (mem.eql(u8, name, "/names")) {
675 break :str_tab_index name_index;
676 }
677 }
678 return error.MissingDebugInfo;
679 };
680
681 self.string_table = self.getStreamById(string_table_index) orelse
682 return error.MissingDebugInfo;
683 }
684
685 pub fn getSymbolName(self: *Pdb, module: *Module, address: u64) ?[]const u8 {
686 _ = self;
687 std.debug.assert(module.populated);
688
689 var symbol_i: usize = 0;
690 while (symbol_i != module.symbols.len) {
691 const prefix = @as(*align(1) RecordPrefix, @ptrCast(&module.symbols[symbol_i]));
692 if (prefix.RecordLen < 2)
693 return null;
694 switch (prefix.RecordKind) {
695 .S_LPROC32, .S_GPROC32 => {
696 const proc_sym = @as(*align(1) ProcSym, @ptrCast(&module.symbols[symbol_i + @sizeOf(RecordPrefix)]));
697 if (address >= proc_sym.CodeOffset and address < proc_sym.CodeOffset + proc_sym.CodeSize) {
698 return mem.sliceTo(@as([*:0]u8, @ptrCast(&proc_sym.Name[0])), 0);
699 }
700 },
701 else => {},
702 }
703 symbol_i += prefix.RecordLen + @sizeOf(u16);
704 }
705
706 return null;
707 }
708
709 pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !debug.LineInfo {
710 std.debug.assert(module.populated);
711 const subsect_info = module.subsect_info;
712
713 var sect_offset: usize = 0;
714 var skip_len: usize = undefined;
715 const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo;
716 while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {
717 const subsect_hdr = @as(*align(1) DebugSubsectionHeader, @ptrCast(&subsect_info[sect_offset]));
718 skip_len = subsect_hdr.Length;
719 sect_offset += @sizeOf(DebugSubsectionHeader);
720
721 switch (subsect_hdr.Kind) {
722 .Lines => {
723 var line_index = sect_offset;
724
725 const line_hdr = @as(*align(1) LineFragmentHeader, @ptrCast(&subsect_info[line_index]));
726 if (line_hdr.RelocSegment == 0)
727 return error.MissingDebugInfo;
728 line_index += @sizeOf(LineFragmentHeader);
729 const frag_vaddr_start = line_hdr.RelocOffset;
730 const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize;
731
732 if (address >= frag_vaddr_start and address < frag_vaddr_end) {
733 // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records)
734 // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in,
735 // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection.
736 const subsection_end_index = sect_offset + subsect_hdr.Length;
737
738 while (line_index < subsection_end_index) {
739 const block_hdr = @as(*align(1) LineBlockFragmentHeader, @ptrCast(&subsect_info[line_index]));
740 line_index += @sizeOf(LineBlockFragmentHeader);
741 const start_line_index = line_index;
742
743 const has_column = line_hdr.Flags.LF_HaveColumns;
744
745 // All line entries are stored inside their line block by ascending start address.
746 // Heuristic: we want to find the last line entry
747 // that has a vaddr_start <= address.
748 // This is done with a simple linear search.
749 var line_i: u32 = 0;
750 while (line_i < block_hdr.NumLines) : (line_i += 1) {
751 const line_num_entry = @as(*align(1) LineNumberEntry, @ptrCast(&subsect_info[line_index]));
752 line_index += @sizeOf(LineNumberEntry);
753
754 const vaddr_start = frag_vaddr_start + line_num_entry.Offset;
755 if (address < vaddr_start) {
756 break;
757 }
758 }
759
760 // line_i == 0 would mean that no matching LineNumberEntry was found.
761 if (line_i > 0) {
762 const subsect_index = checksum_offset + block_hdr.NameIndex;
763 const chksum_hdr = @as(*align(1) FileChecksumEntryHeader, @ptrCast(&module.subsect_info[subsect_index]));
764 const strtab_offset = @sizeOf(PDBStringTableHeader) + chksum_hdr.FileNameOffset;
765 try self.string_table.?.seekTo(strtab_offset);
766 const source_file_name = try self.string_table.?.reader().readUntilDelimiterAlloc(self.allocator, 0, 1024);
767
768 const line_entry_idx = line_i - 1;
769
770 const column = if (has_column) blk: {
771 const start_col_index = start_line_index + @sizeOf(LineNumberEntry) * block_hdr.NumLines;
772 const col_index = start_col_index + @sizeOf(ColumnNumberEntry) * line_entry_idx;
773 const col_num_entry = @as(*align(1) ColumnNumberEntry, @ptrCast(&subsect_info[col_index]));
774 break :blk col_num_entry.StartColumn;
775 } else 0;
776
777 const found_line_index = start_line_index + line_entry_idx * @sizeOf(LineNumberEntry);
778 const line_num_entry: *align(1) LineNumberEntry = @ptrCast(&subsect_info[found_line_index]);
779 const flags: *align(1) LineNumberEntry.Flags = @ptrCast(&line_num_entry.Flags);
780
781 return debug.LineInfo{
782 .file_name = source_file_name,
783 .line = flags.Start,
784 .column = column,
785 };
786 }
787 }
788
789 // Checking that we are not reading garbage after the (possibly) multiple block fragments.
790 if (line_index != subsection_end_index) {
791 return error.InvalidDebugInfo;
792 }
793 }
794 },
795 else => {},
796 }
797
798 if (sect_offset > subsect_info.len)
799 return error.InvalidDebugInfo;
800 }
801
802 return error.MissingDebugInfo;
803 }
804
805 pub fn getModule(self: *Pdb, index: usize) !?*Module {
806 if (index >= self.modules.len)
807 return null;
808
809 const mod = &self.modules[index];
810 if (mod.populated)
811 return mod;
812
813 // At most one can be non-zero.
814 if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0)
815 return error.InvalidDebugInfo;
816 if (mod.mod_info.C13ByteSize == 0)
817 return error.InvalidDebugInfo;
818
819 const stream = self.getStreamById(mod.mod_info.ModuleSymStream) orelse
820 return error.MissingDebugInfo;
821 const reader = stream.reader();
822
823 const signature = try reader.readInt(u32, .little);
824 if (signature != 4)
825 return error.InvalidDebugInfo;
826
827 mod.symbols = try self.allocator.alloc(u8, mod.mod_info.SymByteSize - 4);
828 errdefer self.allocator.free(mod.symbols);
829 try reader.readNoEof(mod.symbols);
830
831 mod.subsect_info = try self.allocator.alloc(u8, mod.mod_info.C13ByteSize);
832 errdefer self.allocator.free(mod.subsect_info);
833 try reader.readNoEof(mod.subsect_info);
834
835 var sect_offset: usize = 0;
836 var skip_len: usize = undefined;
837 while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) {
838 const subsect_hdr = @as(*align(1) DebugSubsectionHeader, @ptrCast(&mod.subsect_info[sect_offset]));
839 skip_len = subsect_hdr.Length;
840 sect_offset += @sizeOf(DebugSubsectionHeader);
841
842 switch (subsect_hdr.Kind) {
843 .FileChecksums => {
844 mod.checksum_offset = sect_offset;
845 break;
846 },
847 else => {},
848 }
849
850 if (sect_offset > mod.subsect_info.len)
851 return error.InvalidDebugInfo;
852 }
853
854 mod.populated = true;
855 return mod;
856 }
857
858 pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream {
859 if (id >= self.msf.streams.len)
860 return null;
861 return &self.msf.streams[id];
862 }
863
864 pub fn getStream(self: *Pdb, stream: StreamType) ?*MsfStream {
865 const id = @intFromEnum(stream);
866 return self.getStreamById(id);
867 }
868};
869
870// see https://llvm.org/docs/PDB/MsfFile.html
871const Msf = struct {
872 directory: MsfStream,
873 streams: []MsfStream,
874
875 fn init(allocator: mem.Allocator, file: File) !Msf {
876 const in = file.reader();
877
878 const superblock = try in.readStruct(SuperBlock);
879
880 // Sanity checks
881 if (!mem.eql(u8, &superblock.FileMagic, SuperBlock.file_magic))
882 return error.InvalidDebugInfo;
883 if (superblock.FreeBlockMapBlock != 1 and superblock.FreeBlockMapBlock != 2)
884 return error.InvalidDebugInfo;
885 const file_len = try file.getEndPos();
886 if (superblock.NumBlocks * superblock.BlockSize != file_len)
887 return error.InvalidDebugInfo;
888 switch (superblock.BlockSize) {
889 // llvm only supports 4096 but we can handle any of these values
890 512, 1024, 2048, 4096 => {},
891 else => return error.InvalidDebugInfo,
892 }
893
894 const dir_block_count = blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize);
895 if (dir_block_count > superblock.BlockSize / @sizeOf(u32))
896 return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment.
897
898 try file.seekTo(superblock.BlockSize * superblock.BlockMapAddr);
899 const dir_blocks = try allocator.alloc(u32, dir_block_count);
900 for (dir_blocks) |*b| {
901 b.* = try in.readInt(u32, .little);
902 }
903 var directory = MsfStream.init(
904 superblock.BlockSize,
905 file,
906 dir_blocks,
907 );
908
909 const begin = directory.pos;
910 const stream_count = try directory.reader().readInt(u32, .little);
911 const stream_sizes = try allocator.alloc(u32, stream_count);
912 defer allocator.free(stream_sizes);
913
914 // Microsoft's implementation uses @as(u32, -1) for inexistent streams.
915 // These streams are not used, but still participate in the file
916 // and must be taken into account when resolving stream indices.
917 const Nil = 0xFFFFFFFF;
918 for (stream_sizes) |*s| {
919 const size = try directory.reader().readInt(u32, .little);
920 s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.BlockSize);
921 }
922
923 const streams = try allocator.alloc(MsfStream, stream_count);
924 for (streams, 0..) |*stream, i| {
925 const size = stream_sizes[i];
926 if (size == 0) {
927 stream.* = MsfStream{
928 .blocks = &[_]u32{},
929 };
930 } else {
931 var blocks = try allocator.alloc(u32, size);
932 var j: u32 = 0;
933 while (j < size) : (j += 1) {
934 const block_id = try directory.reader().readInt(u32, .little);
935 const n = (block_id % superblock.BlockSize);
936 // 0 is for SuperBlock, 1 and 2 for FPMs.
937 if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > file_len)
938 return error.InvalidBlockIndex;
939 blocks[j] = block_id;
940 }
941
942 stream.* = MsfStream.init(
943 superblock.BlockSize,
944 file,
945 blocks,
946 );
947 }
948 }
949
950 const end = directory.pos;
951 if (end - begin != superblock.NumDirectoryBytes)
952 return error.InvalidStreamDirectory;
953
954 return Msf{
955 .directory = directory,
956 .streams = streams,
957 };
958 }
959
960 fn deinit(self: *Msf, allocator: mem.Allocator) void {
961 allocator.free(self.directory.blocks);
962 for (self.streams) |*stream| {
963 allocator.free(stream.blocks);
964 }
965 allocator.free(self.streams);
966 }
967};
968
969fn blockCountFromSize(size: u32, block_size: u32) u32 {
970 return (size + block_size - 1) / block_size;
971}
972
973467// https://llvm.org/docs/PDB/MsfFile.html#the-superblock
974468pub const SuperBlock = extern struct {
975469 /// The LLVM docs list a space between C / C++ but empirically this is not the case.
......@@ -1016,82 +510,3 @@ pub const SuperBlock = extern struct {
1016510 // implement it so we're kind of safe making this assumption for now.
1017511 BlockMapAddr: u32,
1018512};
1019
1020const MsfStream = struct {
1021 in_file: File = undefined,
1022 pos: u64 = undefined,
1023 blocks: []u32 = undefined,
1024 block_size: u32 = undefined,
1025
1026 pub const Error = @typeInfo(@typeInfo(@TypeOf(read)).Fn.return_type.?).ErrorUnion.error_set;
1027
1028 fn init(block_size: u32, file: File, blocks: []u32) MsfStream {
1029 const stream = MsfStream{
1030 .in_file = file,
1031 .pos = 0,
1032 .blocks = blocks,
1033 .block_size = block_size,
1034 };
1035
1036 return stream;
1037 }
1038
1039 fn read(self: *MsfStream, buffer: []u8) !usize {
1040 var block_id = @as(usize, @intCast(self.pos / self.block_size));
1041 if (block_id >= self.blocks.len) return 0; // End of Stream
1042 var block = self.blocks[block_id];
1043 var offset = self.pos % self.block_size;
1044
1045 try self.in_file.seekTo(block * self.block_size + offset);
1046 const in = self.in_file.reader();
1047
1048 var size: usize = 0;
1049 var rem_buffer = buffer;
1050 while (size < buffer.len) {
1051 const size_to_read = @min(self.block_size - offset, rem_buffer.len);
1052 size += try in.read(rem_buffer[0..size_to_read]);
1053 rem_buffer = buffer[size..];
1054 offset += size_to_read;
1055
1056 // If we're at the end of a block, go to the next one.
1057 if (offset == self.block_size) {
1058 offset = 0;
1059 block_id += 1;
1060 if (block_id >= self.blocks.len) break; // End of Stream
1061 block = self.blocks[block_id];
1062 try self.in_file.seekTo(block * self.block_size);
1063 }
1064 }
1065
1066 self.pos += buffer.len;
1067 return buffer.len;
1068 }
1069
1070 pub fn seekBy(self: *MsfStream, len: i64) !void {
1071 self.pos = @as(u64, @intCast(@as(i64, @intCast(self.pos)) + len));
1072 if (self.pos >= self.blocks.len * self.block_size)
1073 return error.EOF;
1074 }
1075
1076 pub fn seekTo(self: *MsfStream, len: u64) !void {
1077 self.pos = len;
1078 if (self.pos >= self.blocks.len * self.block_size)
1079 return error.EOF;
1080 }
1081
1082 fn getSize(self: *const MsfStream) u64 {
1083 return self.blocks.len * self.block_size;
1084 }
1085
1086 fn getFilePos(self: MsfStream) u64 {
1087 const block_id = self.pos / self.block_size;
1088 const block = self.blocks[block_id];
1089 const offset = self.pos % self.block_size;
1090
1091 return block * self.block_size + offset;
1092 }
1093
1094 pub fn reader(self: *MsfStream) std.io.Reader(*MsfStream, Error, read) {
1095 return .{ .context = self };
1096 }
1097};
src/crash_report.zig+1-1
......@@ -256,7 +256,7 @@ const StackContext = union(enum) {
256256 current: struct {
257257 ret_addr: ?usize,
258258 },
259 exception: *const debug.ThreadContext,
259 exception: *debug.ThreadContext,
260260 not_supported: void,
261261
262262 pub fn dumpStackTrace(ctx: @This()) void {
test/standalone/coff_dwarf/main.zig+1-1
......@@ -9,7 +9,7 @@ pub fn main() !void {
99 defer assert(gpa.deinit() == .ok);
1010 const allocator = gpa.allocator();
1111
12 var debug_info = try std.debug.openSelfDebugInfo(allocator);
12 var debug_info = try std.debug.SelfInfo.open(allocator);
1313 defer debug_info.deinit();
1414
1515 var add_addr: usize = undefined;