authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-01 16:50:39+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:49+01:00
logb750e7cf9e2a1225b20ef7fdf53df9ef97cf8065
treed4760dd9e1279db621fbf4318264f951117a2172
parentb706949736fe67e104a14ac1dcaac8b7eb1cc33f
signaturelock-open Commit is signed but in an unrecognized format.

change one million things


10 files changed, 2140 insertions(+), 2544 deletions(-)

lib/std/coff.zig+10-9
......@@ -1083,26 +1083,27 @@ pub const Coff = struct {
10831083 age: u32 = undefined,
10841084
10851085 // The lifetime of `data` must be longer than the lifetime of the returned Coff
1086 pub fn init(data: []const u8, is_loaded: bool) !Coff {
1086 pub fn init(data: []const u8, is_loaded: bool) error{ EndOfStream, MissingPEHeader }!Coff {
10871087 const pe_pointer_offset = 0x3C;
10881088 const pe_magic = "PE\x00\x00";
10891089
1090 var reader: std.Io.Reader = .fixed(data);
1091 reader.seek = pe_pointer_offset;
1092 const coff_header_offset = try reader.takeInt(u32, .little);
1093 reader.seek = coff_header_offset;
1094 const is_image = mem.eql(u8, pe_magic, try reader.takeArray(4));
1090 if (data.len < pe_pointer_offset + 4) return error.EndOfStream;
1091 const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little);
1092 if (data.len < header_offset + 4) return error.EndOfStream;
1093 const is_image = mem.eql(u8, data[header_offset..][0..4], pe_magic);
10951094
1096 var coff = @This(){
1095 const coff: Coff = .{
10971096 .data = data,
10981097 .is_image = is_image,
10991098 .is_loaded = is_loaded,
1100 .coff_header_offset = coff_header_offset,
1099 .coff_header_offset = o: {
1100 if (is_image) break :o header_offset + 4;
1101 break :o header_offset;
1102 },
11011103 };
11021104
11031105 // Do some basic validation upfront
11041106 if (is_image) {
1105 coff.coff_header_offset = coff.coff_header_offset + 4;
11061107 const coff_header = coff.getCoffHeader();
11071108 if (coff_header.size_of_optional_header == 0) return error.MissingPEHeader;
11081109 }
lib/std/debug.zig+62-71
......@@ -153,6 +153,7 @@ pub const SourceLocation = struct {
153153};
154154
155155pub const Symbol = struct {
156 // MLUGG TODO: remove the defaults and audit everywhere. also grep for '???' across std
156157 name: []const u8 = "???",
157158 compile_unit_name: []const u8 = "???",
158159 source_location: ?SourceLocation = null,
......@@ -232,15 +233,14 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
232233}
233234
234235/// TODO multithreaded awareness
235var self_debug_info: ?SelfInfo = null;
236
237pub fn getSelfDebugInfo() !*SelfInfo {
238 if (self_debug_info) |*info| {
239 return info;
240 } else {
241 self_debug_info = try SelfInfo.open(getDebugInfoAllocator());
242 return &self_debug_info.?;
243 }
236/// Marked `inline` to propagate a comptime-known error to callers.
237pub inline fn getSelfDebugInfo() !*SelfInfo {
238 if (builtin.strip_debug_info) return error.MissingDebugInfo;
239 if (!SelfInfo.target_supported) return error.UnsupportedOperatingSystem;
240 const S = struct {
241 var self_info: SelfInfo = .init;
242 };
243 return &S.self_info;
244244}
245245
246246/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
......@@ -446,10 +446,7 @@ pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void {
446446 defer it.deinit();
447447
448448 // DWARF unwinding on aarch64-macos is not complete so we need to get pc address from mcontext
449 const pc_addr = if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64)
450 context.mcontext.ss.pc
451 else
452 it.unwind_state.?.dwarf_context.pc;
449 const pc_addr = it.unwind_state.?.dwarf_context.pc;
453450 printSourceAtAddress(debug_info, stderr, pc_addr, tty_config) catch return;
454451
455452 while (it.next()) |return_address| {
......@@ -460,7 +457,7 @@ pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void {
460457 // an overflow. We do not need to signal `StackIterator` as it will correctly detect this
461458 // condition on the subsequent iteration and return `null` thus terminating the loop.
462459 // same behaviour for x86-windows-msvc
463 const address = if (return_address == 0) return_address else return_address - 1;
460 const address = return_address -| 1;
464461 printSourceAtAddress(debug_info, stderr, address, tty_config) catch return;
465462 } else printLastUnwindError(&it, debug_info, stderr, tty_config);
466463 }
......@@ -758,7 +755,7 @@ pub fn writeStackTrace(
758755 frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len;
759756 }) {
760757 const return_address = stack_trace.instruction_addresses[frame_index];
761 try printSourceAtAddress(debug_info, writer, return_address - 1, tty_config);
758 try printSourceAtAddress(debug_info, writer, return_address -| 1, tty_config);
762759 }
763760
764761 if (stack_trace.index > stack_trace.instruction_addresses.len) {
......@@ -808,16 +805,11 @@ pub const StackIterator = struct {
808805 }
809806
810807 pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t, fp: usize) !StackIterator {
811 // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that
812 // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder.
813 if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64)
814 return init(first_address, @truncate(context.mcontext.ss.fp));
815
816808 if (SelfInfo.supports_unwinding) {
817809 var iterator = init(first_address, fp);
818810 iterator.unwind_state = .{
819811 .debug_info = debug_info,
820 .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context),
812 .dwarf_context = try SelfInfo.UnwindContext.init(getDebugInfoAllocator(), context),
821813 };
822814 return iterator;
823815 }
......@@ -890,7 +882,7 @@ pub const StackIterator = struct {
890882 if (!unwind_state.failed) {
891883 if (unwind_state.dwarf_context.pc == 0) return null;
892884 defer it.fp = unwind_state.dwarf_context.getFp() catch 0;
893 if (unwind_state.debug_info.unwindFrame(&unwind_state.dwarf_context)) |return_address| {
885 if (unwind_state.debug_info.unwindFrame(getDebugInfoAllocator(), &unwind_state.dwarf_context)) |return_address| {
894886 return return_address;
895887 } else |err| {
896888 unwind_state.last_error = err;
......@@ -1039,19 +1031,6 @@ pub fn writeStackTraceWindows(
10391031 }
10401032}
10411033
1042fn printUnknownSource(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
1043 const module_name = debug_info.getModuleNameForAddress(address);
1044 return printLineInfo(
1045 writer,
1046 null,
1047 address,
1048 "???",
1049 module_name orelse "???",
1050 tty_config,
1051 printLineFromFileAnyOs,
1052 );
1053}
1054
10551034fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: tty.Config) void {
10561035 if (!have_ucontext) return;
10571036 if (it.getLastError()) |unwind_error| {
......@@ -1059,32 +1038,48 @@ fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writ
10591038 }
10601039}
10611040
1062fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, err: UnwindError, tty_config: tty.Config) !void {
1063 const module_name = debug_info.getModuleNameForAddress(address) orelse "???";
1041fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, unwind_err: UnwindError, tty_config: tty.Config) !void {
1042 const module_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {
1043 error.Unexpected, error.OutOfMemory => |e| return e,
1044 error.MissingDebugInfo => "???",
1045 };
10641046 try tty_config.setColor(writer, .dim);
1065 if (err == error.MissingDebugInfo) {
1047 if (unwind_err == error.MissingDebugInfo) {
10661048 try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });
10671049 } else {
1068 try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err });
1050 try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, unwind_err });
10691051 }
10701052 try tty_config.setColor(writer, .reset);
10711053}
10721054
10731055pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
1074 const symbol_info = debug_info.getSymbolAtAddress(address) catch |err| switch (err) {
1075 error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),
1076 else => return err,
1056 const gpa = getDebugInfoAllocator();
1057 if (debug_info.getSymbolAtAddress(gpa, address)) |symbol_info| {
1058 defer if (symbol_info.source_location) |sl| gpa.free(sl.file_name);
1059 return printLineInfo(
1060 writer,
1061 symbol_info.source_location,
1062 address,
1063 symbol_info.name,
1064 symbol_info.compile_unit_name,
1065 tty_config,
1066 );
1067 } else |err| switch (err) {
1068 error.MissingDebugInfo, error.InvalidDebugInfo => {},
1069 else => |e| return e,
1070 }
1071 // Unknown source location, but perhaps we can at least get a module name
1072 const compile_unit_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {
1073 error.MissingDebugInfo => "???",
1074 error.Unexpected, error.OutOfMemory => |e| return e,
10771075 };
1078 defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name);
1079
10801076 return printLineInfo(
10811077 writer,
1082 symbol_info.source_location,
1078 null,
10831079 address,
1084 symbol_info.name,
1085 symbol_info.compile_unit_name,
1080 "???",
1081 compile_unit_name,
10861082 tty_config,
1087 printLineFromFileAnyOs,
10881083 );
10891084}
10901085
......@@ -1095,7 +1090,6 @@ fn printLineInfo(
10951090 symbol_name: []const u8,
10961091 compile_unit_name: []const u8,
10971092 tty_config: tty.Config,
1098 comptime printLineFromFile: anytype,
10991093) !void {
11001094 nosuspend {
11011095 try tty_config.setColor(writer, .bold);
......@@ -1136,7 +1130,7 @@ fn printLineInfo(
11361130 }
11371131}
11381132
1139fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !void {
1133fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
11401134 // Need this to always block even in async I/O mode, because this could potentially
11411135 // be called from e.g. the event loop code crashing.
11421136 var f = try fs.cwd().openFile(source_location.file_name, .{});
......@@ -1190,7 +1184,7 @@ fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !voi
11901184 }
11911185}
11921186
1193test printLineFromFileAnyOs {
1187test printLineFromFile {
11941188 var aw: Writer.Allocating = .init(std.testing.allocator);
11951189 defer aw.deinit();
11961190 const output_stream = &aw.writer;
......@@ -1212,9 +1206,9 @@ test printLineFromFileAnyOs {
12121206 defer allocator.free(path);
12131207 try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });
12141208
1215 try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
1209 try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
12161210
1217 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1211 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
12181212 try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());
12191213 aw.clearRetainingCapacity();
12201214 }
......@@ -1230,11 +1224,11 @@ test printLineFromFileAnyOs {
12301224 ,
12311225 });
12321226
1233 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1227 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
12341228 try expectEqualStrings("1\n", aw.written());
12351229 aw.clearRetainingCapacity();
12361230
1237 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 3, .column = 0 });
1231 try printLineFromFile(output_stream, .{ .file_name = path, .line = 3, .column = 0 });
12381232 try expectEqualStrings("3\n", aw.written());
12391233 aw.clearRetainingCapacity();
12401234 }
......@@ -1253,7 +1247,7 @@ test printLineFromFileAnyOs {
12531247 try writer.splatByteAll('a', overlap);
12541248 try writer.flush();
12551249
1256 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
1250 try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
12571251 try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());
12581252 aw.clearRetainingCapacity();
12591253 }
......@@ -1267,7 +1261,7 @@ test printLineFromFileAnyOs {
12671261 const writer = &file_writer.interface;
12681262 try writer.splatByteAll('a', std.heap.page_size_max);
12691263
1270 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1264 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
12711265 try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());
12721266 aw.clearRetainingCapacity();
12731267 }
......@@ -1281,19 +1275,19 @@ test printLineFromFileAnyOs {
12811275 const writer = &file_writer.interface;
12821276 try writer.splatByteAll('a', 3 * std.heap.page_size_max);
12831277
1284 try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
1278 try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
12851279
1286 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1280 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
12871281 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());
12881282 aw.clearRetainingCapacity();
12891283
12901284 try writer.writeAll("a\na");
12911285
1292 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1286 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
12931287 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());
12941288 aw.clearRetainingCapacity();
12951289
1296 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
1290 try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
12971291 try expectEqualStrings("a\n", aw.written());
12981292 aw.clearRetainingCapacity();
12991293 }
......@@ -1309,26 +1303,23 @@ test printLineFromFileAnyOs {
13091303 try writer.splatByteAll('\n', real_file_start);
13101304 try writer.writeAll("abc\ndef");
13111305
1312 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
1306 try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
13131307 try expectEqualStrings("abc\n", aw.written());
13141308 aw.clearRetainingCapacity();
13151309
1316 try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
1310 try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
13171311 try expectEqualStrings("def\n", aw.written());
13181312 aw.clearRetainingCapacity();
13191313 }
13201314}
13211315
13221316/// TODO multithreaded awareness
1323var debug_info_allocator: ?mem.Allocator = null;
1324var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;
1317var debug_info_arena: ?std.heap.ArenaAllocator = null;
13251318fn getDebugInfoAllocator() mem.Allocator {
1326 if (debug_info_allocator) |a| return a;
1327
1328 debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
1329 const allocator = debug_info_arena_allocator.allocator();
1330 debug_info_allocator = allocator;
1331 return allocator;
1319 if (debug_info_arena == null) {
1320 debug_info_arena = .init(std.heap.page_allocator);
1321 }
1322 return debug_info_arena.?.allocator();
13321323}
13331324
13341325/// Whether or not the current target can print useful debug information when a segfault occurs.
lib/std/debug/Dwarf.zig+27-56
......@@ -78,17 +78,6 @@ pub const Section = struct {
7878 debug_addr,
7979 debug_names,
8080 };
81
82 // For sections that are not memory mapped by the loader, this is an offset
83 // from `data.ptr` to where the section would have been mapped. Otherwise,
84 // `data` is directly backed by the section and the offset is zero.
85 pub fn virtualOffset(self: Section, base_address: usize) i64 {
86 return if (self.virtual_address) |va|
87 @as(i64, @intCast(base_address + va)) -
88 @as(i64, @intCast(@intFromPtr(self.data.ptr)))
89 else
90 0;
91 }
9281};
9382
9483pub const Abbrev = struct {
......@@ -342,10 +331,6 @@ pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 {
342331 return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;
343332}
344333
345pub fn sectionVirtualOffset(di: Dwarf, dwarf_section: Section.Id, base_address: usize) ?i64 {
346 return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null;
347}
348
349334pub fn deinit(di: *Dwarf, gpa: Allocator) void {
350335 for (di.sections) |opt_section| {
351336 if (opt_section) |s| if (s.owned) gpa.free(s.data);
......@@ -364,8 +349,6 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void {
364349 }
365350 di.compile_unit_list.deinit(gpa);
366351 di.func_list.deinit(gpa);
367 di.cie_map.deinit(gpa);
368 di.fde_list.deinit(gpa);
369352 di.ranges.deinit(gpa);
370353 di.* = undefined;
371354}
......@@ -983,8 +966,8 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit:
983966 },
984967 0,
985968 };
986 _ = addr_size;
987 _ = seg_size;
969 if (seg_size != 0) return bad(); // unsupported
970 _ = addr_size; // TODO: ignoring this is incorrect, we should use it to decide address lengths
988971
989972 const prologue_length = try readAddress(&fr, unit_header.format, endian);
990973 const prog_start_offset = fr.seek + prologue_length;
......@@ -1472,44 +1455,27 @@ pub const ElfModule = struct {
14721455 mapped_memory: ?[]align(std.heap.page_size_min) const u8,
14731456 external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,
14741457
1475 pub const Lookup = struct {
1476 base_address: usize,
1477 name: []const u8,
1478 build_id: ?[]const u8,
1479 gnu_eh_frame: ?[]const u8,
1458 pub const init: ElfModule = .{
1459 .unwind = .{
1460 .debug_frame = null,
1461 .eh_frame = null,
1462 },
1463 .dwarf = .{},
1464 .mapped_memory = null,
1465 .external_mapped_memory = null,
14801466 };
14811467
1482 pub fn init(lookup: *const Lookup) ElfModule {
1483 var em: ElfModule = .{
1484 .unwind = .{
1485 .sections = @splat(null),
1486 },
1487 .dwarf = .{},
1488 .mapped_memory = null,
1489 .external_mapped_memory = null,
1490 };
1491 if (lookup.gnu_eh_frame) |eh_frame_hdr| {
1492 // This is a special case - pointer offsets inside .eh_frame_hdr
1493 // are encoded relative to its base address, so we must use the
1494 // version that is already memory mapped, and not the one that
1495 // will be mapped separately from the ELF file.
1496 em.unwind.sections[@intFromEnum(Dwarf.Unwind.Section.Id.eh_frame_hdr)] = .{
1497 .data = eh_frame_hdr,
1498 };
1499 }
1500 return em;
1501 }
1502
15031468 pub fn deinit(self: *@This(), allocator: Allocator) void {
15041469 self.dwarf.deinit(allocator);
15051470 std.posix.munmap(self.mapped_memory);
15061471 if (self.external_mapped_memory) |m| std.posix.munmap(m);
15071472 }
15081473
1509 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, endian: Endian, base_address: usize, address: usize) !std.debug.Symbol {
1510 // Translate the VA into an address into this object
1511 const relocated_address = address - base_address;
1512 return self.dwarf.getSymbol(allocator, endian, relocated_address);
1474 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, endian: Endian, load_offset: usize, address: usize) !std.debug.Symbol {
1475 // Translate the runtime address into a virtual address into the module
1476 // MLUGG TODO: this clearly tells us that the logic should live near SelfInfo...
1477 const vaddr = address - load_offset;
1478 return self.dwarf.getSymbol(allocator, endian, vaddr);
15131479 }
15141480
15151481 pub fn getDwarfUnwindForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf.Unwind {
......@@ -1548,7 +1514,7 @@ pub const ElfModule = struct {
15481514 mapped_mem: []align(std.heap.page_size_min) const u8,
15491515 build_id: ?[]const u8,
15501516 expected_crc: ?u32,
1551 parent_sections: *Dwarf.SectionArray,
1517 parent_sections: ?*Dwarf.SectionArray,
15521518 parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
15531519 elf_filename: ?[]const u8,
15541520 ) LoadError!void {
......@@ -1577,10 +1543,12 @@ pub const ElfModule = struct {
15771543 var sections: Dwarf.SectionArray = @splat(null);
15781544
15791545 // Combine section list. This takes ownership over any owned sections from the parent scope.
1580 for (parent_sections, &sections) |*parent, *section_elem| {
1581 if (parent.*) |*p| {
1582 section_elem.* = p.*;
1583 p.owned = false;
1546 if (parent_sections) |ps| {
1547 for (ps, &sections) |*parent, *section_elem| {
1548 if (parent.*) |*p| {
1549 section_elem.* = p.*;
1550 p.owned = false;
1551 }
15841552 }
15851553 }
15861554 errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data);
......@@ -1647,7 +1615,6 @@ pub const ElfModule = struct {
16471615 // Attempt to load debug info from an external file
16481616 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
16491617 if (missing_debug_info) {
1650
16511618 // Only allow one level of debug info nesting
16521619 if (parent_mapped_mem) |_| {
16531620 return error.MissingDebugInfo;
......@@ -1775,6 +1742,7 @@ pub const ElfModule = struct {
17751742
17761743 em.mapped_memory = parent_mapped_mem orelse mapped_mem;
17771744 em.external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null;
1745 em.dwarf.sections = sections;
17781746 try em.dwarf.open(gpa, endian);
17791747 }
17801748
......@@ -1844,7 +1812,8 @@ pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]cons
18441812 return ptr[start..end];
18451813}
18461814
1847pub fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
1815fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
1816 // MLUGG TODO FIX BEFORE MERGE: this function is slightly bogus. addresses have a byte width which is independent of the `dwarf.Format`!
18481817 return switch (format) {
18491818 .@"32" => try r.takeInt(u32, endian),
18501819 .@"64" => try r.takeInt(u64, endian),
......@@ -1852,6 +1821,8 @@ pub fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
18521821}
18531822
18541823fn nativeFormat() std.dwarf.Format {
1824 // MLUGG TODO FIX BEFORE MERGE: this is nonsensical. this is neither what `dwarf.Format` is for, nor does it make sense to check the NATIVE FUCKING FORMAT
1825 // when parsing ARBITRARY DWARF.
18551826 return switch (@sizeOf(usize)) {
18561827 4 => .@"32",
18571828 8 => .@"64",
lib/std/debug/Dwarf/Unwind.zig+496-506
......@@ -1,632 +1,622 @@
1sections: SectionArray = @splat(null),
1pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");
22
3/// Starts out non-`null` if the `.eh_frame_hdr` section is present. May become `null` later if we
4/// find that `.eh_frame_hdr` is incomplete.
5eh_frame_hdr: ?ExceptionFrameHeader = null,
6/// These lookup tables are only used if `eh_frame_hdr` is null
7cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .empty,
8/// Sorted by start_pc
9fde_list: std.ArrayList(FrameDescriptionEntry) = .empty,
10
11pub const Section = struct {
3/// The contents of the `.debug_frame` section as specified by DWARF. This might be a more reliable
4/// stack unwind mechanism in some cases, or it may be present when `.eh_frame` is not, but fetching
5/// the data requires loading the binary, so it is not a viable approach for fast stack trace
6/// capturing within a process.
7debug_frame: ?struct {
128 data: []const u8,
13
14 pub const Id = enum {
15 debug_frame,
16 eh_frame,
17 eh_frame_hdr,
18 };
9 /// Offsets into `data` of FDEs, sorted by ascending `pc_begin`.
10 sorted_fdes: []SortedFdeEntry,
11},
12
13/// Data associated with the `.eh_frame` and `.eh_frame_hdr` sections as defined by LSB Core. The
14/// format of `.eh_frame` is an extension of that of DWARF's `.debug_frame` -- in fact it is almost
15/// identical, though subtly different in a few places.
16eh_frame: ?struct {
17 header: EhFrameHeader,
18 /// Though this is a slice, it may be longer than the `.eh_frame` section. When unwinding
19 /// through the runtime-loaded `.eh_frame_hdr` data, we are not told the size of the `.eh_frame`
20 /// section, so construct a slice referring to all of the rest of memory. The end of the section
21 /// must be detected through `EntryHeader.terminator`.
22 eh_frame_data: []const u8,
23 /// Offsets into `eh_frame_data` of FDEs, sorted by ascending `pc_begin`.
24 /// Populated only if `header` does not already contain a lookup table.
25 sorted_fdes: ?[]SortedFdeEntry,
26},
27
28const SortedFdeEntry = struct {
29 /// This FDE's value of `pc_begin`.
30 pc_begin: u64,
31 /// Offset into the section of the corresponding FDE, including the entry header.
32 fde_offset: u64,
1933};
2034
21const num_sections = std.enums.directEnumArrayLen(Section.Id, 0);
22pub const SectionArray = [num_sections]?Section;
23
24pub fn section(unwind: Unwind, dwarf_section: Section.Id) ?[]const u8 {
25 return if (unwind.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;
26}
35const Section = enum { debug_frame, eh_frame };
2736
2837/// This represents the decoded .eh_frame_hdr header
29pub const ExceptionFrameHeader = struct {
30 eh_frame_ptr: usize,
31 table_enc: u8,
32 fde_count: usize,
33 entries: []const u8,
34
35 pub fn entrySize(table_enc: u8) !u8 {
36 return switch (table_enc & EH.PE.type_mask) {
37 EH.PE.udata2,
38 EH.PE.sdata2,
39 => 4,
40 EH.PE.udata4,
41 EH.PE.sdata4,
42 => 8,
43 EH.PE.udata8,
44 EH.PE.sdata8,
45 => 16,
46 // This is a binary search table, so all entries must be the same length
47 else => return bad(),
38pub const EhFrameHeader = struct {
39 vaddr: u64,
40 eh_frame_vaddr: u64,
41 search_table: ?struct {
42 /// The byte offset of the search table into the `.eh_frame_hdr` section.
43 offset: u8,
44 encoding: EH.PE,
45 fde_count: usize,
46 entries: []const u8,
47 },
48
49 pub fn entrySize(table_enc: EH.PE, addr_size_bytes: u8) !u8 {
50 return switch (table_enc.type) {
51 .absptr => 2 * addr_size_bytes,
52 .udata2, .sdata2 => 4,
53 .udata4, .sdata4 => 8,
54 .udata8, .sdata8 => 16,
55 .uleb128, .sleb128 => return bad(), // this is a binary search table; all entries must be the same size
56 _ => return bad(),
4857 };
4958 }
5059
51 pub fn findEntry(
52 self: ExceptionFrameHeader,
53 eh_frame_len: usize,
54 eh_frame_hdr_ptr: usize,
55 pc: usize,
56 cie: *CommonInformationEntry,
57 fde: *FrameDescriptionEntry,
60 pub fn parse(
61 eh_frame_hdr_vaddr: u64,
62 eh_frame_hdr_bytes: []const u8,
63 addr_size_bytes: u8,
5864 endian: Endian,
59 ) !void {
60 const entry_size = try entrySize(self.table_enc);
65 ) !EhFrameHeader {
66 var r: Reader = .fixed(eh_frame_hdr_bytes);
6167
62 var left: usize = 0;
63 var len: usize = self.fde_count;
64 var fbr: Reader = .fixed(self.entries);
68 const version = try r.takeByte();
69 if (version != 1) return bad();
6570
66 while (len > 1) {
67 const mid = left + len / 2;
71 const eh_frame_ptr_enc: EH.PE = @bitCast(try r.takeByte());
72 const fde_count_enc: EH.PE = @bitCast(try r.takeByte());
73 const table_enc: EH.PE = @bitCast(try r.takeByte());
6874
69 fbr.seek = mid * entry_size;
70 const pc_begin = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
71 .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),
72 .follow_indirect = true,
73 .data_rel_base = eh_frame_hdr_ptr,
74 }, endian) orelse return bad();
75 const eh_frame_ptr = try readEhPointer(&r, eh_frame_ptr_enc, addr_size_bytes, .{
76 .pc_rel_base = eh_frame_hdr_vaddr + r.seek,
77 }, endian);
7578
79 return .{
80 .vaddr = eh_frame_hdr_vaddr,
81 .eh_frame_vaddr = eh_frame_ptr,
82 .search_table = table: {
83 if (fde_count_enc == EH.PE.omit) break :table null;
84 if (table_enc == EH.PE.omit) break :table null;
85 const fde_count = try readEhPointer(&r, fde_count_enc, addr_size_bytes, .{
86 .pc_rel_base = eh_frame_hdr_vaddr + r.seek,
87 }, endian);
88 const entry_size = try entrySize(table_enc, addr_size_bytes);
89 const bytes_offset = r.seek;
90 const bytes_len = cast(usize, fde_count * entry_size) orelse return error.EndOfStream;
91 const bytes = try r.take(bytes_len);
92 break :table .{
93 .encoding = table_enc,
94 .fde_count = @intCast(fde_count),
95 .entries = bytes,
96 .offset = @intCast(bytes_offset),
97 };
98 },
99 };
100 }
101
102 /// Asserts that `eh_frame_hdr.search_table != null`.
103 fn findEntry(
104 eh_frame_hdr: *const EhFrameHeader,
105 pc: u64,
106 addr_size_bytes: u8,
107 endian: Endian,
108 ) !?u64 {
109 const table = &eh_frame_hdr.search_table.?;
110 const table_vaddr = eh_frame_hdr.vaddr + table.offset;
111 const entry_size = try EhFrameHeader.entrySize(table.encoding, addr_size_bytes);
112 var left: usize = 0;
113 var len: usize = table.fde_count;
114 while (len > 1) {
115 const mid = left + len / 2;
116 var entry_reader: Reader = .fixed(table.entries[mid * entry_size ..][0..entry_size]);
117 const pc_begin = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
118 .pc_rel_base = table_vaddr + left * entry_size,
119 .data_rel_base = eh_frame_hdr.vaddr,
120 }, endian);
76121 if (pc < pc_begin) {
77122 len /= 2;
78123 } else {
79124 left = mid;
80 if (pc == pc_begin) break;
81125 len -= len / 2;
82126 }
83127 }
84
85 if (len == 0) return missing();
86 fbr.seek = left * entry_size;
87
88 // Read past the pc_begin field of the entry
89 _ = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
90 .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),
91 .follow_indirect = true,
92 .data_rel_base = eh_frame_hdr_ptr,
93 }, endian) orelse return bad();
94
95 const fde_ptr = cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
96 .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),
97 .follow_indirect = true,
98 .data_rel_base = eh_frame_hdr_ptr,
99 }, endian) orelse return bad()) orelse return bad();
100
101 if (fde_ptr < self.eh_frame_ptr) return bad();
102
103 const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0..eh_frame_len];
104
105 const fde_offset = fde_ptr - self.eh_frame_ptr;
106 var eh_frame_fbr: Reader = .fixed(eh_frame);
107 eh_frame_fbr.seek = fde_offset;
108
109 const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame, endian);
110 if (fde_entry_header.type != .fde) return bad();
111
112 // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable
113 const cie_offset = fde_entry_header.type.fde;
114 eh_frame_fbr.seek = @intCast(cie_offset);
115 const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame, endian);
116 if (cie_entry_header.type != .cie) return bad();
117
118 cie.* = try CommonInformationEntry.parse(
119 cie_entry_header.entry_bytes,
120 0,
121 true,
122 cie_entry_header.format,
123 .eh_frame,
124 cie_entry_header.length_offset,
125 @sizeOf(usize),
126 endian,
127 );
128
129 fde.* = try FrameDescriptionEntry.parse(
130 fde_entry_header.entry_bytes,
131 0,
132 true,
133 cie.*,
134 @sizeOf(usize),
135 endian,
136 );
137
138 if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return missing();
128 if (len == 0) return null;
129 var entry_reader: Reader = .fixed(table.entries[left * entry_size ..][0..entry_size]);
130 // Skip past `pc_begin`; we're now interested in the fde offset
131 _ = try readEhPointerAbs(&entry_reader, table.encoding.type, addr_size_bytes, endian);
132 const fde_ptr = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
133 .pc_rel_base = table_vaddr + left * entry_size,
134 .data_rel_base = eh_frame_hdr.vaddr,
135 }, endian);
136 return std.math.sub(u64, fde_ptr, eh_frame_hdr.eh_frame_vaddr) catch bad(); // offset into .eh_frame
139137 }
140138};
141139
142pub const EntryHeader = struct {
143 /// Offset of the length field in the backing buffer
144 length_offset: usize,
145 format: Format,
146 type: union(enum) {
147 cie,
148 /// Value is the offset of the corresponding CIE
149 fde: u64,
150 terminator,
140pub const EntryHeader = union(enum) {
141 cie: struct {
142 format: Format,
143 /// Remaining bytes in the CIE. These are parseable by `CommonInformationEntry.parse`.
144 bytes_len: u64,
145 },
146 fde: struct {
147 format: Format,
148 /// Offset into the section of the corresponding CIE, *including* its entry header.
149 cie_offset: u64,
150 /// Remaining bytes in the FDE. These are parseable by `FrameDescriptionEntry.parse`.
151 bytes_len: u64,
151152 },
152 /// The entry's contents, not including the ID field
153 entry_bytes: []const u8,
153 /// The `.eh_frame` format includes terminators which indicate that the last CIE/FDE has been
154 /// reached. However, `.debug_frame` does not include such a terminator, so the caller must
155 /// keep track of how many section bytes remain when parsing all entries in `.debug_frame`.
156 terminator,
154157
155 /// The length of the entry including the ID field, but not the length field itself
156 pub fn entryLength(self: EntryHeader) usize {
157 return self.entry_bytes.len + @as(u8, if (self.format == .@"64") 8 else 4);
158 }
158 pub fn read(r: *Reader, header_section_offset: u64, section: Section, endian: Endian) !EntryHeader {
159 const unit_header = try Dwarf.readUnitHeader(r, endian);
160 if (unit_header.unit_length == 0) return .terminator;
159161
160 /// Reads a header for either an FDE or a CIE, then advances the fbr to the
161 /// position after the trailing structure.
162 ///
163 /// `fbr` must be backed by either the .eh_frame or .debug_frame sections.
164 ///
165 /// TODO that's a bad API, don't do that. this function should neither require
166 /// a fixed reader nor depend on seeking.
167 pub fn read(fbr: *Reader, dwarf_section: Section.Id, endian: Endian) !EntryHeader {
168 assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame);
169
170 const length_offset = fbr.seek;
171 const unit_header = try Dwarf.readUnitHeader(fbr, endian);
172 const unit_length = cast(usize, unit_header.unit_length) orelse return bad();
173 if (unit_length == 0) return .{
174 .length_offset = length_offset,
175 .format = unit_header.format,
176 .type = .terminator,
177 .entry_bytes = &.{},
178 };
179 const start_offset = fbr.seek;
180 const end_offset = start_offset + unit_length;
181 defer fbr.seek = end_offset;
182
183 const id = try Dwarf.readAddress(fbr, unit_header.format, endian);
184 const entry_bytes = fbr.buffer[fbr.seek..end_offset];
185 const cie_id: u64 = switch (dwarf_section) {
186 .eh_frame => CommonInformationEntry.eh_id,
162 // TODO MLUGG: seriously, just... check the formats of everything in BOTH LSB Core and DWARF. this is a fucking *mess*. maybe add spec references.
163
164 // Next is a value which will disambiguate CIEs and FDEs. Annoyingly, LSB Core makes this
165 // value always 4-byte, whereas DWARF makes it depend on the `dwarf.Format`.
166 const cie_ptr_or_id_size: u8 = switch (section) {
167 .eh_frame => 4,
187168 .debug_frame => switch (unit_header.format) {
188 .@"32" => CommonInformationEntry.dwarf32_id,
189 .@"64" => CommonInformationEntry.dwarf64_id,
169 .@"32" => 4,
170 .@"64" => 8,
190171 },
172 };
173 const cie_ptr_or_id = switch (cie_ptr_or_id_size) {
174 4 => try r.takeInt(u32, endian),
175 8 => try r.takeInt(u64, endian),
191176 else => unreachable,
192177 };
178 const remaining_bytes = unit_header.unit_length - cie_ptr_or_id_size;
193179
194 return .{
195 .length_offset = length_offset,
196 .format = unit_header.format,
197 .type = if (id == cie_id) .cie else .{ .fde = switch (dwarf_section) {
198 .eh_frame => try std.math.sub(u64, start_offset, id),
199 .debug_frame => id,
200 else => unreachable,
201 } },
202 .entry_bytes = entry_bytes,
180 // If this entry is a CIE, then `cie_ptr_or_id` will have this value, which is different
181 // between the DWARF `.debug_frame` section and the LSB Core `.eh_frame` section.
182 const cie_id: u64 = switch (section) {
183 .eh_frame => 0,
184 .debug_frame => switch (unit_header.format) {
185 .@"32" => maxInt(u32),
186 .@"64" => maxInt(u64),
187 },
203188 };
189 if (cie_ptr_or_id == cie_id) {
190 return .{ .cie = .{
191 .format = unit_header.format,
192 .bytes_len = remaining_bytes,
193 } };
194 }
195
196 // This is an FDE -- `cie_ptr_or_id` points to the associated CIE. Unfortunately, the format
197 // of that pointer again differs between `.debug_frame` and `.eh_frame`.
198 const cie_offset = switch (section) {
199 .eh_frame => try std.math.sub(u64, header_section_offset + unit_header.header_length, cie_ptr_or_id),
200 .debug_frame => cie_ptr_or_id,
201 };
202 return .{ .fde = .{
203 .format = unit_header.format,
204 .cie_offset = cie_offset,
205 .bytes_len = remaining_bytes,
206 } };
204207 }
205208};
206209
207210pub const CommonInformationEntry = struct {
208 // Used in .eh_frame
209 pub const eh_id = 0;
210
211 // Used in .debug_frame (DWARF32)
212 pub const dwarf32_id = maxInt(u32);
213
214 // Used in .debug_frame (DWARF64)
215 pub const dwarf64_id = maxInt(u64);
216
217 // Offset of the length field of this entry in the eh_frame section.
218 // This is the key that FDEs use to reference CIEs.
219 length_offset: u64,
220211 version: u8,
221 address_size: u8,
222 format: Format,
223212
224 // Only present in version 4
225 segment_selector_size: ?u8,
213 /// In version 4, CIEs can specify the address size used in the CIE and associated FDEs.
214 /// This value must be used *only* to parse associated FDEs in `FrameDescriptionEntry.parse`.
215 addr_size_bytes: u8,
216
217 /// Always 0 for versions which do not specify this (currently all versions other than 4).
218 segment_selector_size: u8,
226219
227220 code_alignment_factor: u32,
228221 data_alignment_factor: i32,
229222 return_address_register: u8,
230223
231 aug_str: []const u8,
232 aug_data: []const u8,
233 lsda_pointer_enc: u8,
234 personality_enc: ?u8,
235 personality_routine_pointer: ?u64,
236 fde_pointer_enc: u8,
237 initial_instructions: []const u8,
224 fde_pointer_enc: EH.PE,
225 is_signal_frame: bool,
238226
239 pub fn isSignalFrame(self: CommonInformationEntry) bool {
240 for (self.aug_str) |c| if (c == 'S') return true;
241 return false;
242 }
227 augmentation_kind: AugmentationKind,
243228
244 pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool {
245 for (self.aug_str) |c| if (c == 'B') return true;
246 return false;
247 }
229 initial_instructions: []const u8,
248230
249 pub fn mteTaggedFrame(self: CommonInformationEntry) bool {
250 for (self.aug_str) |c| if (c == 'G') return true;
251 return false;
252 }
231 pub const AugmentationKind = enum { none, gcc_eh, lsb_z };
253232
254233 /// This function expects to read the CIE starting with the version field.
255 /// The returned struct references memory backed by cie_bytes.
256 ///
257 /// See the FrameDescriptionEntry.parse documentation for the description
258 /// of `pc_rel_offset` and `is_runtime`.
234 /// The returned struct references memory backed by `cie_bytes`.
259235 ///
260236 /// `length_offset` specifies the offset of this CIE's length field in the
261237 /// .eh_frame / .debug_frame section.
262238 pub fn parse(
263239 cie_bytes: []const u8,
264 pc_rel_offset: i64,
265 is_runtime: bool,
266 format: Format,
267 dwarf_section: Section.Id,
268 length_offset: u64,
269 addr_size_bytes: u8,
270 endian: Endian,
240 section: Section,
241 default_addr_size_bytes: u8,
271242 ) !CommonInformationEntry {
272 if (addr_size_bytes > 8) return error.UnsupportedAddrSize;
243 // We only read the data through this reader.
244 var r: Reader = .fixed(cie_bytes);
273245
274 var fbr: Reader = .fixed(cie_bytes);
275
276 const version = try fbr.takeByte();
277 switch (dwarf_section) {
246 const version = try r.takeByte();
247 switch (section) {
278248 .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion,
279249 .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion,
280 else => return error.UnsupportedDwarfSection,
281250 }
282251
283 var has_eh_data = false;
284 var has_aug_data = false;
285
286 var aug_str_len: usize = 0;
287 const aug_str_start = fbr.seek;
288 var aug_byte = try fbr.takeByte();
289 while (aug_byte != 0) : (aug_byte = try fbr.takeByte()) {
290 switch (aug_byte) {
291 'z' => {
292 if (aug_str_len != 0) return bad();
293 has_aug_data = true;
294 },
295 'e' => {
296 if (has_aug_data or aug_str_len != 0) return bad();
297 if (try fbr.takeByte() != 'h') return bad();
298 has_eh_data = true;
299 },
300 else => if (has_eh_data) return bad(),
301 }
302
303 aug_str_len += 1;
304 }
252 const aug_str = try r.takeSentinel(0);
253 const aug_kind: AugmentationKind = aug: {
254 if (aug_str.len == 0) break :aug .none;
255 if (aug_str[0] == 'z') break :aug .lsb_z;
256 if (std.mem.eql(u8, aug_str, "eh")) break :aug .gcc_eh;
257 // We can't finish parsing the CIE if we don't know what its augmentation means.
258 return bad();
259 };
305260
306 if (has_eh_data) {
307 // legacy data created by older versions of gcc - unsupported here
308 for (0..addr_size_bytes) |_| _ = try fbr.takeByte();
261 switch (aug_kind) {
262 .none => {}, // no extra data
263 .lsb_z => {}, // no extra data yet, but there is a bit later
264 .gcc_eh => try r.discardAll(default_addr_size_bytes), // unsupported data
309265 }
310266
311 const address_size = if (version == 4) try fbr.takeByte() else addr_size_bytes;
312 const segment_selector_size = if (version == 4) try fbr.takeByte() else null;
313
314 const code_alignment_factor = try fbr.takeLeb128(u32);
315 const data_alignment_factor = try fbr.takeLeb128(i32);
316 const return_address_register = if (version == 1) try fbr.takeByte() else try fbr.takeLeb128(u8);
317
318 var lsda_pointer_enc: u8 = EH.PE.omit;
319 var personality_enc: ?u8 = null;
320 var personality_routine_pointer: ?u64 = null;
321 var fde_pointer_enc: u8 = EH.PE.absptr;
322
323 var aug_data: []const u8 = &[_]u8{};
324 const aug_str = if (has_aug_data) blk: {
325 const aug_data_len = try fbr.takeLeb128(usize);
326 const aug_data_start = fbr.seek;
327 aug_data = cie_bytes[aug_data_start..][0..aug_data_len];
328
329 const aug_str = cie_bytes[aug_str_start..][0..aug_str_len];
330 for (aug_str[1..]) |byte| {
331 switch (byte) {
332 'L' => {
333 lsda_pointer_enc = try fbr.takeByte();
334 },
335 'P' => {
336 personality_enc = try fbr.takeByte();
337 personality_routine_pointer = try readEhPointer(&fbr, personality_enc.?, addr_size_bytes, .{
338 .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[fbr.seek]), pc_rel_offset),
339 .follow_indirect = is_runtime,
340 }, endian);
341 },
342 'R' => {
343 fde_pointer_enc = try fbr.takeByte();
344 },
345 'S', 'B', 'G' => {},
346 else => return bad(),
347 }
348 }
349
350 // aug_data_len can include padding so the CIE ends on an address boundary
351 fbr.seek = aug_data_start + aug_data_len;
352 break :blk aug_str;
353 } else &[_]u8{};
267 const addr_size_bytes = if (version == 4) try r.takeByte() else default_addr_size_bytes;
268 const segment_selector_size: u8 = if (version == 4) try r.takeByte() else 0;
269 const code_alignment_factor = try r.takeLeb128(u32);
270 const data_alignment_factor = try r.takeLeb128(i32);
271 const return_address_register = if (version == 1) try r.takeByte() else try r.takeLeb128(u8);
272
273 // This is where LSB's augmentation might add some data.
274 const fde_pointer_enc: EH.PE, const is_signal_frame: bool = aug: {
275 const default_fde_pointer_enc: EH.PE = .{ .type = .absptr, .rel = .abs };
276 if (aug_kind != .lsb_z) break :aug .{ default_fde_pointer_enc, false };
277 const aug_data_len = try r.takeLeb128(u32);
278 var aug_data: Reader = .fixed(try r.take(aug_data_len));
279 var fde_pointer_enc: EH.PE = default_fde_pointer_enc;
280 var is_signal_frame = false;
281 for (aug_str[1..]) |byte| switch (byte) {
282 'L' => _ = try aug_data.takeByte(), // we ignore the LSDA pointer
283 'P' => {
284 const enc: EH.PE = @bitCast(try aug_data.takeByte());
285 const endian: Endian = .little; // irrelevant because we're discarding the value anyway
286 _ = try readEhPointerAbs(&r, enc.type, addr_size_bytes, endian); // we ignore the personality routine; endianness is irrelevant since we're discarding
287 },
288 'R' => fde_pointer_enc = @bitCast(try aug_data.takeByte()),
289 'S' => is_signal_frame = true,
290 'B', 'G' => {},
291 else => return bad(),
292 };
293 break :aug .{ fde_pointer_enc, is_signal_frame };
294 };
354295
355 const initial_instructions = cie_bytes[fbr.seek..];
356296 return .{
357 .length_offset = length_offset,
358297 .version = version,
359 .address_size = address_size,
360 .format = format,
298 .addr_size_bytes = addr_size_bytes,
361299 .segment_selector_size = segment_selector_size,
362300 .code_alignment_factor = code_alignment_factor,
363301 .data_alignment_factor = data_alignment_factor,
364302 .return_address_register = return_address_register,
365 .aug_str = aug_str,
366 .aug_data = aug_data,
367 .lsda_pointer_enc = lsda_pointer_enc,
368 .personality_enc = personality_enc,
369 .personality_routine_pointer = personality_routine_pointer,
370303 .fde_pointer_enc = fde_pointer_enc,
371 .initial_instructions = initial_instructions,
304 .is_signal_frame = is_signal_frame,
305 .augmentation_kind = aug_kind,
306 .initial_instructions = r.buffered(),
372307 };
373308 }
374309};
375310
376311pub const FrameDescriptionEntry = struct {
377 // Offset into eh_frame where the CIE for this FDE is stored
378 cie_length_offset: u64,
379
380312 pc_begin: u64,
381313 pc_range: u64,
382 lsda_pointer: ?u64,
383 aug_data: []const u8,
384314 instructions: []const u8,
385315
386316 /// This function expects to read the FDE starting at the PC Begin field.
387317 /// The returned struct references memory backed by `fde_bytes`.
388 ///
389 /// `pc_rel_offset` specifies an offset to be applied to pc_rel_base values
390 /// used when decoding pointers. This should be set to zero if fde_bytes is
391 /// backed by the memory of a .eh_frame / .debug_frame section in the running executable.
392 /// Otherwise, it should be the relative offset to translate addresses from
393 /// where the section is currently stored in memory, to where it *would* be
394 /// stored at runtime: section base addr - backing data base ptr.
395 ///
396 /// Similarly, `is_runtime` specifies this function is being called on a runtime
397 /// section, and so indirect pointers can be followed.
398318 pub fn parse(
319 /// The virtual address of the FDE we're parsing, *excluding* its entry header (i.e. the
320 /// address is after the header). If `fde_bytes` is backed by the memory of a loaded
321 /// module's `.eh_frame` section, this will equal `fde_bytes.ptr`.
322 fde_vaddr: u64,
399323 fde_bytes: []const u8,
400 pc_rel_offset: i64,
401 is_runtime: bool,
402324 cie: CommonInformationEntry,
403 addr_size_bytes: u8,
404325 endian: Endian,
405326 ) !FrameDescriptionEntry {
406 if (addr_size_bytes > 8) return error.InvalidAddrSize;
407
408 var fbr: Reader = .fixed(fde_bytes);
409
410 const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{
411 .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset),
412 .follow_indirect = is_runtime,
413 }, endian) orelse return bad();
414
415 const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{
416 .pc_rel_base = 0,
417 .follow_indirect = false,
418 }, endian) orelse return bad();
419
420 var aug_data: []const u8 = &[_]u8{};
421 const lsda_pointer = if (cie.aug_str.len > 0) blk: {
422 const aug_data_len = try fbr.takeLeb128(usize);
423 const aug_data_start = fbr.seek;
424 aug_data = fde_bytes[aug_data_start..][0..aug_data_len];
425
426 const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit)
427 try readEhPointer(&fbr, cie.lsda_pointer_enc, addr_size_bytes, .{
428 .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset),
429 .follow_indirect = is_runtime,
430 }, endian)
431 else
432 null;
433
434 fbr.seek = aug_data_start + aug_data_len;
435 break :blk lsda_pointer;
436 } else null;
437
438 const instructions = fde_bytes[fbr.seek..];
327 if (cie.segment_selector_size != 0) return error.UnsupportedAddrSize;
328
329 var r: Reader = .fixed(fde_bytes);
330
331 const pc_begin = try readEhPointer(&r, cie.fde_pointer_enc, cie.addr_size_bytes, .{
332 .pc_rel_base = fde_vaddr,
333 }, endian);
334
335 // I swear I'm not kidding when I say that PC Range is encoded with `cie.fde_pointer_enc`, but ignoring `rel`.
336 const pc_range = switch (try readEhPointerAbs(&r, cie.fde_pointer_enc.type, cie.addr_size_bytes, endian)) {
337 .unsigned => |x| x,
338 .signed => |x| cast(u64, x) orelse return bad(),
339 };
340
341 switch (cie.augmentation_kind) {
342 .none, .gcc_eh => {},
343 .lsb_z => {
344 // There is augmentation data, but it's irrelevant to us -- it
345 // only contains the LSDA pointer, which we don't care about.
346 const aug_data_len = try r.takeLeb128(u64);
347 _ = try r.discardAll(aug_data_len);
348 },
349 }
350
439351 return .{
440 .cie_length_offset = cie.length_offset,
441352 .pc_begin = pc_begin,
442353 .pc_range = pc_range,
443 .lsda_pointer = lsda_pointer,
444 .aug_data = aug_data,
445 .instructions = instructions,
354 .instructions = r.buffered(),
446355 };
447356 }
448357};
449358
450/// If `.eh_frame_hdr` is present, then only the header needs to be parsed. Otherwise, `.eh_frame`
451/// and `.debug_frame` are scanned and a sorted list of FDEs is built for binary searching during
452/// unwinding. Even if `.eh_frame_hdr` is used, we may find during unwinding that it's incomplete,
453/// in which case we build the sorted list of FDEs at that point.
454///
455/// See also `scanCieFdeInfo`.
456pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void {
457 const endian = di.endian;
458
459 if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {
460 var fbr: Reader = .fixed(eh_frame_hdr);
461
462 const version = try fbr.takeByte();
463 if (version != 1) break :blk;
464
465 const eh_frame_ptr_enc = try fbr.takeByte();
466 if (eh_frame_ptr_enc == EH.PE.omit) break :blk;
467 const fde_count_enc = try fbr.takeByte();
468 if (fde_count_enc == EH.PE.omit) break :blk;
469 const table_enc = try fbr.takeByte();
470 if (table_enc == EH.PE.omit) break :blk;
471
472 const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{
473 .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]),
474 .follow_indirect = true,
475 }, endian) orelse return bad()) orelse return bad();
476
477 const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{
478 .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]),
479 .follow_indirect = true,
480 }, endian) orelse return bad()) orelse return bad();
481
482 const entry_size = try ExceptionFrameHeader.entrySize(table_enc);
483 const entries_len = fde_count * entry_size;
484 if (entries_len > eh_frame_hdr.len - fbr.seek) return bad();
485
486 di.eh_frame_hdr = .{
487 .eh_frame_ptr = eh_frame_ptr,
488 .table_enc = table_enc,
489 .fde_count = fde_count,
490 .entries = eh_frame_hdr[fbr.seek..][0..entries_len],
491 };
359pub fn scanDebugFrame(
360 unwind: *Unwind,
361 gpa: Allocator,
362 section_vaddr: u64,
363 section_bytes: []const u8,
364 addr_size_bytes: u8,
365 endian: Endian,
366) void {
367 assert(unwind.debug_frame == null);
368
369 var fbr: Reader = .fixed(section_bytes);
370 var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
371 defer fde_list.deinit(gpa);
372 while (fbr.seek < fbr.buffer.len) {
373 const entry_offset = fbr.seek;
374 switch (try EntryHeader.read(&fbr, fbr.seek, .debug_frame, endian)) {
375 // Ignore CIEs; we only need them to parse the FDEs!
376 .cie => |info| {
377 try fbr.discardAll(info.bytes_len);
378 continue;
379 },
380 .fde => |info| {
381 const cie: CommonInformationEntry = cie: {
382 var cie_reader: Reader = .fixed(section_bytes[info.cie_offset..]);
383 const cie_info = switch (try EntryHeader.read(&cie_reader, info.cie_offset, .debug_frame, endian)) {
384 .cie => |cie_info| cie_info,
385 .fde, .terminator => return bad(), // This is meant to be a CIE
386 };
387 break :cie try .parse(try cie_reader.take(cie_info.bytes_len), .debug_frame, addr_size_bytes);
388 };
389 const fde: FrameDescriptionEntry = try .parse(
390 section_vaddr + fbr.seek,
391 try fbr.take(info.bytes_len),
392 cie,
393 endian,
394 );
395 try fde_list.append(.{
396 .pc_begin = fde.pc_begin,
397 .fde_offset = entry_offset, // *not* `fde_offset`, because we need to include the entry header
398 });
399 },
400 .terminator => return bad(), // DWARF `.debug_frame` isn't meant to have terminators
401 }
402 }
403 const fde_slice = try fde_list.toOwnedSlice(gpa);
404 errdefer comptime unreachable;
405 std.mem.sortUnstable(SortedFdeEntry, fde_slice, {}, struct {
406 fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool {
407 ctx;
408 return a.pc_begin < b.pc_begin;
409 }
410 }.lessThan);
411 unwind.debug_frame = .{ .data = section_bytes, .sorted_fdes = fde_slice };
412}
413
414pub fn scanEhFrame(
415 unwind: *Unwind,
416 gpa: Allocator,
417 header: EhFrameHeader,
418 section_bytes_ptr: [*]const u8,
419 /// This is separate from `section_bytes_ptr` because it is unknown when `.eh_frame` is accessed
420 /// through the pointer in the `.eh_frame_hdr` section. If this is non-`null`, we avoid reading
421 /// past this number of bytes, but if `null`, we must assume that the `.eh_frame` data has a
422 /// valid terminator.
423 section_bytes_len: ?usize,
424 addr_size_bytes: u8,
425 endian: Endian,
426) !void {
427 assert(unwind.eh_frame == null);
428
429 const section_bytes: []const u8 = bytes: {
430 // If the length is unknown, let the slice span from `section_bytes_ptr` to the end of memory.
431 const len = section_bytes_len orelse (std.math.maxInt(usize) - @intFromPtr(section_bytes_ptr));
432 break :bytes section_bytes_ptr[0..len];
433 };
492434
493 // No need to scan .eh_frame, we have a binary search table already
435 if (header.search_table != null) {
436 // No need to populate `sorted_fdes`, the header contains a search table.
437 unwind.eh_frame = .{
438 .header = header,
439 .eh_frame_data = section_bytes,
440 .sorted_fdes = null,
441 };
494442 return;
495443 }
496444
497 try di.scanCieFdeInfo(allocator, base_address);
445 // We aren't told the length of this section. Luckily, we don't need it, because there will be
446 // an `EntryHeader.terminator` after the last CIE/FDE. Just make a `Reader` which will give us
447 // alllll of the bytes!
448 var fbr: Reader = .fixed(section_bytes);
449
450 var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
451 defer fde_list.deinit(gpa);
452
453 while (true) {
454 const entry_offset = fbr.seek;
455 switch (try EntryHeader.read(&fbr, fbr.seek, .eh_frame, endian)) {
456 // Ignore CIEs; we only need them to parse the FDEs!
457 .cie => |info| {
458 try fbr.discardAll(info.bytes_len);
459 continue;
460 },
461 .fde => |info| {
462 const cie: CommonInformationEntry = cie: {
463 var cie_reader: Reader = .fixed(section_bytes[info.cie_offset..]);
464 const cie_info = switch (try EntryHeader.read(&cie_reader, info.cie_offset, .eh_frame, endian)) {
465 .cie => |cie_info| cie_info,
466 .fde, .terminator => return bad(), // This is meant to be a CIE
467 };
468 break :cie try .parse(try cie_reader.take(cie_info.bytes_len), .eh_frame, addr_size_bytes);
469 };
470 const fde: FrameDescriptionEntry = try .parse(
471 header.eh_frame_vaddr + fbr.seek,
472 try fbr.take(info.bytes_len),
473 cie,
474 endian,
475 );
476 try fde_list.append(gpa, .{
477 .pc_begin = fde.pc_begin,
478 .fde_offset = entry_offset, // *not* `fde_offset`, because we need to include the entry header
479 });
480 },
481 // Unlike `.debug_frame`, the `.eh_frame` section does have a terminator CIE -- this is
482 // necessary because `header` doesn't include the length of the `.eh_frame` section
483 .terminator => break,
484 }
485 }
486 const fde_slice = try fde_list.toOwnedSlice(gpa);
487 errdefer comptime unreachable;
488 std.mem.sortUnstable(SortedFdeEntry, fde_slice, {}, struct {
489 fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool {
490 ctx;
491 return a.pc_begin < b.pc_begin;
492 }
493 }.lessThan);
494 unwind.eh_frame = .{
495 .header = header,
496 .eh_frame_data = section_bytes,
497 .sorted_fdes = fde_slice,
498 };
498499}
499500
500/// Scan `.eh_frame` and `.debug_frame` and build a sorted list of FDEs for binary searching during
501/// unwinding.
502pub fn scanCieFdeInfo(unwind: *Unwind, allocator: Allocator, endian: Endian, base_address: usize) !void {
503 const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame };
504 for (frame_sections) |frame_section| {
505 if (unwind.section(frame_section)) |section_data| {
506 var fbr: Reader = .fixed(section_data);
507 while (fbr.seek < fbr.buffer.len) {
508 const entry_header = try EntryHeader.read(&fbr, frame_section, endian);
509 switch (entry_header.type) {
510 .cie => {
511 const cie = try CommonInformationEntry.parse(
512 entry_header.entry_bytes,
513 unwind.sectionVirtualOffset(frame_section, base_address).?,
514 true,
515 entry_header.format,
516 frame_section,
517 entry_header.length_offset,
518 @sizeOf(usize),
519 endian,
520 );
521 try unwind.cie_map.put(allocator, entry_header.length_offset, cie);
522 },
523 .fde => |cie_offset| {
524 const cie = unwind.cie_map.get(cie_offset) orelse return bad();
525 const fde = try FrameDescriptionEntry.parse(
526 entry_header.entry_bytes,
527 unwind.sectionVirtualOffset(frame_section, base_address).?,
528 true,
529 cie,
530 @sizeOf(usize),
531 endian,
532 );
533 try unwind.fde_list.append(allocator, fde);
534 },
535 .terminator => break,
536 }
537 }
538
539 std.mem.sortUnstable(FrameDescriptionEntry, unwind.fde_list.items, {}, struct {
540 fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool {
541 _ = ctx;
542 return a.pc_begin < b.pc_begin;
543 }
544 }.lessThan);
501/// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must
502/// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`.
503pub fn findFdeOffset(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: Endian) !?u64 {
504 // We'll break from this block only if we have a manually-constructed search table.
505 const sorted_fdes: []const SortedFdeEntry = fdes: {
506 if (unwind.debug_frame) |df| break :fdes df.sorted_fdes;
507 if (unwind.eh_frame) |eh_frame| {
508 if (eh_frame.sorted_fdes) |fdes| break :fdes fdes;
509 // Use the search table from the `.eh_frame_hdr` section rather than one of our own
510 return eh_frame.header.findEntry(pc, addr_size_bytes, endian);
545511 }
546 }
512 // We have no available unwind info
513 return null;
514 };
515 const first_bad_idx = std.sort.partitionPoint(SortedFdeEntry, sorted_fdes, pc, struct {
516 fn canIncludePc(target_pc: u64, entry: SortedFdeEntry) bool {
517 return target_pc >= entry.pc_begin; // i.e. does 'entry_pc..<last pc>' include 'target_pc'
518 }
519 }.canIncludePc);
520 // `first_bad_idx` is the index of the first FDE whose `pc_begin` is too high to include `pc`.
521 // So if any FDE matches, it'll be the one at `first_bad_idx - 1` (maybe false positive).
522 if (first_bad_idx == 0) return null;
523 return sorted_fdes[first_bad_idx - 1].fde_offset;
524}
525
526pub fn loadFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endian: Endian) !struct { Format, CommonInformationEntry, FrameDescriptionEntry } {
527 const section_bytes: []const u8, const section_vaddr: u64, const section: Section = s: {
528 if (unwind.debug_frame) |df| break :s .{ df.data, if (true) @panic("MLUGG TODO"), .debug_frame };
529 if (unwind.eh_frame) |ef| break :s .{ ef.eh_frame_data, ef.header.eh_frame_vaddr, .eh_frame };
530 unreachable; // how did you get `fde_offset`?!
531 };
532
533 var fde_reader: Reader = .fixed(section_bytes[fde_offset..]);
534 const fde_info = switch (try EntryHeader.read(&fde_reader, fde_offset, section, endian)) {
535 .fde => |info| info,
536 .cie, .terminator => return bad(), // This is meant to be an FDE
537 };
538
539 const cie_offset = fde_info.cie_offset;
540 var cie_reader: Reader = .fixed(section_bytes[cie_offset..]);
541 const cie_info = switch (try EntryHeader.read(&cie_reader, cie_offset, section, endian)) {
542 .cie => |info| info,
543 .fde, .terminator => return bad(), // This is meant to be a CIE
544 };
545
546 const cie: CommonInformationEntry = try .parse(
547 try cie_reader.take(cie_info.bytes_len),
548 section,
549 addr_size_bytes,
550 );
551 const fde: FrameDescriptionEntry = try .parse(
552 section_vaddr + fde_offset + fde_reader.seek,
553 try fde_reader.take(fde_info.bytes_len),
554 cie,
555 endian,
556 );
557
558 return .{ cie_info.format, cie, fde };
547559}
548560
549561const EhPointerContext = struct {
550562 // The address of the pointer field itself
551563 pc_rel_base: u64,
552564
553 // Whether or not to follow indirect pointers. This should only be
554 // used when decoding pointers at runtime using the current process's
555 // debug info
556 follow_indirect: bool,
557
558565 // These relative addressing modes are only used in specific cases, and
559566 // might not be available / required in all parsing contexts
560567 data_rel_base: ?u64 = null,
561568 text_rel_base: ?u64 = null,
562569 function_rel_base: ?u64 = null,
563570};
564
565fn readEhPointer(fbr: *Reader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !?u64 {
566 if (enc == EH.PE.omit) return null;
567
568 const value: union(enum) {
569 signed: i64,
570 unsigned: u64,
571 } = switch (enc & EH.PE.type_mask) {
572 EH.PE.absptr => .{
571/// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`.
572fn readEhPointerAbs(r: *Reader, enc_ty: EH.PE.Type, addr_size_bytes: u8, endian: Endian) !union(enum) {
573 signed: i64,
574 unsigned: u64,
575} {
576 return switch (enc_ty) {
577 .absptr => .{
573578 .unsigned = switch (addr_size_bytes) {
574 2 => try fbr.takeInt(u16, endian),
575 4 => try fbr.takeInt(u32, endian),
576 8 => try fbr.takeInt(u64, endian),
577 else => return error.InvalidAddrSize,
579 2 => try r.takeInt(u16, endian),
580 4 => try r.takeInt(u32, endian),
581 8 => try r.takeInt(u64, endian),
582 else => return error.UnsupportedAddrSize,
578583 },
579584 },
580 EH.PE.uleb128 => .{ .unsigned = try fbr.takeLeb128(u64) },
581 EH.PE.udata2 => .{ .unsigned = try fbr.takeInt(u16, endian) },
582 EH.PE.udata4 => .{ .unsigned = try fbr.takeInt(u32, endian) },
583 EH.PE.udata8 => .{ .unsigned = try fbr.takeInt(u64, endian) },
584 EH.PE.sleb128 => .{ .signed = try fbr.takeLeb128(i64) },
585 EH.PE.sdata2 => .{ .signed = try fbr.takeInt(i16, endian) },
586 EH.PE.sdata4 => .{ .signed = try fbr.takeInt(i32, endian) },
587 EH.PE.sdata8 => .{ .signed = try fbr.takeInt(i64, endian) },
585 .uleb128 => .{ .unsigned = try r.takeLeb128(u64) },
586 .udata2 => .{ .unsigned = try r.takeInt(u16, endian) },
587 .udata4 => .{ .unsigned = try r.takeInt(u32, endian) },
588 .udata8 => .{ .unsigned = try r.takeInt(u64, endian) },
589 .sleb128 => .{ .signed = try r.takeLeb128(i64) },
590 .sdata2 => .{ .signed = try r.takeInt(i16, endian) },
591 .sdata4 => .{ .signed = try r.takeInt(i32, endian) },
592 .sdata8 => .{ .signed = try r.takeInt(i64, endian) },
588593 else => return bad(),
589594 };
590
591 const base = switch (enc & EH.PE.rel_mask) {
592 EH.PE.pcrel => ctx.pc_rel_base,
593 EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified,
594 EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified,
595 EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified,
596 else => null,
595}
596/// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`.
597fn readEhPointer(fbr: *Reader, enc: EH.PE, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !u64 {
598 const offset = try readEhPointerAbs(fbr, enc.type, addr_size_bytes, endian);
599 const base = switch (enc.rel) {
600 .abs, .aligned => 0,
601 .pcrel => ctx.pc_rel_base,
602 .textrel => ctx.text_rel_base orelse return bad(),
603 .datarel => ctx.data_rel_base orelse return bad(),
604 .funcrel => ctx.function_rel_base orelse return bad(),
605 .indirect => return bad(), // GCC extension; not supported
606 _ => return bad(),
597607 };
598
599 const ptr: u64 = if (base) |b| switch (value) {
600 .signed => |s| @intCast(try std.math.add(i64, s, @as(i64, @intCast(b)))),
608 return switch (offset) {
609 .signed => |s| @intCast(try std.math.add(i64, s, @as(i64, @intCast(base)))),
601610 // absptr can actually contain signed values in some cases (aarch64 MachO)
602 .unsigned => |u| u +% b,
603 } else switch (value) {
604 .signed => |s| @as(u64, @intCast(s)),
605 .unsigned => |u| u,
611 .unsigned => |u| u +% base,
606612 };
607
608 if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) {
609 if (@sizeOf(usize) != addr_size_bytes) {
610 // See the documentation for `follow_indirect`
611 return error.NonNativeIndirection;
612 }
613
614 const native_ptr = cast(usize, ptr) orelse return error.PointerOverflow;
615 return switch (addr_size_bytes) {
616 2, 4, 8 => return @as(*const usize, @ptrFromInt(native_ptr)).*,
617 else => return error.UnsupportedAddrSize,
618 };
619 } else {
620 return ptr;
621 }
622613}
623614
624fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize {
625 if (pc_rel_offset < 0) {
626 return std.math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset)));
627 } else {
628 return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset)));
629 }
615/// Like `Reader.fixed`, but when the length of the data is unknown and we just want to allow
616/// reading indefinitely.
617fn maxSlice(ptr: [*]const u8) []const u8 {
618 const len = std.math.maxInt(usize) - @intFromPtr(ptr);
619 return ptr[0..len];
630620}
631621
632622const Allocator = std.mem.Allocator;
lib/std/debug/Dwarf/Unwind/VirtualMachine.zig created+298
......@@ -0,0 +1,298 @@
1//! Virtual machine that evaluates DWARF call frame instructions
2
3/// See section 6.4.1 of the DWARF5 specification for details on each
4pub const RegisterRule = union(enum) {
5 /// The spec says that the default rule for each column is the undefined rule.
6 /// However, it also allows ABI / compiler authors to specify alternate defaults, so
7 /// there is a distinction made here.
8 default: void,
9 undefined: void,
10 same_value: void,
11 /// offset(N)
12 offset: i64,
13 /// val_offset(N)
14 val_offset: i64,
15 /// register(R)
16 register: u8,
17 /// expression(E)
18 expression: []const u8,
19 /// val_expression(E)
20 val_expression: []const u8,
21 /// Augmenter-defined rule
22 architectural: void,
23};
24
25/// Each row contains unwinding rules for a set of registers.
26pub const Row = struct {
27 /// Offset from `FrameDescriptionEntry.pc_begin`
28 offset: u64 = 0,
29 /// Special-case column that defines the CFA (Canonical Frame Address) rule.
30 /// The register field of this column defines the register that CFA is derived from.
31 cfa: Column = .{},
32 /// The register fields in these columns define the register the rule applies to.
33 columns: ColumnRange = .{},
34 /// Indicates that the next write to any column in this row needs to copy
35 /// the backing column storage first, as it may be referenced by previous rows.
36 copy_on_write: bool = false,
37};
38
39pub const Column = struct {
40 register: ?u8 = null,
41 rule: RegisterRule = .{ .default = {} },
42};
43
44const ColumnRange = struct {
45 /// Index into `columns` of the first column in this row.
46 start: usize = undefined,
47 len: u8 = 0,
48};
49
50columns: std.ArrayList(Column) = .empty,
51stack: std.ArrayList(ColumnRange) = .empty,
52current_row: Row = .{},
53
54/// The result of executing the CIE's initial_instructions
55cie_row: ?Row = null,
56
57pub fn deinit(self: *VirtualMachine, gpa: Allocator) void {
58 self.stack.deinit(gpa);
59 self.columns.deinit(gpa);
60 self.* = undefined;
61}
62
63pub fn reset(self: *VirtualMachine) void {
64 self.stack.clearRetainingCapacity();
65 self.columns.clearRetainingCapacity();
66 self.current_row = .{};
67 self.cie_row = null;
68}
69
70/// Return a slice backed by the row's non-CFA columns
71pub fn rowColumns(self: VirtualMachine, row: Row) []Column {
72 if (row.columns.len == 0) return &.{};
73 return self.columns.items[row.columns.start..][0..row.columns.len];
74}
75
76/// Either retrieves or adds a column for `register` (non-CFA) in the current row.
77fn getOrAddColumn(self: *VirtualMachine, gpa: Allocator, register: u8) !*Column {
78 for (self.rowColumns(self.current_row)) |*c| {
79 if (c.register == register) return c;
80 }
81
82 if (self.current_row.columns.len == 0) {
83 self.current_row.columns.start = self.columns.items.len;
84 }
85 self.current_row.columns.len += 1;
86
87 const column = try self.columns.addOne(gpa);
88 column.* = .{
89 .register = register,
90 };
91
92 return column;
93}
94
95/// Runs the CIE instructions, then the FDE instructions. Execution halts
96/// once the row that corresponds to `pc` is known, and the row is returned.
97pub fn runTo(
98 self: *VirtualMachine,
99 gpa: Allocator,
100 pc: u64,
101 cie: Dwarf.Unwind.CommonInformationEntry,
102 fde: Dwarf.Unwind.FrameDescriptionEntry,
103 addr_size_bytes: u8,
104 endian: std.builtin.Endian,
105) !Row {
106 assert(self.cie_row == null);
107 assert(pc >= fde.pc_begin);
108 assert(pc < fde.pc_begin + fde.pc_range);
109
110 var prev_row: Row = self.current_row;
111
112 const instruction_slices: [2][]const u8 = .{
113 cie.initial_instructions,
114 fde.instructions,
115 };
116 for (instruction_slices, [2]bool{ true, false }) |slice, is_cie_stream| {
117 var stream: std.Io.Reader = .fixed(slice);
118 while (stream.seek < slice.len) {
119 const instruction: Dwarf.call_frame.Instruction = try .read(&stream, addr_size_bytes, endian);
120 prev_row = try self.step(gpa, cie, is_cie_stream, instruction);
121 if (pc < fde.pc_begin + self.current_row.offset) return prev_row;
122 }
123 }
124
125 return self.current_row;
126}
127
128fn resolveCopyOnWrite(self: *VirtualMachine, gpa: Allocator) !void {
129 if (!self.current_row.copy_on_write) return;
130
131 const new_start = self.columns.items.len;
132 if (self.current_row.columns.len > 0) {
133 try self.columns.ensureUnusedCapacity(gpa, self.current_row.columns.len);
134 self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row));
135 self.current_row.columns.start = new_start;
136 }
137}
138
139/// Executes a single instruction.
140/// If this instruction is from the CIE, `is_initial` should be set.
141/// Returns the value of `current_row` before executing this instruction.
142pub fn step(
143 self: *VirtualMachine,
144 gpa: Allocator,
145 cie: Dwarf.Unwind.CommonInformationEntry,
146 is_initial: bool,
147 instruction: Dwarf.call_frame.Instruction,
148) !Row {
149 // CIE instructions must be run before FDE instructions
150 assert(!is_initial or self.cie_row == null);
151 if (!is_initial and self.cie_row == null) {
152 self.cie_row = self.current_row;
153 self.current_row.copy_on_write = true;
154 }
155
156 const prev_row = self.current_row;
157 switch (instruction) {
158 .set_loc => |i| {
159 if (i.address <= self.current_row.offset) return error.InvalidOperation;
160 if (cie.segment_selector_size != 0) return error.InvalidOperation; // unsupported
161 // TODO: Check cie.segment_selector_size != 0 for DWARFV4
162 self.current_row.offset = i.address;
163 },
164 inline .advance_loc,
165 .advance_loc1,
166 .advance_loc2,
167 .advance_loc4,
168 => |i| {
169 self.current_row.offset += i.delta * cie.code_alignment_factor;
170 self.current_row.copy_on_write = true;
171 },
172 inline .offset,
173 .offset_extended,
174 .offset_extended_sf,
175 => |i| {
176 try self.resolveCopyOnWrite(gpa);
177 const column = try self.getOrAddColumn(gpa, i.register);
178 column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor };
179 },
180 inline .restore,
181 .restore_extended,
182 => |i| {
183 try self.resolveCopyOnWrite(gpa);
184 if (self.cie_row) |cie_row| {
185 const column = try self.getOrAddColumn(gpa, i.register);
186 column.rule = for (self.rowColumns(cie_row)) |cie_column| {
187 if (cie_column.register == i.register) break cie_column.rule;
188 } else .{ .default = {} };
189 } else return error.InvalidOperation;
190 },
191 .nop => {},
192 .undefined => |i| {
193 try self.resolveCopyOnWrite(gpa);
194 const column = try self.getOrAddColumn(gpa, i.register);
195 column.rule = .{ .undefined = {} };
196 },
197 .same_value => |i| {
198 try self.resolveCopyOnWrite(gpa);
199 const column = try self.getOrAddColumn(gpa, i.register);
200 column.rule = .{ .same_value = {} };
201 },
202 .register => |i| {
203 try self.resolveCopyOnWrite(gpa);
204 const column = try self.getOrAddColumn(gpa, i.register);
205 column.rule = .{ .register = i.target_register };
206 },
207 .remember_state => {
208 try self.stack.append(gpa, self.current_row.columns);
209 self.current_row.copy_on_write = true;
210 },
211 .restore_state => {
212 const restored_columns = self.stack.pop() orelse return error.InvalidOperation;
213 self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len);
214 try self.columns.ensureUnusedCapacity(gpa, restored_columns.len);
215
216 self.current_row.columns.start = self.columns.items.len;
217 self.current_row.columns.len = restored_columns.len;
218 self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]);
219 },
220 .def_cfa => |i| {
221 try self.resolveCopyOnWrite(gpa);
222 self.current_row.cfa = .{
223 .register = i.register,
224 .rule = .{ .val_offset = @intCast(i.offset) },
225 };
226 },
227 .def_cfa_sf => |i| {
228 try self.resolveCopyOnWrite(gpa);
229 self.current_row.cfa = .{
230 .register = i.register,
231 .rule = .{ .val_offset = i.offset * cie.data_alignment_factor },
232 };
233 },
234 .def_cfa_register => |i| {
235 try self.resolveCopyOnWrite(gpa);
236 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
237 self.current_row.cfa.register = i.register;
238 },
239 .def_cfa_offset => |i| {
240 try self.resolveCopyOnWrite(gpa);
241 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
242 self.current_row.cfa.rule = .{
243 .val_offset = @intCast(i.offset),
244 };
245 },
246 .def_cfa_offset_sf => |i| {
247 try self.resolveCopyOnWrite(gpa);
248 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
249 self.current_row.cfa.rule = .{
250 .val_offset = i.offset * cie.data_alignment_factor,
251 };
252 },
253 .def_cfa_expression => |i| {
254 try self.resolveCopyOnWrite(gpa);
255 self.current_row.cfa.register = undefined;
256 self.current_row.cfa.rule = .{
257 .expression = i.block,
258 };
259 },
260 .expression => |i| {
261 try self.resolveCopyOnWrite(gpa);
262 const column = try self.getOrAddColumn(gpa, i.register);
263 column.rule = .{
264 .expression = i.block,
265 };
266 },
267 .val_offset => |i| {
268 try self.resolveCopyOnWrite(gpa);
269 const column = try self.getOrAddColumn(gpa, i.register);
270 column.rule = .{
271 .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor,
272 };
273 },
274 .val_offset_sf => |i| {
275 try self.resolveCopyOnWrite(gpa);
276 const column = try self.getOrAddColumn(gpa, i.register);
277 column.rule = .{
278 .val_offset = i.offset * cie.data_alignment_factor,
279 };
280 },
281 .val_expression => |i| {
282 try self.resolveCopyOnWrite(gpa);
283 const column = try self.getOrAddColumn(gpa, i.register);
284 column.rule = .{
285 .val_expression = i.block,
286 };
287 },
288 }
289
290 return prev_row;
291}
292
293const std = @import("../../../std.zig");
294const assert = std.debug.assert;
295const Allocator = std.mem.Allocator;
296const Dwarf = std.debug.Dwarf;
297
298const VirtualMachine = @This();
lib/std/debug/Dwarf/call_frame.zig+16-20
......@@ -1,12 +1,5 @@
1const builtin = @import("builtin");
21const std = @import("../../std.zig");
3const mem = std.mem;
4const debug = std.debug;
5const leb = std.leb;
6const DW = std.dwarf;
7const abi = std.debug.Dwarf.abi;
8const assert = std.debug.assert;
9const native_endian = builtin.cpu.arch.endian();
2const Reader = std.Io.Reader;
103
114/// TODO merge with std.dwarf.CFA
125const Opcode = enum(u8) {
......@@ -51,9 +44,13 @@ const Opcode = enum(u8) {
5144 pub const hi_user = 0x3f;
5245};
5346
54fn readBlock(reader: *std.Io.Reader) ![]const u8 {
47/// The returned slice points into `reader.buffer`.
48fn readBlock(reader: *Reader) ![]const u8 {
5549 const block_len = try reader.takeLeb128(usize);
56 return reader.take(block_len);
50 return reader.take(block_len) catch |err| switch (err) {
51 error.EndOfStream => return error.InvalidOperand,
52 error.ReadFailed => |e| return e,
53 };
5754}
5855
5956pub const Instruction = union(Opcode) {
......@@ -140,8 +137,9 @@ pub const Instruction = union(Opcode) {
140137 block: []const u8,
141138 },
142139
140 /// `reader` must be a `Reader.fixed` so that regions of its buffer are never invalidated.
143141 pub fn read(
144 reader: *std.Io.Reader,
142 reader: *Reader,
145143 addr_size_bytes: u8,
146144 endian: std.builtin.Endian,
147145 ) !Instruction {
......@@ -173,16 +171,14 @@ pub const Instruction = union(Opcode) {
173171 .restore,
174172 => unreachable,
175173 .nop => .{ .nop = {} },
176 .set_loc => .{
177 .set_loc = .{
178 .address = switch (addr_size_bytes) {
179 2 => try reader.takeInt(u16, endian),
180 4 => try reader.takeInt(u32, endian),
181 8 => try reader.takeInt(u64, endian),
182 else => return error.InvalidAddrSize,
183 },
174 .set_loc => .{ .set_loc = .{
175 .address = switch (addr_size_bytes) {
176 2 => try reader.takeInt(u16, endian),
177 4 => try reader.takeInt(u32, endian),
178 8 => try reader.takeInt(u64, endian),
179 else => return error.UnsupportedAddrSize,
184180 },
185 },
181 } },
186182 .advance_loc1 => .{
187183 .advance_loc1 = .{ .delta = try reader.takeByte() },
188184 },
lib/std/debug/SelfInfo.zig+1114-1771
......@@ -13,7 +13,6 @@ const windows = std.os.windows;
1313const macho = std.macho;
1414const fs = std.fs;
1515const coff = std.coff;
16const pdb = std.pdb;
1716const assert = std.debug.assert;
1817const posix = std.posix;
1918const elf = std.elf;
......@@ -22,86 +21,37 @@ const Pdb = std.debug.Pdb;
2221const File = std.fs.File;
2322const math = std.math;
2423const testing = std.testing;
25const StackIterator = std.debug.StackIterator;
2624const regBytes = Dwarf.abi.regBytes;
2725const regValueNative = Dwarf.abi.regValueNative;
2826
2927const SelfInfo = @This();
3028
31const root = @import("root");
32
33allocator: Allocator,
34address_map: std.AutoHashMapUnmanaged(usize, Module),
35modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void,
36
37pub const OpenError = error{
38 MissingDebugInfo,
39 UnsupportedOperatingSystem,
40} || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).@"fn".return_type.?).error_union.error_set;
41
42pub fn open(allocator: Allocator) OpenError!SelfInfo {
43 if (builtin.strip_debug_info)
44 return error.MissingDebugInfo;
45 switch (native_os) {
46 .linux,
47 .freebsd,
48 .netbsd,
49 .dragonfly,
50 .openbsd,
51 .macos,
52 .solaris,
53 .illumos,
54 .windows,
55 => return try SelfInfo.init(allocator),
56 else => return error.UnsupportedOperatingSystem,
57 }
58}
59
60pub fn init(allocator: Allocator) !SelfInfo {
61 var debug_info: SelfInfo = .{
62 .allocator = allocator,
63 .address_map = .empty,
64 .modules = if (native_os == .windows) .{} else {},
65 };
66
67 if (native_os == .windows) {
68 errdefer debug_info.modules.deinit(allocator);
69
70 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
71 if (handle == windows.INVALID_HANDLE_VALUE) {
72 switch (windows.GetLastError()) {
73 else => |err| return windows.unexpectedError(err),
74 }
75 }
76 defer windows.CloseHandle(handle);
77
78 var module_entry: windows.MODULEENTRY32 = undefined;
79 module_entry.dwSize = @sizeOf(windows.MODULEENTRY32);
80 if (windows.kernel32.Module32First(handle, &module_entry) == 0) {
81 return error.MissingDebugInfo;
82 }
83
84 var module_valid = true;
85 while (module_valid) {
86 const module_info = try debug_info.modules.addOne(allocator);
87 const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{};
88 errdefer allocator.free(name);
89
90 module_info.* = .{
91 .base_address = @intFromPtr(module_entry.modBaseAddr),
92 .size = module_entry.modBaseSize,
93 .name = name,
94 .handle = module_entry.hModule,
95 };
96
97 module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1;
98 }
99 }
29/// MLUGG TODO: what if this field had a less stupid name...
30address_map: std.AutoHashMapUnmanaged(usize, Module.DebugInfo),
31
32module_cache: if (native_os == .windows) std.ArrayListUnmanaged(windows.MODULEENTRY32) else void,
33
34pub const target_supported: bool = switch (native_os) {
35 .linux,
36 .freebsd,
37 .netbsd,
38 .dragonfly,
39 .openbsd,
40 .macos,
41 .solaris,
42 .illumos,
43 .windows,
44 => true,
45 else => false,
46};
10047
101 return debug_info;
102}
48pub const init: SelfInfo = .{
49 .address_map = .empty,
50 .module_cache = if (native_os == .windows) .empty,
51};
10352
10453pub fn deinit(self: *SelfInfo) void {
54 // MLUGG TODO: that's amusing, this function is straight-up unused. i... wonder if it even should be used anywhere? perhaps not... so perhaps it should not even exist...????
10555 var it = self.address_map.iterator();
10656 while (it.next()) |entry| {
10757 const mdi = entry.value_ptr.*;
......@@ -118,49 +68,91 @@ pub fn deinit(self: *SelfInfo) void {
11868 }
11969}
12070
121fn lookupModuleForAddress(self: *SelfInfo, address: usize) !Module.Lookup {
71fn lookupModuleForAddress(self: *SelfInfo, gpa: Allocator, address: usize) !Module {
12272 if (builtin.target.os.tag.isDarwin()) {
12373 return self.lookupModuleDyld(address);
12474 } else if (native_os == .windows) {
125 return self.lookupModuleWin32(address);
75 return self.lookupModuleWin32(gpa, address);
12676 } else if (native_os == .haiku) {
127 return self.lookupModuleHaiku(address);
77 @panic("TODO implement lookup module for Haiku");
12878 } else if (builtin.target.cpu.arch.isWasm()) {
129 return self.lookupModuleWasm(address);
79 @panic("TODO implement lookup module for Wasm");
13080 } else {
13181 return self.lookupModuleDl(address);
13282 }
13383}
13484
135fn loadModuleDebugInfo(self: *SelfInfo, lookup: *const Module.Lookup, module: *Module) !void {
85fn loadModuleDebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
86 // MLUGG TODO: this should totally just go into the `Module` impl or something, right? lol
87 if (builtin.target.os.tag.isDarwin()) {
88 try loadMachODebugInfo(gpa, module, di);
89 } else if (native_os == .windows) {
90 // MLUGG TODO: deal with 'already loaded' properly
91 try readCoffDebugInfo(gpa, module, di);
92 } else if (native_os == .haiku) {
93 unreachable;
94 } else if (builtin.target.cpu.arch.isWasm()) {
95 unreachable;
96 } else {
97 if (di.mapped_memory != null) return; // already loaded
98 const filename: ?[]const u8 = if (module.name.len > 0) module.name else null;
99 const mapped_mem = mapFileOrSelfExe(filename) catch |err| switch (err) {
100 error.FileNotFound => return error.MissingDebugInfo,
101 error.FileTooBig => return error.InvalidDebugInfo,
102 else => |e| return e,
103 };
104 errdefer posix.munmap(mapped_mem);
105 try di.load(gpa, mapped_mem, module.build_id, null, null, null, filename);
106 assert(di.mapped_memory != null);
107 }
108}
109
110fn loadModuleUnwindInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
136111 if (builtin.target.os.tag.isDarwin()) {
137 @compileError("TODO");
112 // MLUGG TODO HACKHACK
113 try loadMachODebugInfo(gpa, module, di);
138114 } else if (native_os == .windows) {
139 @compileError("TODO");
115 comptime unreachable; // not supported
140116 } else if (native_os == .haiku) {
141 @compileError("TODO");
117 comptime unreachable; // not supported
142118 } else if (builtin.target.cpu.arch.isWasm()) {
143 @compileError("TODO");
119 comptime unreachable; // not supported
144120 } else {
145 if (module.mapped_memory == null) {
146 var sections: Dwarf.SectionArray = @splat(null);
147 try readElfDebugInfo(module, self.allocator, if (lookup.name.len > 0) lookup.name else null, lookup.build_id, &sections);
148 assert(module.mapped_memory != null);
121 eh_frame: {
122 if (di.unwind.eh_frame != null) break :eh_frame; // already loaded
123 const eh_frame_hdr_bytes = module.gnu_eh_frame orelse break :eh_frame;
124 const eh_frame_hdr: Dwarf.Unwind.EhFrameHeader = try .parse(
125 @intFromPtr(eh_frame_hdr_bytes.ptr) - module.load_offset,
126 eh_frame_hdr_bytes,
127 @sizeOf(usize),
128 native_endian,
129 );
130 const eh_frame_addr = module.load_offset + @as(usize, @intCast(eh_frame_hdr.eh_frame_vaddr));
131 try di.unwind.scanEhFrame(
132 gpa,
133 eh_frame_hdr,
134 @ptrFromInt(eh_frame_addr),
135 null,
136 @sizeOf(usize),
137 native_endian,
138 );
149139 }
150140 }
151141}
152142
153pub fn unwindFrame(self: *SelfInfo, context: *UnwindContext) !usize {
154 const lookup = try self.lookupModuleForAddress(context.pc);
155 const gop = try self.address_map.getOrPut(self.allocator, lookup.base_address);
156 if (!gop.found_existing) gop.value_ptr.* = .init(&lookup);
143pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
144 comptime assert(target_supported);
145 const module = try self.lookupModuleForAddress(gpa, context.pc);
146 const gop = try self.address_map.getOrPut(gpa, module.load_offset);
147 if (!gop.found_existing) gop.value_ptr.* = .init;
148 try loadModuleUnwindInfo(gpa, &module, gop.value_ptr);
157149 if (native_os.isDarwin()) {
158150 // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding
159151 // via DWARF before attempting to use the compact unwind info will produce incorrect results.
160152 if (gop.value_ptr.unwind_info) |unwind_info| {
161153 if (unwindFrameMachO(
162 self.allocator,
163 lookup.base_address,
154 module.text_base,
155 module.load_offset,
164156 context,
165157 unwind_info,
166158 gop.value_ptr.eh_frame,
......@@ -169,292 +161,42 @@ pub fn unwindFrame(self: *SelfInfo, context: *UnwindContext) !usize {
169161 } else |err| {
170162 if (err != error.RequiresDWARFUnwind) return err;
171163 }
172 } else return error.MissingUnwindInfo;
164 }
165 return error.MissingUnwindInfo;
166 }
167 if (try gop.value_ptr.getDwarfUnwindForAddress(gpa, context.pc)) |unwind| {
168 return unwindFrameDwarf(unwind, module.load_offset, context, null);
173169 }
174 if (try gop.value_ptr.getDwarfUnwindForAddress(self.allocator, context.pc)) |unwind| {
175 return unwindFrameDwarf(self.allocator, unwind, lookup.base_address, context, null);
176 } else return error.MissingDebugInfo;
170 return error.MissingDebugInfo;
177171}
178172
179pub fn getSymbolAtAddress(self: *SelfInfo, address: usize) !std.debug.Symbol {
180 const lookup = try self.lookupModuleForAddress(address);
181 const gop = try self.address_map.getOrPut(self.allocator, lookup.base_address);
182 if (!gop.found_existing) gop.value_ptr.* = .init(&lookup);
183 try self.loadModuleDebugInfo(&lookup, gop.value_ptr);
184 return gop.value_ptr.getSymbolAtAddress(self.allocator, native_endian, lookup.base_address, address);
173pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {
174 comptime assert(target_supported);
175 const module = try self.lookupModuleForAddress(gpa, address);
176 const gop = try self.address_map.getOrPut(gpa, module.key());
177 if (!gop.found_existing) gop.value_ptr.* = .init;
178 try loadModuleDebugInfo(gpa, &module, gop.value_ptr);
179 return module.getSymbolAtAddress(gpa, gop.value_ptr, address);
185180}
186181
187182/// Returns the module name for a given address.
188183/// This can be called when getModuleForAddress fails, so implementations should provide
189184/// a path that doesn't rely on any side-effects of a prior successful module lookup.
190pub fn getModuleNameForAddress(self: *SelfInfo, address: usize) ?[]const u8 {
191 return if (self.lookupModuleForAddress(address)) |lookup| lookup.name else |err| switch (err) {
192 error.MissingDebugInfo => null,
193 };
194}
195
196fn lookupModuleDyld(self: *SelfInfo, address: usize) !*Module {
197 const image_count = std.c._dyld_image_count();
198
199 var i: u32 = 0;
200 while (i < image_count) : (i += 1) {
201 const header = std.c._dyld_get_image_header(i) orelse continue;
202 const base_address = @intFromPtr(header);
203 if (address < base_address) continue;
204 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
205
206 var it = macho.LoadCommandIterator{
207 .ncmds = header.ncmds,
208 .buffer = @alignCast(@as(
209 [*]u8,
210 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
211 )[0..header.sizeofcmds]),
212 };
213
214 var unwind_info: ?[]const u8 = null;
215 var eh_frame: ?[]const u8 = null;
216 while (it.next()) |cmd| switch (cmd.cmd()) {
217 .SEGMENT_64 => {
218 const segment_cmd = cmd.cast(macho.segment_command_64).?;
219 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
220
221 const seg_start = segment_cmd.vmaddr + vmaddr_slide;
222 const seg_end = seg_start + segment_cmd.vmsize;
223 if (address >= seg_start and address < seg_end) {
224 if (self.address_map.get(base_address)) |obj_di| {
225 return obj_di;
226 }
227
228 for (cmd.getSections()) |sect| {
229 const sect_addr: usize = @intCast(sect.addr);
230 const sect_size: usize = @intCast(sect.size);
231 if (mem.eql(u8, "__unwind_info", sect.sectName())) {
232 unwind_info = @as([*]const u8, @ptrFromInt(sect_addr + vmaddr_slide))[0..sect_size];
233 } else if (mem.eql(u8, "__eh_frame", sect.sectName())) {
234 eh_frame = @as([*]const u8, @ptrFromInt(sect_addr + vmaddr_slide))[0..sect_size];
235 }
236 }
237
238 const obj_di = try self.allocator.create(Module);
239 errdefer self.allocator.destroy(obj_di);
240
241 const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0);
242 const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) {
243 error.FileNotFound => return error.MissingDebugInfo,
244 else => return err,
245 };
246 obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
247 obj_di.base_address = base_address;
248 obj_di.vmaddr_slide = vmaddr_slide;
249 obj_di.unwind_info = unwind_info;
250 obj_di.eh_frame = eh_frame;
251
252 try self.address_map.putNoClobber(base_address, obj_di);
253
254 return obj_di;
255 }
256 },
257 else => {},
258 };
259 }
260
261 return error.MissingDebugInfo;
262}
263
264fn lookupModuleNameDyld(self: *SelfInfo, address: usize) ?[]const u8 {
265 _ = self;
266 const image_count = std.c._dyld_image_count();
267
268 var i: u32 = 0;
269 while (i < image_count) : (i += 1) {
270 const header = std.c._dyld_get_image_header(i) orelse continue;
271 const base_address = @intFromPtr(header);
272 if (address < base_address) continue;
273 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
274
275 var it = macho.LoadCommandIterator{
276 .ncmds = header.ncmds,
277 .buffer = @alignCast(@as(
278 [*]u8,
279 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
280 )[0..header.sizeofcmds]),
281 };
282
283 while (it.next()) |cmd| switch (cmd.cmd()) {
284 .SEGMENT_64 => {
285 const segment_cmd = cmd.cast(macho.segment_command_64).?;
286 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
287
288 const original_address = address - vmaddr_slide;
289 const seg_start = segment_cmd.vmaddr;
290 const seg_end = seg_start + segment_cmd.vmsize;
291 if (original_address >= seg_start and original_address < seg_end) {
292 return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0));
293 }
294 },
295 else => {},
296 };
297 }
298
299 return null;
300}
301
302fn lookupModuleWin32(self: *SelfInfo, address: usize) !*Module {
303 for (self.modules.items) |*module| {
304 if (address >= module.base_address and address < module.base_address + module.size) {
305 if (self.address_map.get(module.base_address)) |obj_di| {
306 return obj_di;
307 }
308
309 const obj_di = try self.allocator.create(Module);
310 errdefer self.allocator.destroy(obj_di);
311
312 const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size];
313 var coff_obj = try coff.Coff.init(mapped_module, true);
314
315 // The string table is not mapped into memory by the loader, so if a section name is in the
316 // string table then we have to map the full image file from disk. This can happen when
317 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
318 if (coff_obj.strtabRequired()) {
319 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
320 // openFileAbsoluteW requires the prefix to be present
321 @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' });
322
323 const process_handle = windows.GetCurrentProcess();
324 const len = windows.kernel32.GetModuleFileNameExW(
325 process_handle,
326 module.handle,
327 @ptrCast(&name_buffer[4]),
328 windows.PATH_MAX_WIDE,
329 );
330
331 if (len == 0) return error.MissingDebugInfo;
332 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
333 error.FileNotFound => return error.MissingDebugInfo,
334 else => return err,
335 };
336 errdefer coff_file.close();
337
338 var section_handle: windows.HANDLE = undefined;
339 const create_section_rc = windows.ntdll.NtCreateSection(
340 &section_handle,
341 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
342 null,
343 null,
344 windows.PAGE_READONLY,
345 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
346 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
347 windows.SEC_COMMIT,
348 coff_file.handle,
349 );
350 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
351 errdefer windows.CloseHandle(section_handle);
352
353 var coff_len: usize = 0;
354 var base_ptr: usize = 0;
355 const map_section_rc = windows.ntdll.NtMapViewOfSection(
356 section_handle,
357 process_handle,
358 @ptrCast(&base_ptr),
359 null,
360 0,
361 null,
362 &coff_len,
363 .ViewUnmap,
364 0,
365 windows.PAGE_READONLY,
366 );
367 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
368 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS);
369
370 const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len];
371 coff_obj = try coff.Coff.init(section_view, false);
372
373 module.mapped_file = .{
374 .file = coff_file,
375 .section_handle = section_handle,
376 .section_view = section_view,
377 };
378 }
379 errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit();
380
381 obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj);
382 obj_di.base_address = module.base_address;
383
384 try self.address_map.putNoClobber(module.base_address, obj_di);
385 return obj_di;
386 }
387 }
388
389 return error.MissingDebugInfo;
390}
391
392fn lookupModuleNameWin32(self: *SelfInfo, address: usize) ?[]const u8 {
393 for (self.modules.items) |module| {
394 if (address >= module.base_address and address < module.base_address + module.size) {
395 return module.name;
396 }
397 }
398 return null;
399}
400
401fn lookupModuleNameDl(self: *SelfInfo, address: usize) ?[]const u8 {
402 _ = self;
403
404 var ctx: struct {
405 // Input
406 address: usize,
407 // Output
408 name: []const u8 = "",
409 } = .{ .address = address };
410 const CtxTy = @TypeOf(ctx);
411
412 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
413 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
414 _ = size;
415 if (context.address < info.addr) return;
416 const phdrs = info.phdr[0..info.phnum];
417 for (phdrs) |*phdr| {
418 if (phdr.p_type != elf.PT_LOAD) continue;
419
420 const seg_start = info.addr +% phdr.p_vaddr;
421 const seg_end = seg_start + phdr.p_memsz;
422 if (context.address >= seg_start and context.address < seg_end) {
423 context.name = mem.sliceTo(info.name, 0) orelse "";
424 break;
425 }
426 } else return;
427
428 return error.Found;
429 }
430 }.callback)) {
431 return null;
432 } else |err| switch (err) {
433 error.Found => return fs.path.basename(ctx.name),
434 }
435
436 return null;
185pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {
186 comptime assert(target_supported);
187 const module = try self.lookupModuleForAddress(gpa, address);
188 return module.name;
437189}
438190
439fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
440 var ctx: struct {
441 // Input
191fn lookupModuleDl(self: *SelfInfo, address: usize) !Module {
192 _ = self; // MLUGG
193 const DlIterContext = struct {
194 /// input
442195 address: usize,
443 // Output
444 lookup: Module.Lookup,
445 } = .{
446 .address = address,
447 .lookup = .{
448 .base_address = undefined,
449 .name = undefined,
450 .build_id = null,
451 .gnu_eh_frame = null,
452 },
453 };
454 const CtxTy = @TypeOf(ctx);
196 /// output
197 module: Module,
455198
456 posix.dl_iterate_phdr(&ctx, error{Found}, struct {
457 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
199 fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void {
458200 _ = size;
459201 // The base address is too high
460202 if (context.address < info.addr)
......@@ -468,10 +210,13 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
468210 const seg_start = info.addr +% phdr.p_vaddr;
469211 const seg_end = seg_start + phdr.p_memsz;
470212 if (context.address >= seg_start and context.address < seg_end) {
471 // Android libc uses NULL instead of an empty string to mark the
472 // main program
473 context.lookup.name = mem.sliceTo(info.name, 0) orelse "";
474 context.lookup.base_address = info.addr;
213 context.module = .{
214 .load_offset = info.addr,
215 // Android libc uses NULL instead of "" to mark the main program
216 .name = mem.sliceTo(info.name, 0) orelse "",
217 .build_id = null,
218 .gnu_eh_frame = null,
219 };
475220 break;
476221 }
477222 } else return;
......@@ -480,17 +225,20 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
480225 switch (phdr.p_type) {
481226 elf.PT_NOTE => {
482227 // Look for .note.gnu.build-id
483 const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
484 const name_size = mem.readInt(u32, note_bytes[0..4], native_endian);
485 if (name_size != 4) continue;
486 const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian);
487 const note_type = mem.readInt(u32, note_bytes[8..12], native_endian);
228 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
229 var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
230 const name_size = r.takeInt(u32, native_endian) catch continue;
231 const desc_size = r.takeInt(u32, native_endian) catch continue;
232 const note_type = r.takeInt(u32, native_endian) catch continue;
233 const name = r.take(name_size) catch continue;
488234 if (note_type != elf.NT_GNU_BUILD_ID) continue;
489 if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
490 context.lookup.build_id = note_bytes[16..][0..desc_size];
235 if (!mem.eql(u8, name, "GNU\x00")) continue;
236 const desc = r.take(desc_size) catch continue;
237 context.module.build_id = desc;
491238 },
492239 elf.PT_GNU_EH_FRAME => {
493 context.lookup.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
240 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
241 context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
494242 },
495243 else => {},
496244 }
......@@ -499,425 +247,558 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
499247 // Stop the iteration
500248 return error.Found;
501249 }
502 }.callback) catch |err| switch (err) {
503 error.Found => return ctx.lookup,
504250 };
505 if (true) return error.MissingDebugInfo;
506
507 if (self.address_map.get(ctx.lookup.base_address)) |obj_di| {
508 return obj_di;
509 }
251 var ctx: DlIterContext = .{
252 .address = address,
253 .module = undefined,
254 };
255 posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
256 error.Found => return ctx.module,
257 };
258 return error.MissingDebugInfo;
259}
510260
511 var sections: Dwarf.SectionArray = @splat(null);
512 if (ctx.lookup.gnu_eh_frame) |eh_frame_hdr| {
513 // This is a special case - pointer offsets inside .eh_frame_hdr
514 // are encoded relative to its base address, so we must use the
515 // version that is already memory mapped, and not the one that
516 // will be mapped separately from the ELF file.
517 sections[@intFromEnum(Dwarf.Unwind.Section.Id.eh_frame_hdr)] = .{
518 .data = eh_frame_hdr,
519 .owned = false,
261fn lookupModuleDyld(self: *SelfInfo, address: usize) !Module {
262 _ = self; // MLUGG
263 const image_count = std.c._dyld_image_count();
264 for (0..image_count) |image_idx| {
265 const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;
266 const text_base = @intFromPtr(header);
267 if (address < text_base) continue;
268 const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx));
269
270 // Find the __TEXT segment
271 var it: macho.LoadCommandIterator = .{
272 .ncmds = header.ncmds,
273 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
274 };
275 const text_segment_cmd, const text_sections = while (it.next()) |load_cmd| {
276 if (load_cmd.cmd() != .SEGMENT_64) continue;
277 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
278 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
279 break .{ segment_cmd, load_cmd.getSections() };
280 } else continue;
281
282 const seg_start = load_offset + text_segment_cmd.vmaddr;
283 assert(seg_start == text_base);
284 const seg_end = seg_start + text_segment_cmd.vmsize;
285 if (address < seg_start or address >= seg_end) continue;
286
287 // We've found the matching __TEXT segment. This is the image we need, but we must look
288 // for unwind info in it before returning.
289
290 var result: Module = .{
291 .text_base = text_base,
292 .load_offset = load_offset,
293 .name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))),
294 .unwind_info = null,
295 .eh_frame = null,
520296 };
297 for (text_sections) |sect| {
298 if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
299 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr)));
300 result.unwind_info = sect_ptr[0..@intCast(sect.size)];
301 } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
302 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr)));
303 result.eh_frame = sect_ptr[0..@intCast(sect.size)];
304 }
305 }
306 return result;
521307 }
522
523 const obj_di = try self.allocator.create(Module);
524 errdefer self.allocator.destroy(obj_di);
525 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.lookup.name.len > 0) ctx.lookup.name else null, ctx.lookup.build_id, &sections);
526 obj_di.base_address = ctx.lookup.base_address;
527
528 // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding
529 obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.lookup.base_address) catch {};
530
531 try self.address_map.putNoClobber(self.allocator, ctx.lookup.base_address, obj_di);
532
533 return obj_di;
308 return error.MissingDebugInfo;
534309}
535310
536fn lookupModuleHaiku(self: *SelfInfo, address: usize) !*Module {
537 _ = self;
538 _ = address;
539 @panic("TODO implement lookup module for Haiku");
540}
311fn lookupModuleWin32(self: *SelfInfo, gpa: Allocator, address: usize) !Module {
312 if (self.lookupModuleWin32Cache(address)) |m| return m;
541313
542fn lookupModuleWasm(self: *SelfInfo, address: usize) !*Module {
543 _ = self;
544 _ = address;
545 @panic("TODO implement lookup module for Wasm");
546}
314 {
315 // Check a new module hasn't been loaded
316 self.module_cache.clearRetainingCapacity();
547317
548pub const Module = switch (native_os) {
549 .macos, .ios, .watchos, .tvos, .visionos => struct {
550 base_address: usize,
551 vmaddr_slide: usize,
552 mapped_memory: []align(std.heap.page_size_min) const u8,
553 symbols: []const MachoSymbol,
554 strings: [:0]const u8,
555 ofiles: OFileTable,
556
557 // Backed by the in-memory sections mapped by the loader
558 unwind_info: ?[]const u8 = null,
559 eh_frame: ?[]const u8 = null,
560
561 const OFileTable = std.StringHashMap(OFileInfo);
562 const OFileInfo = struct {
563 di: Dwarf,
564 addr_table: std.StringHashMap(u64),
565 };
318 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
319 if (handle == windows.INVALID_HANDLE_VALUE) {
320 return windows.unexpectedError(windows.GetLastError());
321 }
322 defer windows.CloseHandle(handle);
566323
567 pub fn deinit(self: *@This(), allocator: Allocator) void {
568 var it = self.ofiles.iterator();
569 while (it.next()) |entry| {
570 const ofile = entry.value_ptr;
571 ofile.di.deinit(allocator);
572 ofile.addr_table.deinit();
324 var entry: windows.MODULEENTRY32 = undefined;
325 entry.dwSize = @sizeOf(windows.MODULEENTRY32);
326 if (windows.kernel32.Module32First(handle, &entry) != 0) {
327 try self.module_cache.append(gpa, entry);
328 while (windows.kernel32.Module32Next(handle, &entry) != 0) {
329 try self.module_cache.append(gpa, entry);
573330 }
574 self.ofiles.deinit();
575 allocator.free(self.symbols);
576 posix.munmap(self.mapped_memory);
577331 }
332 }
578333
579 fn loadOFile(self: *@This(), allocator: Allocator, o_file_path: []const u8) !*OFileInfo {
580 const o_file = try fs.cwd().openFile(o_file_path, .{});
581 const mapped_mem = try mapWholeFile(o_file);
582
583 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
584 if (hdr.magic != std.macho.MH_MAGIC_64)
585 return error.InvalidDebugInfo;
586
587 var segcmd: ?macho.LoadCommandIterator.LoadCommand = null;
588 var symtabcmd: ?macho.symtab_command = null;
589 var it = macho.LoadCommandIterator{
590 .ncmds = hdr.ncmds,
591 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
592 };
593 while (it.next()) |cmd| switch (cmd.cmd()) {
594 .SEGMENT_64 => segcmd = cmd,
595 .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?,
596 else => {},
334 if (self.lookupModuleWin32Cache(address)) |m| return m;
335 return error.MissingDebugInfo;
336}
337fn lookupModuleWin32Cache(self: *SelfInfo, address: usize) ?Module {
338 for (self.module_cache.items) |*entry| {
339 const base_address = @intFromPtr(entry.modBaseAddr);
340 if (address >= base_address and address < base_address + entry.modBaseSize) {
341 return .{
342 .base_address = base_address,
343 .size = entry.modBaseSize,
344 .name = std.mem.sliceTo(&entry.szModule, 0),
345 .handle = entry.hModule,
597346 };
347 }
348 }
349 return null;
350}
598351
599 if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo;
600
601 // Parse symbols
602 const strtab = @as(
603 [*]const u8,
604 @ptrCast(&mapped_mem[symtabcmd.?.stroff]),
605 )[0 .. symtabcmd.?.strsize - 1 :0];
606 const symtab = @as(
607 [*]const macho.nlist_64,
608 @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])),
609 )[0..symtabcmd.?.nsyms];
610
611 // TODO handle tentative (common) symbols
612 var addr_table = std.StringHashMap(u64).init(allocator);
613 try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len)));
614 for (symtab) |sym| {
615 if (sym.n_strx == 0) continue;
616 if (sym.undf() or sym.tentative() or sym.abs()) continue;
617 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
618 // TODO is it possible to have a symbol collision?
619 addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);
620 }
352fn readCoffDebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
353 const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
354 const mapped = mapped_ptr[0..module.size];
355 var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
356 // The string table is not mapped into memory by the loader, so if a section name is in the
357 // string table then we have to map the full image file from disk. This can happen when
358 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
359 if (coff_obj.strtabRequired()) {
360 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
361 name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
362 const process_handle = windows.GetCurrentProcess();
363 const len = windows.kernel32.GetModuleFileNameExW(
364 process_handle,
365 module.handle,
366 name_buffer[4..],
367 windows.PATH_MAX_WIDE,
368 );
369 if (len == 0) return error.MissingDebugInfo;
370 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
371 error.FileNotFound => return error.MissingDebugInfo,
372 else => |e| return e,
373 };
374 errdefer coff_file.close();
375 var section_handle: windows.HANDLE = undefined;
376 const create_section_rc = windows.ntdll.NtCreateSection(
377 &section_handle,
378 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
379 null,
380 null,
381 windows.PAGE_READONLY,
382 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
383 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
384 windows.SEC_COMMIT,
385 coff_file.handle,
386 );
387 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
388 errdefer windows.CloseHandle(section_handle);
389 var coff_len: usize = 0;
390 var section_view_ptr: [*]const u8 = undefined;
391 const map_section_rc = windows.ntdll.NtMapViewOfSection(
392 section_handle,
393 process_handle,
394 @ptrCast(&section_view_ptr),
395 null,
396 0,
397 null,
398 &coff_len,
399 .ViewUnmap,
400 0,
401 windows.PAGE_READONLY,
402 );
403 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
404 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS);
405 const section_view = section_view_ptr[0..coff_len];
406 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
407 di.mapped_file = .{
408 .file = coff_file,
409 .section_handle = section_handle,
410 .section_view = section_view,
411 };
412 }
413 di.coff_image_base = coff_obj.getImageBase();
621414
622 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
623 if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{
624 .data = eh_frame,
625 .owned = false,
626 };
415 if (coff_obj.getSectionByName(".debug_info")) |_| {
416 di.dwarf = .{};
627417
628 for (segcmd.?.getSections()) |sect| {
629 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
418 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
419 di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
420 break :blk .{
421 .data = try coff_obj.getSectionDataAlloc(section_header, gpa),
422 .virtual_address = section_header.virtual_address,
423 .owned = true,
424 };
425 } else null;
426 }
630427
631 var section_index: ?usize = null;
632 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
633 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i;
634 }
635 if (section_index == null) continue;
428 try di.dwarf.?.open(gpa, native_endian);
429 }
636430
637 const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size);
638 sections[section_index.?] = .{
639 .data = section_bytes,
640 .virtual_address = @intCast(sect.addr),
641 .owned = false,
642 };
431 if (try coff_obj.getPdbPath()) |raw_path| pdb: {
432 const path = blk: {
433 if (fs.path.isAbsolute(raw_path)) {
434 break :blk raw_path;
435 } else {
436 const self_dir = try fs.selfExeDirPathAlloc(gpa);
437 defer gpa.free(self_dir);
438 break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
643439 }
440 };
441 defer if (path.ptr != raw_path.ptr) gpa.free(path);
644442
645 const missing_debug_info =
646 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
647 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
648 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
649 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
650 if (missing_debug_info) return error.MissingDebugInfo;
651
652 var di: Dwarf = .{
653 .endian = .little,
654 .sections = sections,
655 .is_macho = true,
656 };
443 di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
444 error.FileNotFound, error.IsDir => break :pdb,
445 else => return err,
446 };
447 try di.pdb.?.parseInfoStream();
448 try di.pdb.?.parseDbiStream();
657449
658 try Dwarf.open(&di, allocator);
659 const info = OFileInfo{
660 .di = di,
661 .addr_table = addr_table,
662 };
450 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
451 return error.InvalidDebugInfo;
663452
664 // Add the debug info to the cache
665 const result = try self.ofiles.getOrPut(o_file_path);
666 assert(!result.found_existing);
667 result.value_ptr.* = info;
453 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
454 }
455}
668456
669 return result.value_ptr;
457const Module = switch (native_os) {
458 else => "MLUGG TODO", // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm...
459 .macos, .ios, .watchos, .tvos, .visionos => struct {
460 /// The runtime address where __TEXT is loaded.
461 text_base: usize,
462 load_offset: usize,
463 name: []const u8,
464 unwind_info: ?[]const u8,
465 eh_frame: ?[]const u8,
466 fn key(m: *const Module) usize {
467 return m.text_base;
670468 }
469 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
470 const vaddr = address - module.load_offset;
471 const symbol = MachoSymbol.find(di.symbols, vaddr) orelse return .{}; // MLUGG TODO null?
671472
672 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
673 const result = try self.getOFileInfoForAddress(allocator, address);
674 if (result.symbol == null) return .{};
473 // offset of `address` from start of `symbol`
474 const address_symbol_offset = vaddr - symbol.addr;
675475
676476 // Take the symbol name from the N_FUN STAB entry, we're going to
677477 // use it if we fail to find the DWARF infos
678 const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0);
679 if (result.o_file_info == null) return .{ .name = stab_symbol };
680
681 // Translate again the address, this time into an address inside the
682 // .o file
683 const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{
684 .name = "???",
478 const stab_symbol = mem.sliceTo(di.strings[symbol.strx..], 0);
479 const o_file_path = mem.sliceTo(di.strings[symbol.ofile..], 0);
480
481 const o_file: *DebugInfo.OFile = of: {
482 const gop = try di.ofiles.getOrPut(gpa, o_file_path);
483 if (!gop.found_existing) {
484 gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| {
485 defer _ = di.ofiles.pop().?;
486 switch (err) {
487 error.FileNotFound,
488 error.MissingDebugInfo,
489 error.InvalidDebugInfo,
490 => return .{ .name = stab_symbol },
491 else => |e| return e,
492 }
493 };
494 }
495 break :of gop.value_ptr;
685496 };
686497
687 const addr_off = result.relocated_address - result.symbol.?.addr;
688 const o_file_di = &result.o_file_info.?.di;
689 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
690 return .{
691 .name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
692 .compile_unit_name = compile_unit.die.getAttrString(
693 o_file_di,
694 std.dwarf.AT.name,
695 o_file_di.section(.debug_str),
696 compile_unit.*,
697 ) catch |err| switch (err) {
698 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
699 },
700 .source_location = o_file_di.getLineNumberInfo(
701 allocator,
702 compile_unit,
703 relocated_address_o + addr_off,
704 ) catch |err| switch (err) {
705 error.MissingDebugInfo, error.InvalidDebugInfo => null,
706 else => return err,
707 },
708 };
709 } else |err| switch (err) {
710 error.MissingDebugInfo, error.InvalidDebugInfo => {
711 return .{ .name = stab_symbol };
712 },
713 else => return err,
714 }
715 }
498 const symbol_ofile_vaddr = o_file.addr_table.get(stab_symbol) orelse return .{ .name = stab_symbol };
716499
717 pub fn getOFileInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !struct {
718 relocated_address: usize,
719 symbol: ?*const MachoSymbol = null,
720 o_file_info: ?*OFileInfo = null,
721 } {
722 // Translate the VA into an address into this object
723 const relocated_address = address - self.vmaddr_slide;
724
725 // Find the .o file where this symbol is defined
726 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{
727 .relocated_address = relocated_address,
500 const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) {
501 error.MissingDebugInfo, error.InvalidDebugInfo => return .{ .name = stab_symbol },
502 else => |e| return e,
728503 };
729504
730 // Check if its debug infos are already in the cache
731 const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
732 const o_file_info = self.ofiles.getPtr(o_file_path) orelse
733 (self.loadOFile(allocator, o_file_path) catch |err| switch (err) {
734 error.FileNotFound,
735 error.MissingDebugInfo,
736 error.InvalidDebugInfo,
737 => return .{
738 .relocated_address = relocated_address,
739 .symbol = symbol,
740 },
741 else => return err,
742 });
743
744505 return .{
745 .relocated_address = relocated_address,
746 .symbol = symbol,
747 .o_file_info = o_file_info,
506 .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr) orelse stab_symbol,
507 .compile_unit_name = compile_unit.die.getAttrString(
508 &o_file.dwarf,
509 native_endian,
510 std.dwarf.AT.name,
511 o_file.dwarf.section(.debug_str),
512 compile_unit,
513 ) catch |err| switch (err) {
514 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
515 },
516 .source_location = o_file.dwarf.getLineNumberInfo(
517 gpa,
518 native_endian,
519 compile_unit,
520 symbol_ofile_vaddr + address_symbol_offset,
521 ) catch |err| switch (err) {
522 error.MissingDebugInfo, error.InvalidDebugInfo => null,
523 else => return err,
524 },
748525 };
749526 }
527 const DebugInfo = struct {
528 // MLUGG TODO: these are duplicated state. i actually reckon they should be removed from Module, and loadMachODebugInfo should be the one discovering them!
529 mapped_memory: []align(std.heap.page_size_min) const u8,
530 symbols: []const MachoSymbol,
531 strings: [:0]const u8,
532 // MLUGG TODO: this could use an adapter to just index straight into `strings`!
533 ofiles: std.StringArrayHashMapUnmanaged(OFile),
534
535 // Backed by the in-memory sections mapped by the loader
536 unwind_info: ?[]const u8,
537 eh_frame: ?[]const u8,
538
539 // MLUGG TODO HACKHACK: this is awful
540 const init: DebugInfo = undefined;
541
542 const OFile = struct {
543 dwarf: Dwarf,
544 // MLUGG TODO: this could use an adapter to just index straight into the strtab!
545 addr_table: std.StringArrayHashMapUnmanaged(u64),
546 };
750547
751 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
752 return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;
753 }
754 },
755 .uefi, .windows => struct {
756 base_address: usize,
757 pdb: ?Pdb,
758 dwarf: ?Dwarf,
759 coff_image_base: u64,
760
761 /// Only used if pdb is non-null
762 coff_section_headers: []coff.SectionHeader,
763
764 pub fn deinit(self: *@This(), gpa: Allocator) void {
765 if (self.dwarf) |*dwarf| {
766 dwarf.deinit(gpa);
767 }
768
769 if (self.pdb) |*p| {
770 gpa.free(p.file_reader.interface.buffer);
771 gpa.destroy(p.file_reader);
772 p.deinit();
773 gpa.free(self.coff_section_headers);
548 fn deinit(di: *DebugInfo, gpa: Allocator) void {
549 for (di.ofiles.values()) |*ofile| {
550 ofile.dwarf.deinit(gpa);
551 ofile.addr_table.deinit(gpa);
552 }
553 di.ofiles.deinit();
554 gpa.free(di.symbols);
555 posix.munmap(di.mapped_memory);
774556 }
775557
776 self.* = undefined;
777 }
558 fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
559 const mapped_mem = try mapFileOrSelfExe(o_file_path);
560 errdefer posix.munmap(mapped_mem);
778561
779 fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?std.debug.Symbol {
780 var coff_section: *align(1) const coff.SectionHeader = undefined;
781 const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| {
782 if (sect_contrib.section > self.coff_section_headers.len) continue;
783 // Remember that SectionContribEntry.Section is 1-based.
784 coff_section = &self.coff_section_headers[sect_contrib.section - 1];
785
786 const vaddr_start = coff_section.virtual_address + sect_contrib.offset;
787 const vaddr_end = vaddr_start + sect_contrib.size;
788 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
789 break sect_contrib.module_index;
790 }
791 } else {
792 // we have no information to add to the address
793 return null;
794 };
562 if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
563 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
564 if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo;
795565
796 const module = (try self.pdb.?.getModule(mod_index)) orelse
797 return error.InvalidDebugInfo;
798 const obj_basename = fs.path.basename(module.obj_file_name);
799
800 const symbol_name = self.pdb.?.getSymbolName(
801 module,
802 relocated_address - coff_section.virtual_address,
803 ) orelse "???";
804 const opt_line_info = try self.pdb.?.getLineNumberInfo(
805 module,
806 relocated_address - coff_section.virtual_address,
807 );
566 const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
567 var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
568 var symtab_cmd: ?macho.symtab_command = null;
569 var it: macho.LoadCommandIterator = .{
570 .ncmds = hdr.ncmds,
571 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
572 };
573 while (it.next()) |cmd| switch (cmd.cmd()) {
574 .SEGMENT_64 => seg_cmd = cmd,
575 .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
576 else => {},
577 };
578 break :cmds .{
579 seg_cmd orelse return error.MissingDebugInfo,
580 symtab_cmd orelse return error.MissingDebugInfo,
581 };
582 };
808583
809 return .{
810 .name = symbol_name,
811 .compile_unit_name = obj_basename,
812 .source_location = opt_line_info,
813 };
814 }
584 if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
585 if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo;
586 const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
587
588 const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
589 if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;
590 const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]);
591
592 // TODO handle tentative (common) symbols
593 // MLUGG TODO: does initCapacity actually make sense?
594 var addr_table: std.StringArrayHashMapUnmanaged(u64) = .empty;
595 defer addr_table.deinit(gpa);
596 try addr_table.ensureUnusedCapacity(gpa, @intCast(symtab.len));
597 for (symtab) |sym| {
598 if (sym.n_strx == 0) continue;
599 switch (sym.n_type.bits.type) {
600 .undf => continue, // includes tentative symbols
601 .abs => continue,
602 else => {},
603 }
604 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
605 const gop = addr_table.getOrPutAssumeCapacity(sym_name);
606 if (gop.found_existing) return error.InvalidDebugInfo;
607 gop.value_ptr.* = sym.n_value;
608 }
815609
816 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
817 // Translate the VA into an address into this object
818 const relocated_address = address - self.base_address;
610 var sections: Dwarf.SectionArray = @splat(null);
611 for (seg_cmd.getSections()) |sect| {
612 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
819613
820 if (self.pdb != null) {
821 if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol;
822 }
614 const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
615 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
616 } else continue;
823617
824 if (self.dwarf) |*dwarf| {
825 const dwarf_address = relocated_address + self.coff_image_base;
826 return dwarf.getSymbol(allocator, dwarf_address);
827 }
618 const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size);
619 sections[section_index] = .{
620 .data = section_bytes,
621 .virtual_address = @intCast(sect.addr),
622 .owned = false,
623 };
624 }
828625
829 return .{};
830 }
626 const missing_debug_info =
627 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
628 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
629 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
630 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
631 if (missing_debug_info) return error.MissingDebugInfo;
831632
832 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
833 _ = allocator;
834 _ = address;
633 var dwarf: Dwarf = .{ .sections = sections };
634 errdefer dwarf.deinit(gpa);
635 try dwarf.open(gpa, native_endian);
835636
836 return switch (self.debug_data) {
837 .dwarf => |*dwarf| dwarf,
838 else => null,
839 };
840 }
637 return .{
638 .dwarf = dwarf,
639 .addr_table = addr_table.move(),
640 };
641 }
642 };
841643 },
842 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => Dwarf.ElfModule,
843644 .wasi, .emscripten => struct {
844 pub fn deinit(self: *@This(), allocator: Allocator) void {
845 _ = self;
846 _ = allocator;
847 }
848
849 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
850 _ = self;
851 _ = allocator;
852 _ = address;
853 return .{};
645 const DebugInfo = struct {
646 const init: DebugInfo = .{};
647 fn getSymbolAtAddress(di: *DebugInfo, gpa: Allocator, base_address: usize, address: usize) !std.debug.Symbol {
648 _ = di;
649 _ = gpa;
650 _ = base_address;
651 _ = address;
652 unreachable;
653 }
654 };
655 },
656 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
657 load_offset: usize,
658 name: []const u8,
659 build_id: ?[]const u8,
660 gnu_eh_frame: ?[]const u8,
661 fn key(m: Module) usize {
662 return m.load_offset; // MLUGG TODO: is this technically valid? idk
854663 }
855
856 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
857 _ = self;
858 _ = allocator;
859 _ = address;
860 return null;
664 const DebugInfo = Dwarf.ElfModule;
665 fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
666 return di.getSymbolAtAddress(gpa, native_endian, mod.load_offset, address);
861667 }
862668 },
863 else => Dwarf,
864};
669 .uefi, .windows => struct {
670 base_address: usize,
671 size: usize,
672 name: []const u8,
673 handle: windows.HMODULE,
674 fn key(m: Module) usize {
675 return m.base_address;
676 }
677 const DebugInfo = struct {
678 coff_image_base: u64,
679 mapped_file: ?struct {
680 file: File,
681 section_handle: windows.HANDLE,
682 section_view: []const u8,
683 fn deinit(mapped: @This()) void {
684 const process_handle = windows.GetCurrentProcess();
685 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mapped.section_view.ptr)) == .SUCCESS);
686 windows.CloseHandle(mapped.section_handle);
687 mapped.file.close();
688 }
689 },
865690
866/// How is this different than `Module` when the host is Windows?
867/// Why are both stored in the `SelfInfo` struct?
868/// Boy, it sure would be nice if someone added documentation comments for this
869/// struct explaining it.
870pub const WindowsModule = struct {
871 base_address: usize,
872 size: u32,
873 name: []const u8,
874 handle: windows.HMODULE,
875
876 // Set when the image file needed to be mapped from disk
877 mapped_file: ?struct {
878 file: File,
879 section_handle: windows.HANDLE,
880 section_view: []const u8,
881
882 pub fn deinit(self: @This()) void {
883 const process_handle = windows.GetCurrentProcess();
884 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrCast(@constCast(self.section_view.ptr))) == .SUCCESS);
885 windows.CloseHandle(self.section_handle);
886 self.file.close();
691 dwarf: ?Dwarf,
692
693 pdb: ?Pdb,
694 /// Populated iff `pdb != null`; otherwise `&.{}`.
695 coff_section_headers: []coff.SectionHeader,
696
697 const init: DebugInfo = .{
698 .coff_image_base = undefined,
699 .mapped_file = null,
700 .dwarf = null,
701 .pdb = null,
702 .coff_section_headers = &.{},
703 };
704
705 fn deinit(di: *DebugInfo, gpa: Allocator) void {
706 if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
707 if (di.pdb) |*pdb| pdb.deinit();
708 gpa.free(di.coff_section_headers);
709 if (di.mapped_file) |mapped| mapped.deinit();
710 }
711
712 fn getSymbolFromPdb(di: *DebugInfo, relocated_address: usize) !?std.debug.Symbol {
713 var coff_section: *align(1) const coff.SectionHeader = undefined;
714 const mod_index = for (di.pdb.?.sect_contribs) |sect_contrib| {
715 if (sect_contrib.section > di.coff_section_headers.len) continue;
716 // Remember that SectionContribEntry.Section is 1-based.
717 coff_section = &di.coff_section_headers[sect_contrib.section - 1];
718
719 const vaddr_start = coff_section.virtual_address + sect_contrib.offset;
720 const vaddr_end = vaddr_start + sect_contrib.size;
721 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
722 break sect_contrib.module_index;
723 }
724 } else {
725 // we have no information to add to the address
726 return null;
727 };
728
729 const module = (try di.pdb.?.getModule(mod_index)) orelse
730 return error.InvalidDebugInfo;
731 const obj_basename = fs.path.basename(module.obj_file_name);
732
733 const symbol_name = di.pdb.?.getSymbolName(
734 module,
735 relocated_address - coff_section.virtual_address,
736 ) orelse "???";
737 const opt_line_info = try di.pdb.?.getLineNumberInfo(
738 module,
739 relocated_address - coff_section.virtual_address,
740 );
741
742 return .{
743 .name = symbol_name,
744 .compile_unit_name = obj_basename,
745 .source_location = opt_line_info,
746 };
747 }
748 };
749
750 fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
751 // Translate the runtime address into a virtual address into the module
752 const vaddr = address - mod.base_address;
753
754 if (di.pdb != null) {
755 if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol;
756 }
757
758 if (di.dwarf) |*dwarf| {
759 const dwarf_address = vaddr + di.coff_image_base;
760 return dwarf.getSymbol(gpa, native_endian, dwarf_address);
761 }
762
763 return error.MissingDebugInfo;
887764 }
888 } = null,
765 },
889766};
890767
891/// This takes ownership of macho_file: users of this function should not close
892/// it themselves, even on error.
893/// TODO it's weird to take ownership even on error, rework this code.
894fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
895 const mapped_mem = try mapWholeFile(macho_file);
768fn loadMachODebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
769 const mapped_mem = mapFileOrSelfExe(module.name) catch |err| switch (err) {
770 error.FileNotFound => return error.MissingDebugInfo,
771 error.FileTooBig => return error.InvalidDebugInfo,
772 else => |e| return e,
773 };
774 errdefer posix.munmap(mapped_mem);
896775
897776 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
898777 if (hdr.magic != macho.MH_MAGIC_64)
899778 return error.InvalidDebugInfo;
900779
901 var it = macho.LoadCommandIterator{
902 .ncmds = hdr.ncmds,
903 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
780 const symtab: macho.symtab_command = symtab: {
781 var it: macho.LoadCommandIterator = .{
782 .ncmds = hdr.ncmds,
783 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
784 };
785 while (it.next()) |cmd| switch (cmd.cmd()) {
786 .SYMTAB => break :symtab cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
787 else => {},
788 };
789 return error.MissingDebugInfo;
904790 };
905 const symtab = while (it.next()) |cmd| switch (cmd.cmd()) {
906 .SYMTAB => break cmd.cast(macho.symtab_command).?,
907 else => {},
908 } else return error.MissingDebugInfo;
909
910 const syms = @as(
911 [*]const macho.nlist_64,
912 @ptrCast(@alignCast(&mapped_mem[symtab.symoff])),
913 )[0..symtab.nsyms];
791
792 const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab.symoff..]);
793 const syms = syms_ptr[0..symtab.nsyms];
914794 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
915795
916 const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
796 // MLUGG TODO: does it really make sense to initCapacity here? how many of syms are omitted?
797 var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
798 defer symbols.deinit(gpa);
917799
918800 var ofile: u32 = undefined;
919801 var last_sym: MachoSymbol = undefined;
920 var symbol_index: usize = 0;
921802 var state: enum {
922803 init,
923804 oso_open,
......@@ -929,64 +810,53 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
929810 } = .init;
930811
931812 for (syms) |*sym| {
932 if (!sym.stab()) continue;
813 if (sym.n_type.bits.is_stab == 0) continue;
933814
934815 // TODO handle globals N_GSYM, and statics N_STSYM
935 switch (sym.n_type) {
936 macho.N_OSO => {
937 switch (state) {
938 .init, .oso_close => {
939 state = .oso_open;
940 ofile = sym.n_strx;
941 },
942 else => return error.InvalidDebugInfo,
943 }
816 switch (sym.n_type.stab) {
817 .oso => switch (state) {
818 .init, .oso_close => {
819 state = .oso_open;
820 ofile = sym.n_strx;
821 },
822 else => return error.InvalidDebugInfo,
944823 },
945 macho.N_BNSYM => {
946 switch (state) {
947 .oso_open, .ensym => {
948 state = .bnsym;
949 last_sym = .{
950 .strx = 0,
951 .addr = sym.n_value,
952 .size = 0,
953 .ofile = ofile,
954 };
955 },
956 else => return error.InvalidDebugInfo,
957 }
824 .bnsym => switch (state) {
825 .oso_open, .ensym => {
826 state = .bnsym;
827 last_sym = .{
828 .strx = 0,
829 .addr = sym.n_value,
830 .size = 0,
831 .ofile = ofile,
832 };
833 },
834 else => return error.InvalidDebugInfo,
958835 },
959 macho.N_FUN => {
960 switch (state) {
961 .bnsym => {
962 state = .fun_strx;
963 last_sym.strx = sym.n_strx;
964 },
965 .fun_strx => {
966 state = .fun_size;
967 last_sym.size = @as(u32, @intCast(sym.n_value));
968 },
969 else => return error.InvalidDebugInfo,
970 }
836 .fun => switch (state) {
837 .bnsym => {
838 state = .fun_strx;
839 last_sym.strx = sym.n_strx;
840 },
841 .fun_strx => {
842 state = .fun_size;
843 last_sym.size = @intCast(sym.n_value);
844 },
845 else => return error.InvalidDebugInfo,
971846 },
972 macho.N_ENSYM => {
973 switch (state) {
974 .fun_size => {
975 state = .ensym;
976 symbols_buf[symbol_index] = last_sym;
977 symbol_index += 1;
978 },
979 else => return error.InvalidDebugInfo,
980 }
847 .ensym => switch (state) {
848 .fun_size => {
849 state = .ensym;
850 symbols.appendAssumeCapacity(last_sym);
851 },
852 else => return error.InvalidDebugInfo,
981853 },
982 macho.N_SO => {
983 switch (state) {
984 .init, .oso_close => {},
985 .oso_open, .ensym => {
986 state = .oso_close;
987 },
988 else => return error.InvalidDebugInfo,
989 }
854 .so => switch (state) {
855 .init, .oso_close => {},
856 .oso_open, .ensym => {
857 state = .oso_close;
858 },
859 else => return error.InvalidDebugInfo,
990860 },
991861 else => {},
992862 }
......@@ -998,560 +868,187 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
998868 else => return error.InvalidDebugInfo,
999869 }
1000870
1001 const symbols = try allocator.realloc(symbols_buf, symbol_index);
871 const symbols_slice = try symbols.toOwnedSlice(gpa);
872 errdefer gpa.free(symbols_slice);
1002873
1003874 // Even though lld emits symbols in ascending order, this debug code
1004875 // should work for programs linked in any valid way.
1005876 // This sort is so that we can binary search later.
1006 mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan);
877 mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
1007878
1008 return .{
1009 .base_address = undefined,
1010 .vmaddr_slide = undefined,
879 di.* = .{
880 .unwind_info = module.unwind_info,
881 .eh_frame = module.eh_frame,
1011882 .mapped_memory = mapped_mem,
1012 .ofiles = Module.OFileTable.init(allocator),
1013 .symbols = symbols,
883 .symbols = symbols_slice,
1014884 .strings = strings,
885 .ofiles = .empty,
1015886 };
1016887}
1017888
1018fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module {
1019 var di: Module = .{
1020 .base_address = undefined,
1021 .coff_image_base = coff_obj.getImageBase(),
1022 .coff_section_headers = undefined,
1023 };
1024
1025 if (coff_obj.getSectionByName(".debug_info")) |_| {
1026 // This coff file has embedded DWARF debug info
1027 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
1028 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1029
1030 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
1031 sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
1032 break :blk .{
1033 .data = try coff_obj.getSectionDataAlloc(section_header, allocator),
1034 .virtual_address = section_header.virtual_address,
1035 .owned = true,
1036 };
1037 } else null;
1038 }
1039
1040 var dwarf: Dwarf = .{
1041 .endian = native_endian,
1042 .sections = sections,
1043 .is_macho = false,
1044 };
1045
1046 try Dwarf.open(&dwarf, allocator);
1047 di.dwarf = dwarf;
1048 }
1049
1050 const raw_path = try coff_obj.getPdbPath() orelse return di;
1051 const path = blk: {
1052 if (fs.path.isAbsolute(raw_path)) {
1053 break :blk raw_path;
1054 } else {
1055 const self_dir = try fs.selfExeDirPathAlloc(allocator);
1056 defer allocator.free(self_dir);
1057 break :blk try fs.path.join(allocator, &.{ self_dir, raw_path });
1058 }
1059 };
1060 defer if (path.ptr != raw_path.ptr) allocator.free(path);
1061
1062 di.pdb = Pdb.init(allocator, path) catch |err| switch (err) {
1063 error.FileNotFound, error.IsDir => {
1064 if (di.dwarf == null) return error.MissingDebugInfo;
1065 return di;
1066 },
1067 else => return err,
1068 };
1069 try di.pdb.?.parseInfoStream();
1070 try di.pdb.?.parseDbiStream();
1071
1072 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
1073 return error.InvalidDebugInfo;
1074
1075 // Only used by the pdb path
1076 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
1077 errdefer allocator.free(di.coff_section_headers);
1078
1079 return di;
1080}
1081
1082/// Reads debug info from an ELF file, or the current binary if none in specified.
1083/// If the required sections aren't present but a reference to external debug info is,
1084/// then this this function will recurse to attempt to load the debug sections from
1085/// an external file.
1086pub fn readElfDebugInfo(
1087 em: *Dwarf.ElfModule,
1088 allocator: Allocator,
1089 elf_filename: ?[]const u8,
1090 build_id: ?[]const u8,
1091 parent_sections: *Dwarf.SectionArray,
1092) !void {
1093 const elf_file = (if (elf_filename) |filename| blk: {
1094 break :blk fs.cwd().openFile(filename, .{});
1095 } else fs.openSelfExe(.{})) catch |err| switch (err) {
1096 error.FileNotFound => return error.MissingDebugInfo,
1097 else => return err,
1098 };
1099
1100 const mapped_mem = try mapWholeFile(elf_file);
1101 return em.load(
1102 allocator,
1103 mapped_mem,
1104 build_id,
1105 null,
1106 parent_sections,
1107 null,
1108 elf_filename,
1109 );
1110}
1111
1112889const MachoSymbol = struct {
1113890 strx: u32,
1114891 addr: u64,
1115892 size: u32,
1116893 ofile: u32,
1117
1118 /// Returns the address from the macho file
1119 fn address(self: MachoSymbol) u64 {
1120 return self.addr;
1121 }
1122
1123894 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
1124895 _ = context;
1125896 return lhs.addr < rhs.addr;
1126897 }
1127};
1128
1129/// Takes ownership of file, even on error.
1130/// TODO it's weird to take ownership even on error, rework this code.
1131fn mapWholeFile(file: File) ![]align(std.heap.page_size_min) const u8 {
1132 defer file.close();
1133
1134 const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize);
1135 const mapped_mem = try posix.mmap(
1136 null,
1137 file_len,
1138 posix.PROT.READ,
1139 .{ .TYPE = .SHARED },
1140 file.handle,
1141 0,
1142 );
1143 errdefer posix.munmap(mapped_mem);
1144
1145 return mapped_mem;
1146}
1147
1148fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
1149 var min: usize = 0;
1150 var max: usize = symbols.len - 1;
1151 while (min < max) {
1152 const mid = min + (max - min) / 2;
1153 const curr = &symbols[mid];
1154 const next = &symbols[mid + 1];
1155 if (address >= next.address()) {
1156 min = mid + 1;
1157 } else if (address < curr.address()) {
1158 max = mid;
1159 } else {
1160 return curr;
1161 }
1162 }
1163
1164 const max_sym = &symbols[symbols.len - 1];
1165 if (address >= max_sym.address())
1166 return max_sym;
1167
1168 return null;
1169}
1170
1171test machoSearchSymbols {
1172 const symbols = [_]MachoSymbol{
1173 .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
1174 .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
1175 .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
1176 };
1177
1178 try testing.expectEqual(null, machoSearchSymbols(&symbols, 0));
1179 try testing.expectEqual(null, machoSearchSymbols(&symbols, 99));
1180 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?);
1181 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?);
1182 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?);
1183
1184 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?);
1185 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?);
1186 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?);
1187
1188 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?);
1189 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?);
1190 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?);
1191}
1192
1193/// Unwind a frame using MachO compact unwind info (from __unwind_info).
1194/// If the compact encoding can't encode a way to unwind a frame, it will
1195/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
1196fn unwindFrameMachO(
1197 allocator: Allocator,
1198 base_address: usize,
1199 context: *UnwindContext,
1200 unwind_info: []const u8,
1201 eh_frame: ?[]const u8,
1202) !usize {
1203 const header = std.mem.bytesAsValue(
1204 macho.unwind_info_section_header,
1205 unwind_info[0..@sizeOf(macho.unwind_info_section_header)],
1206 );
1207 const indices = std.mem.bytesAsSlice(
1208 macho.unwind_info_section_header_index_entry,
1209 unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)],
1210 );
1211 if (indices.len == 0) return error.MissingUnwindInfo;
1212
1213 const mapped_pc = context.pc - base_address;
1214 const second_level_index = blk: {
898 /// Assumes that `symbols` is sorted in order of ascending `addr`.
899 fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
900 if (symbols.len == 0) return null; // no potential match
901 if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
1215902 var left: usize = 0;
1216 var len: usize = indices.len;
1217
903 var len: usize = symbols.len;
1218904 while (len > 1) {
1219905 const mid = left + len / 2;
1220 const offset = indices[mid].functionOffset;
1221 if (mapped_pc < offset) {
906 if (address < symbols[mid].addr) {
1222907 len /= 2;
1223908 } else {
1224909 left = mid;
1225 if (mapped_pc == offset) break;
1226910 len -= len / 2;
1227911 }
1228912 }
913 return &symbols[left];
914 }
1229915
1230 // Last index is a sentinel containing the highest address as its functionOffset
1231 if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo;
1232 break :blk &indices[left];
1233 };
1234
1235 const common_encodings = std.mem.bytesAsSlice(
1236 macho.compact_unwind_encoding_t,
1237 unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)],
1238 );
1239
1240 const start_offset = second_level_index.secondLevelPagesSectionOffset;
1241 const kind = std.mem.bytesAsValue(
1242 macho.UNWIND_SECOND_LEVEL,
1243 unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)],
1244 );
1245
1246 const entry: struct {
1247 function_offset: usize,
1248 raw_encoding: u32,
1249 } = switch (kind.*) {
1250 .REGULAR => blk: {
1251 const page_header = std.mem.bytesAsValue(
1252 macho.unwind_info_regular_second_level_page_header,
1253 unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)],
1254 );
1255
1256 const entries = std.mem.bytesAsSlice(
1257 macho.unwind_info_regular_second_level_entry,
1258 unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)],
1259 );
1260 if (entries.len == 0) return error.InvalidUnwindInfo;
1261
1262 var left: usize = 0;
1263 var len: usize = entries.len;
1264 while (len > 1) {
1265 const mid = left + len / 2;
1266 const offset = entries[mid].functionOffset;
1267 if (mapped_pc < offset) {
1268 len /= 2;
1269 } else {
1270 left = mid;
1271 if (mapped_pc == offset) break;
1272 len -= len / 2;
1273 }
1274 }
916 test find {
917 const symbols: []const MachoSymbol = &.{
918 .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
919 .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
920 .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
921 };
1275922
1276 break :blk .{
1277 .function_offset = entries[left].functionOffset,
1278 .raw_encoding = entries[left].encoding,
1279 };
1280 },
1281 .COMPRESSED => blk: {
1282 const page_header = std.mem.bytesAsValue(
1283 macho.unwind_info_compressed_second_level_page_header,
1284 unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)],
1285 );
923 try testing.expectEqual(null, find(symbols, 0));
924 try testing.expectEqual(null, find(symbols, 99));
925 try testing.expectEqual(&symbols[0], find(symbols, 100).?);
926 try testing.expectEqual(&symbols[0], find(symbols, 150).?);
927 try testing.expectEqual(&symbols[0], find(symbols, 199).?);
1286928
1287 const entries = std.mem.bytesAsSlice(
1288 macho.UnwindInfoCompressedEntry,
1289 unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)],
1290 );
1291 if (entries.len == 0) return error.InvalidUnwindInfo;
929 try testing.expectEqual(&symbols[1], find(symbols, 200).?);
930 try testing.expectEqual(&symbols[1], find(symbols, 250).?);
931 try testing.expectEqual(&symbols[1], find(symbols, 299).?);
1292932
1293 var left: usize = 0;
1294 var len: usize = entries.len;
1295 while (len > 1) {
1296 const mid = left + len / 2;
1297 const offset = second_level_index.functionOffset + entries[mid].funcOffset;
1298 if (mapped_pc < offset) {
1299 len /= 2;
1300 } else {
1301 left = mid;
1302 if (mapped_pc == offset) break;
1303 len -= len / 2;
1304 }
1305 }
933 try testing.expectEqual(&symbols[2], find(symbols, 300).?);
934 try testing.expectEqual(&symbols[2], find(symbols, 301).?);
935 try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
936 }
937};
938test {
939 _ = MachoSymbol;
940}
1306941
1307 const entry = entries[left];
1308 const function_offset = second_level_index.functionOffset + entry.funcOffset;
1309 if (entry.encodingIndex < header.commonEncodingsArrayCount) {
1310 if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo;
1311 break :blk .{
1312 .function_offset = function_offset,
1313 .raw_encoding = common_encodings[entry.encodingIndex],
1314 };
1315 } else {
1316 const local_index = try math.sub(
1317 u8,
1318 entry.encodingIndex,
1319 math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo,
1320 );
1321 const local_encodings = std.mem.bytesAsSlice(
1322 macho.compact_unwind_encoding_t,
1323 unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)],
1324 );
1325 if (local_index >= local_encodings.len) return error.InvalidUnwindInfo;
1326 break :blk .{
1327 .function_offset = function_offset,
1328 .raw_encoding = local_encodings[local_index],
1329 };
1330 }
1331 },
1332 else => return error.InvalidUnwindInfo,
1333 };
942pub const UnwindContext = struct {
943 gpa: Allocator,
944 cfa: ?usize,
945 pc: usize,
946 thread_context: *std.debug.ThreadContext,
947 reg_context: Dwarf.abi.RegisterContext,
948 vm: Dwarf.Unwind.VirtualMachine,
949 stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
1334950
1335 if (entry.raw_encoding == 0) return error.NoUnwindInfo;
1336 const reg_context = Dwarf.abi.RegisterContext{
1337 .eh_frame = false,
1338 .is_macho = true,
1339 };
951 pub fn init(gpa: Allocator, thread_context: *std.debug.ThreadContext) !UnwindContext {
952 comptime assert(supports_unwinding);
1340953
1341 const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
1342 const new_ip = switch (builtin.cpu.arch) {
1343 .x86_64 => switch (encoding.mode.x86_64) {
1344 .OLD => return error.UnimplementedUnwindEncoding,
1345 .RBP_FRAME => blk: {
1346 const regs: [5]u3 = .{
1347 encoding.value.x86_64.frame.reg0,
1348 encoding.value.x86_64.frame.reg1,
1349 encoding.value.x86_64.frame.reg2,
1350 encoding.value.x86_64.frame.reg3,
1351 encoding.value.x86_64.frame.reg4,
1352 };
954 const pc = stripInstructionPtrAuthCode(
955 (try regValueNative(thread_context, ip_reg_num, null)).*,
956 );
1353957
1354 const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize);
1355 var max_reg: usize = 0;
1356 inline for (regs, 0..) |reg, i| {
1357 if (reg > 0) max_reg = i;
1358 }
958 const context_copy = try gpa.create(std.debug.ThreadContext);
959 std.debug.copyContext(thread_context, context_copy);
1359960
1360 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
1361 const new_sp = fp + 2 * @sizeOf(usize);
961 return .{
962 .gpa = gpa,
963 .cfa = null,
964 .pc = pc,
965 .thread_context = context_copy,
966 .reg_context = undefined,
967 .vm = .{},
968 .stack_machine = .{},
969 };
970 }
1362971
1363 const ip_ptr = fp + @sizeOf(usize);
1364 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
1365 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
972 pub fn deinit(self: *UnwindContext) void {
973 self.vm.deinit(self.gpa);
974 self.stack_machine.deinit(self.gpa);
975 self.gpa.destroy(self.thread_context);
976 self.* = undefined;
977 }
1366978
1367 (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
1368 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
1369 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
979 pub fn getFp(self: *const UnwindContext) !usize {
980 return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*;
981 }
1370982
1371 for (regs, 0..) |reg, i| {
1372 if (reg == 0) continue;
1373 const addr = fp - frame_offset + i * @sizeOf(usize);
1374 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
1375 (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
983 /// Resolves the register rule and places the result into `out` (see regBytes)
984 pub fn resolveRegisterRule(
985 context: *UnwindContext,
986 col: Dwarf.Unwind.VirtualMachine.Column,
987 expression_context: std.debug.Dwarf.expression.Context,
988 out: []u8,
989 ) !void {
990 switch (col.rule) {
991 .default => {
992 const register = col.register orelse return error.InvalidRegister;
993 // The default type is usually undefined, but can be overriden by ABI authors.
994 // See the doc comment on `Dwarf.Unwind.VirtualMachine.RegisterRule.default`.
995 if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 18) {
996 // Callee-saved registers are initialized as if they had the .same_value rule
997 const src = try regBytes(context.thread_context, register, context.reg_context);
998 if (src.len != out.len) return error.RegisterSizeMismatch;
999 @memcpy(out, src);
1000 return;
13761001 }
1377
1378 break :blk new_ip;
1002 @memset(out, undefined);
13791003 },
1380 .STACK_IMMD,
1381 .STACK_IND,
1382 => blk: {
1383 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
1384 const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD)
1385 @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize)
1386 else stack_size: {
1387 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
1388 const sub_offset_addr =
1389 base_address +
1390 entry.function_offset +
1391 encoding.value.x86_64.frameless.stack.indirect.sub_offset;
1392
1393 // `sub_offset_addr` points to the offset of the literal within the instruction
1394 const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
1395 break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust);
1396 };
1397
1398 // Decode the Lehmer-coded sequence of registers.
1399 // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
1400
1401 // Decode the variable-based permutation number into its digits. Each digit represents
1402 // an index into the list of register numbers that weren't yet used in the sequence at
1403 // the time the digit was added.
1404 const reg_count = encoding.value.x86_64.frameless.stack_reg_count;
1405 const ip_ptr = if (reg_count > 0) reg_blk: {
1406 var digits: [6]u3 = undefined;
1407 var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation;
1408 var base: usize = 2;
1409 for (0..reg_count) |i| {
1410 const div = accumulator / base;
1411 digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
1412 accumulator = div;
1413 base += 1;
1414 }
1415
1416 const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 };
1417 var registers: [reg_numbers.len]u3 = undefined;
1418 var used_indices = [_]bool{false} ** reg_numbers.len;
1419 for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
1420 var unused_count: u8 = 0;
1421 const unused_index = for (used_indices, 0..) |used, index| {
1422 if (!used) {
1423 if (target_unused_index == unused_count) break index;
1424 unused_count += 1;
1425 }
1426 } else unreachable;
1427
1428 registers[i] = reg_numbers[unused_index];
1429 used_indices[unused_index] = true;
1430 }
1431
1432 var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
1433 for (0..reg_count) |i| {
1434 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
1435 (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1436 reg_addr += @sizeOf(usize);
1437 }
1438
1439 break :reg_blk reg_addr;
1440 } else sp + stack_size - @sizeOf(usize);
1441
1442 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
1443 const new_sp = ip_ptr + @sizeOf(usize);
1444
1445 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
1446 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
1447
1448 break :blk new_ip;
1004 .undefined => {
1005 @memset(out, undefined);
14491006 },
1450 .DWARF => {
1451 return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));
1007 .same_value => {
1008 // TODO: This copy could be eliminated if callers always copy the state then call this function to update it
1009 const register = col.register orelse return error.InvalidRegister;
1010 const src = try regBytes(context.thread_context, register, context.reg_context);
1011 if (src.len != out.len) return error.RegisterSizeMismatch;
1012 @memcpy(out, src);
14521013 },
1453 },
1454 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
1455 .OLD => return error.UnimplementedUnwindEncoding,
1456 .FRAMELESS => blk: {
1457 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
1458 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
1459 const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*;
1460 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
1461 break :blk new_ip;
1014 .offset => |offset| {
1015 if (context.cfa) |cfa| {
1016 const addr = try applyOffset(cfa, offset);
1017 const ptr: *const usize = @ptrFromInt(addr);
1018 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
1019 } else return error.InvalidCFA;
14621020 },
1463 .DWARF => {
1464 return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));
1021 .val_offset => |offset| {
1022 if (context.cfa) |cfa| {
1023 mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian);
1024 } else return error.InvalidCFA;
14651025 },
1466 .FRAME => blk: {
1467 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
1468 const ip_ptr = fp + @sizeOf(usize);
1469
1470 var reg_addr = fp - @sizeOf(usize);
1471 inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
1472 if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) {
1473 (try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1474 reg_addr += @sizeOf(usize);
1475 (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1476 reg_addr += @sizeOf(usize);
1477 }
1478 }
1479
1480 inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| {
1481 if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) {
1482 // Only the lower half of the 128-bit V registers are restored during unwinding
1483 @memcpy(
1484 try regBytes(context.thread_context, 64 + 8 + i, context.reg_context),
1485 std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
1486 );
1487 reg_addr += @sizeOf(usize);
1488 @memcpy(
1489 try regBytes(context.thread_context, 64 + 9 + i, context.reg_context),
1490 std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
1491 );
1492 reg_addr += @sizeOf(usize);
1493 }
1494 }
1495
1496 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
1497 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
1498
1499 (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
1500 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
1501
1502 break :blk new_ip;
1026 .register => |register| {
1027 const src = try regBytes(context.thread_context, register, context.reg_context);
1028 if (src.len != out.len) return error.RegisterSizeMismatch;
1029 @memcpy(out, try regBytes(context.thread_context, register, context.reg_context));
15031030 },
1504 },
1505 else => return error.UnimplementedArch,
1506 };
1507
1508 context.pc = stripInstructionPtrAuthCode(new_ip);
1509 if (context.pc > 0) context.pc -= 1;
1510 return new_ip;
1511}
1512
1513pub const UnwindContext = struct {
1514 allocator: Allocator,
1515 cfa: ?usize,
1516 pc: usize,
1517 thread_context: *std.debug.ThreadContext,
1518 reg_context: Dwarf.abi.RegisterContext,
1519 vm: VirtualMachine,
1520 stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
1521
1522 pub fn init(
1523 allocator: Allocator,
1524 thread_context: *std.debug.ThreadContext,
1525 ) !UnwindContext {
1526 comptime assert(supports_unwinding);
1527
1528 const pc = stripInstructionPtrAuthCode(
1529 (try regValueNative(thread_context, ip_reg_num, null)).*,
1530 );
1531
1532 const context_copy = try allocator.create(std.debug.ThreadContext);
1533 std.debug.copyContext(thread_context, context_copy);
1534
1535 return .{
1536 .allocator = allocator,
1537 .cfa = null,
1538 .pc = pc,
1539 .thread_context = context_copy,
1540 .reg_context = undefined,
1541 .vm = .{},
1542 .stack_machine = .{},
1543 };
1544 }
1545
1546 pub fn deinit(self: *UnwindContext) void {
1547 self.vm.deinit(self.allocator);
1548 self.stack_machine.deinit(self.allocator);
1549 self.allocator.destroy(self.thread_context);
1550 self.* = undefined;
1551 }
1552
1553 pub fn getFp(self: *const UnwindContext) !usize {
1554 return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*;
1031 .expression => |expression| {
1032 context.stack_machine.reset();
1033 const value = try context.stack_machine.run(expression, context.gpa, expression_context, context.cfa.?);
1034 const addr = if (value) |v| blk: {
1035 if (v != .generic) return error.InvalidExpressionValue;
1036 break :blk v.generic;
1037 } else return error.NoExpressionValue;
1038
1039 const ptr: *usize = @ptrFromInt(addr);
1040 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
1041 },
1042 .val_expression => |expression| {
1043 context.stack_machine.reset();
1044 const value = try context.stack_machine.run(expression, context.gpa, expression_context, context.cfa.?);
1045 if (value) |v| {
1046 if (v != .generic) return error.InvalidExpressionValue;
1047 mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian);
1048 } else return error.NoExpressionValue;
1049 },
1050 .architectural => return error.UnimplementedRegisterRule,
1051 }
15551052 }
15561053};
15571054
......@@ -1584,113 +1081,30 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
15841081/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
15851082/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
15861083fn unwindFrameDwarf(
1587 allocator: Allocator,
1588 unwind: *Dwarf.Unwind,
1589 base_address: usize,
1084 unwind: *const Dwarf.Unwind,
1085 load_offset: usize,
15901086 context: *UnwindContext,
15911087 explicit_fde_offset: ?usize,
15921088) !usize {
15931089 if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
15941090 if (context.pc == 0) return 0;
15951091
1596 // Find the FDE and CIE
1597 const cie, const fde = if (explicit_fde_offset) |fde_offset| blk: {
1598 const frame_section = unwind.section(.eh_frame) orelse return error.MissingFDE;
1599 if (fde_offset >= frame_section.len) return error.MissingFDE;
1600
1601 var fbr: std.Io.Reader = .fixed(frame_section);
1602 fbr.seek = fde_offset;
1092 const pc_vaddr = context.pc - load_offset;
16031093
1604 const fde_entry_header = try Dwarf.Unwind.EntryHeader.read(&fbr, .eh_frame, native_endian);
1605 if (fde_entry_header.type != .fde) return error.MissingFDE;
1094 const fde_offset = explicit_fde_offset orelse try unwind.findFdeOffset(
1095 pc_vaddr,
1096 @sizeOf(usize),
1097 native_endian,
1098 ) orelse return error.MissingDebugInfo;
1099 const format, const cie, const fde = try unwind.loadFde(fde_offset, @sizeOf(usize), native_endian);
16061100
1607 const cie_offset = fde_entry_header.type.fde;
1608 fbr.seek = @intCast(cie_offset);
1609
1610 const cie_entry_header = try Dwarf.Unwind.EntryHeader.read(&fbr, .eh_frame, native_endian);
1611 if (cie_entry_header.type != .cie) return Dwarf.bad();
1612
1613 const cie = try Dwarf.Unwind.CommonInformationEntry.parse(
1614 cie_entry_header.entry_bytes,
1615 0,
1616 true,
1617 cie_entry_header.format,
1618 .eh_frame,
1619 cie_entry_header.length_offset,
1620 @sizeOf(usize),
1621 native_endian,
1622 );
1623 const fde = try Dwarf.Unwind.FrameDescriptionEntry.parse(
1624 fde_entry_header.entry_bytes,
1625 0,
1626 true,
1627 cie,
1628 @sizeOf(usize),
1629 native_endian,
1630 );
1631
1632 break :blk .{ cie, fde };
1633 } else blk: {
1634 // `.eh_frame_hdr` may be incomplete. We'll try it first, but if the lookup fails, we fall
1635 // back to loading `.eh_frame`/`.debug_frame` and using those from that point on.
1636
1637 if (unwind.eh_frame_hdr) |header| hdr: {
1638 const eh_frame_len = if (unwind.section(.eh_frame)) |eh_frame| eh_frame.len else {
1639 try unwind.scanCieFdeInfo(allocator, native_endian, base_address);
1640 unwind.eh_frame_hdr = null;
1641 break :hdr;
1642 };
1643
1644 var cie: Dwarf.Unwind.CommonInformationEntry = undefined;
1645 var fde: Dwarf.Unwind.FrameDescriptionEntry = undefined;
1646
1647 header.findEntry(
1648 eh_frame_len,
1649 @intFromPtr(unwind.section(.eh_frame_hdr).?.ptr),
1650 context.pc,
1651 &cie,
1652 &fde,
1653 native_endian,
1654 ) catch |err| switch (err) {
1655 error.MissingDebugInfo => {
1656 // `.eh_frame_hdr` appears to be incomplete, so go ahead and populate `cie_map`
1657 // and `fde_list`, and fall back to the binary search logic below.
1658 try unwind.scanCieFdeInfo(allocator, native_endian, base_address);
1659
1660 // Since `.eh_frame_hdr` is incomplete, we're very likely to get more lookup
1661 // failures using it, and we've just built a complete, sorted list of FDEs
1662 // anyway, so just stop using `.eh_frame_hdr` altogether.
1663 unwind.eh_frame_hdr = null;
1664
1665 break :hdr;
1666 },
1667 else => return err,
1668 };
1669
1670 break :blk .{ cie, fde };
1671 }
1672
1673 const index = std.sort.binarySearch(Dwarf.Unwind.FrameDescriptionEntry, unwind.fde_list.items, context.pc, struct {
1674 pub fn compareFn(pc: usize, item: Dwarf.Unwind.FrameDescriptionEntry) std.math.Order {
1675 if (pc < item.pc_begin) return .lt;
1676
1677 const range_end = item.pc_begin + item.pc_range;
1678 if (pc < range_end) return .eq;
1679
1680 return .gt;
1681 }
1682 }.compareFn);
1683
1684 const fde = if (index) |i| unwind.fde_list.items[i] else return error.MissingFDE;
1685 const cie = unwind.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;
1686
1687 break :blk .{ cie, fde };
1688 };
1101 // Check if this FDE *actually* includes the address.
1102 if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo;
16891103
16901104 // Do not set `compile_unit` because the spec states that CFIs
16911105 // may not reference other debug sections anyway.
16921106 var expression_context: Dwarf.expression.Context = .{
1693 .format = cie.format,
1107 .format = format,
16941108 .thread_context = context.thread_context,
16951109 .reg_context = context.reg_context,
16961110 .cfa = context.cfa,
......@@ -1700,7 +1114,7 @@ fn unwindFrameDwarf(
17001114 context.reg_context.eh_frame = cie.version != 4;
17011115 context.reg_context.is_macho = native_os.isDarwin();
17021116
1703 const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde);
1117 const row = try context.vm.runTo(context.gpa, context.pc - load_offset, cie, fde, @sizeOf(usize), native_endian);
17041118 context.cfa = switch (row.cfa.rule) {
17051119 .val_offset => |offset| blk: {
17061120 const register = row.cfa.register orelse return error.InvalidCFARule;
......@@ -1711,7 +1125,7 @@ fn unwindFrameDwarf(
17111125 context.stack_machine.reset();
17121126 const value = try context.stack_machine.run(
17131127 expr,
1714 context.allocator,
1128 context.gpa,
17151129 expression_context,
17161130 context.cfa,
17171131 );
......@@ -1728,9 +1142,9 @@ fn unwindFrameDwarf(
17281142
17291143 // Buffering the modifications is done because copying the thread context is not portable,
17301144 // some implementations (ie. darwin) use internal pointers to the mcontext.
1731 var arena = std.heap.ArenaAllocator.init(context.allocator);
1145 var arena: std.heap.ArenaAllocator = .init(context.gpa);
17321146 defer arena.deinit();
1733 const update_allocator = arena.allocator();
1147 const update_arena = arena.allocator();
17341148
17351149 const RegisterUpdate = struct {
17361150 // Backed by thread_context
......@@ -1749,17 +1163,16 @@ fn unwindFrameDwarf(
17491163 }
17501164
17511165 const dest = try regBytes(context.thread_context, register, context.reg_context);
1752 const src = try update_allocator.alloc(u8, dest.len);
1166 const src = try update_arena.alloc(u8, dest.len);
1167 try context.resolveRegisterRule(column, expression_context, src);
17531168
1754 const prev = update_tail;
1755 update_tail = try update_allocator.create(RegisterUpdate);
1756 update_tail.?.* = .{
1169 const new_update = try update_arena.create(RegisterUpdate);
1170 new_update.* = .{
17571171 .dest = dest,
17581172 .src = src,
1759 .prev = prev,
1173 .prev = update_tail,
17601174 };
1761
1762 try column.resolveValue(context, expression_context, src);
1175 update_tail = new_update;
17631176 }
17641177 }
17651178
......@@ -1792,7 +1205,7 @@ fn unwindFrameDwarf(
17921205 // The exception to this rule is signal frames, where we return execution would be returned to the instruction
17931206 // that triggered the handler.
17941207 const return_address = context.pc;
1795 if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1;
1208 if (context.pc > 0 and !cie.is_signal_frame) context.pc -= 1;
17961209
17971210 return return_address;
17981211}
......@@ -1843,415 +1256,345 @@ pub fn supportsUnwinding(target: *const std.Target) bool {
18431256 };
18441257}
18451258
1846fn unwindFrameMachODwarf(
1847 allocator: Allocator,
1848 base_address: usize,
1849 context: *UnwindContext,
1850 eh_frame: []const u8,
1851 fde_offset: usize,
1852) !usize {
1853 var di: Dwarf = .{
1854 .endian = native_endian,
1855 .is_macho = true,
1856 };
1857 defer di.deinit(context.allocator);
1259/// Since register rules are applied (usually) during a panic,
1260/// checked addition / subtraction is used so that we can return
1261/// an error and fall back to FP-based unwinding.
1262fn applyOffset(base: usize, offset: i64) !usize {
1263 return if (offset >= 0)
1264 try std.math.add(usize, base, @as(usize, @intCast(offset)))
1265 else
1266 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
1267}
18581268
1859 di.sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{
1860 .data = eh_frame,
1861 .owned = false,
1862 };
1269/// Uses `mmap` to map the file at `opt_path` (or, if `null`, the self executable image) into memory.
1270fn mapFileOrSelfExe(opt_path: ?[]const u8) ![]align(std.heap.page_size_min) const u8 {
1271 const file = if (opt_path) |path|
1272 try fs.cwd().openFile(path, .{})
1273 else
1274 try fs.openSelfExe(.{});
1275 defer file.close();
18631276
1864 return unwindFrameDwarf(allocator, &di, base_address, context, fde_offset);
1277 const file_len = math.cast(usize, try file.getEndPos()) orelse return error.FileTooBig;
1278
1279 return posix.mmap(
1280 null,
1281 file_len,
1282 posix.PROT.READ,
1283 .{ .TYPE = .SHARED },
1284 file.handle,
1285 0,
1286 );
18651287}
18661288
1867/// This is a virtual machine that runs DWARF call frame instructions.
1868pub const VirtualMachine = struct {
1869 /// See section 6.4.1 of the DWARF5 specification for details on each
1870 const RegisterRule = union(enum) {
1871 // The spec says that the default rule for each column is the undefined rule.
1872 // However, it also allows ABI / compiler authors to specify alternate defaults, so
1873 // there is a distinction made here.
1874 default: void,
1875 undefined: void,
1876 same_value: void,
1877 // offset(N)
1878 offset: i64,
1879 // val_offset(N)
1880 val_offset: i64,
1881 // register(R)
1882 register: u8,
1883 // expression(E)
1884 expression: []const u8,
1885 // val_expression(E)
1886 val_expression: []const u8,
1887 // Augmenter-defined rule
1888 architectural: void,
1889 };
1289/// Unwind a frame using MachO compact unwind info (from __unwind_info).
1290/// If the compact encoding can't encode a way to unwind a frame, it will
1291/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
1292fn unwindFrameMachO(
1293 text_base: usize,
1294 load_offset: usize,
1295 context: *UnwindContext,
1296 unwind_info: []const u8,
1297 eh_frame: ?[]const u8,
1298) !usize {
1299 if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
1300 const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
18901301
1891 /// Each row contains unwinding rules for a set of registers.
1892 pub const Row = struct {
1893 /// Offset from `FrameDescriptionEntry.pc_begin`
1894 offset: u64 = 0,
1895 /// Special-case column that defines the CFA (Canonical Frame Address) rule.
1896 /// The register field of this column defines the register that CFA is derived from.
1897 cfa: Column = .{},
1898 /// The register fields in these columns define the register the rule applies to.
1899 columns: ColumnRange = .{},
1900 /// Indicates that the next write to any column in this row needs to copy
1901 /// the backing column storage first, as it may be referenced by previous rows.
1902 copy_on_write: bool = false,
1903 };
1302 const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry);
1303 if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidUnwindInfo;
1304 const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
1305 if (indices.len == 0) return error.MissingUnwindInfo;
19041306
1905 pub const Column = struct {
1906 register: ?u8 = null,
1907 rule: RegisterRule = .{ .default = {} },
1908
1909 /// Resolves the register rule and places the result into `out` (see regBytes)
1910 pub fn resolveValue(
1911 self: Column,
1912 context: *SelfInfo.UnwindContext,
1913 expression_context: std.debug.Dwarf.expression.Context,
1914 out: []u8,
1915 ) !void {
1916 switch (self.rule) {
1917 .default => {
1918 const register = self.register orelse return error.InvalidRegister;
1919 try getRegDefaultValue(register, context, out);
1920 },
1921 .undefined => {
1922 @memset(out, undefined);
1923 },
1924 .same_value => {
1925 // TODO: This copy could be eliminated if callers always copy the state then call this function to update it
1926 const register = self.register orelse return error.InvalidRegister;
1927 const src = try regBytes(context.thread_context, register, context.reg_context);
1928 if (src.len != out.len) return error.RegisterSizeMismatch;
1929 @memcpy(out, src);
1930 },
1931 .offset => |offset| {
1932 if (context.cfa) |cfa| {
1933 const addr = try applyOffset(cfa, offset);
1934 const ptr: *const usize = @ptrFromInt(addr);
1935 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
1936 } else return error.InvalidCFA;
1937 },
1938 .val_offset => |offset| {
1939 if (context.cfa) |cfa| {
1940 mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian);
1941 } else return error.InvalidCFA;
1942 },
1943 .register => |register| {
1944 const src = try regBytes(context.thread_context, register, context.reg_context);
1945 if (src.len != out.len) return error.RegisterSizeMismatch;
1946 @memcpy(out, try regBytes(context.thread_context, register, context.reg_context));
1947 },
1948 .expression => |expression| {
1949 context.stack_machine.reset();
1950 const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?);
1951 const addr = if (value) |v| blk: {
1952 if (v != .generic) return error.InvalidExpressionValue;
1953 break :blk v.generic;
1954 } else return error.NoExpressionValue;
1955
1956 const ptr: *usize = @ptrFromInt(addr);
1957 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
1958 },
1959 .val_expression => |expression| {
1960 context.stack_machine.reset();
1961 const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?);
1962 if (value) |v| {
1963 if (v != .generic) return error.InvalidExpressionValue;
1964 mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian);
1965 } else return error.NoExpressionValue;
1966 },
1967 .architectural => return error.UnimplementedRegisterRule,
1307 // MLUGG TODO HACKHACK -- Unwind needs a slight refactor to make this work well
1308 const opt_dwarf_unwind: ?Dwarf.Unwind = if (eh_frame) |eh_frame_data| .{
1309 .debug_frame = null,
1310 .eh_frame = .{
1311 .header = .{
1312 .vaddr = undefined,
1313 .eh_frame_vaddr = @intFromPtr(eh_frame_data.ptr) - load_offset,
1314 .search_table = null,
1315 },
1316 .eh_frame_data = eh_frame_data,
1317 .sorted_fdes = null,
1318 },
1319 } else null;
1320
1321 // offset of the PC into the `__TEXT` segment
1322 const pc_text_offset = context.pc - text_base;
1323
1324 const start_offset: u32, const first_level_offset: u32 = index: {
1325 var left: usize = 0;
1326 var len: usize = indices.len;
1327 while (len > 1) {
1328 const mid = left + len / 2;
1329 if (pc_text_offset < indices[mid].functionOffset) {
1330 len /= 2;
1331 } else {
1332 left = mid;
1333 len -= len / 2;
19681334 }
19691335 }
1336 break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset };
19701337 };
1338 // An offset of 0 is a sentinel indicating a range does not have unwind info.
1339 if (start_offset == 0) return error.MissingUnwindInfo;
19711340
1972 const ColumnRange = struct {
1973 /// Index into `columns` of the first column in this row.
1974 start: usize = undefined,
1975 len: u8 = 0,
1976 };
1341 const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t);
1342 if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidUnwindInfo;
1343 const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
1344 unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
1345 );
19771346
1978 columns: std.ArrayListUnmanaged(Column) = .empty,
1979 stack: std.ArrayListUnmanaged(ColumnRange) = .empty,
1980 current_row: Row = .{},
1347 if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidUnwindInfo;
1348 const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
19811349
1982 /// The result of executing the CIE's initial_instructions
1983 cie_row: ?Row = null,
1350 const entry: struct {
1351 function_offset: usize,
1352 raw_encoding: u32,
1353 } = switch (kind.*) {
1354 .REGULAR => entry: {
1355 if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidUnwindInfo;
1356 const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
1357
1358 const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry);
1359 if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidUnwindInfo;
1360 const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast(
1361 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
1362 );
1363 if (entries.len == 0) return error.InvalidUnwindInfo;
19841364
1985 pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void {
1986 self.stack.deinit(allocator);
1987 self.columns.deinit(allocator);
1988 self.* = undefined;
1989 }
1365 var left: usize = 0;
1366 var len: usize = entries.len;
1367 while (len > 1) {
1368 const mid = left + len / 2;
1369 if (pc_text_offset < entries[mid].functionOffset) {
1370 len /= 2;
1371 } else {
1372 left = mid;
1373 len -= len / 2;
1374 }
1375 }
1376 break :entry .{
1377 .function_offset = entries[left].functionOffset,
1378 .raw_encoding = entries[left].encoding,
1379 };
1380 },
1381 .COMPRESSED => entry: {
1382 if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidUnwindInfo;
1383 const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
1384
1385 const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry);
1386 if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidUnwindInfo;
1387 const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast(
1388 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
1389 );
1390 if (entries.len == 0) return error.InvalidUnwindInfo;
19901391
1991 pub fn reset(self: *VirtualMachine) void {
1992 self.stack.clearRetainingCapacity();
1993 self.columns.clearRetainingCapacity();
1994 self.current_row = .{};
1995 self.cie_row = null;
1996 }
1392 var left: usize = 0;
1393 var len: usize = entries.len;
1394 while (len > 1) {
1395 const mid = left + len / 2;
1396 if (pc_text_offset < first_level_offset + entries[mid].funcOffset) {
1397 len /= 2;
1398 } else {
1399 left = mid;
1400 len -= len / 2;
1401 }
1402 }
1403 const entry = entries[left];
19971404
1998 /// Return a slice backed by the row's non-CFA columns
1999 pub fn rowColumns(self: VirtualMachine, row: Row) []Column {
2000 if (row.columns.len == 0) return &.{};
2001 return self.columns.items[row.columns.start..][0..row.columns.len];
2002 }
1405 const function_offset = first_level_offset + entry.funcOffset;
1406 if (entry.encodingIndex < common_encodings.len) {
1407 break :entry .{
1408 .function_offset = function_offset,
1409 .raw_encoding = common_encodings[entry.encodingIndex],
1410 };
1411 }
20031412
2004 /// Either retrieves or adds a column for `register` (non-CFA) in the current row.
2005 fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column {
2006 for (self.rowColumns(self.current_row)) |*c| {
2007 if (c.register == register) return c;
2008 }
1413 const local_index = entry.encodingIndex - common_encodings.len;
1414 const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t);
1415 if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidUnwindInfo;
1416 const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
1417 unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count],
1418 );
1419 if (local_index >= local_encodings.len) return error.InvalidUnwindInfo;
1420 break :entry .{
1421 .function_offset = function_offset,
1422 .raw_encoding = local_encodings[local_index],
1423 };
1424 },
1425 else => return error.InvalidUnwindInfo,
1426 };
20091427
2010 if (self.current_row.columns.len == 0) {
2011 self.current_row.columns.start = self.columns.items.len;
2012 }
2013 self.current_row.columns.len += 1;
1428 if (entry.raw_encoding == 0) return error.NoUnwindInfo;
1429 const reg_context: Dwarf.abi.RegisterContext = .{ .eh_frame = false, .is_macho = true };
20141430
2015 const column = try self.columns.addOne(allocator);
2016 column.* = .{
2017 .register = register,
2018 };
1431 const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
1432 const new_ip = switch (builtin.cpu.arch) {
1433 .x86_64 => switch (encoding.mode.x86_64) {
1434 .OLD => return error.UnimplementedUnwindEncoding,
1435 .RBP_FRAME => ip: {
1436 const frame = encoding.value.x86_64.frame;
20191437
2020 return column;
2021 }
1438 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
1439 const new_sp = fp + 2 * @sizeOf(usize);
20221440
2023 /// Runs the CIE instructions, then the FDE instructions. Execution halts
2024 /// once the row that corresponds to `pc` is known, and the row is returned.
2025 pub fn runTo(
2026 self: *VirtualMachine,
2027 allocator: std.mem.Allocator,
2028 pc: u64,
2029 cie: std.debug.Dwarf.Unwind.CommonInformationEntry,
2030 fde: std.debug.Dwarf.Unwind.FrameDescriptionEntry,
2031 addr_size_bytes: u8,
2032 endian: std.builtin.Endian,
2033 ) !Row {
2034 assert(self.cie_row == null);
2035 if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange;
2036
2037 var prev_row: Row = self.current_row;
2038
2039 var cie_stream: std.Io.Reader = .fixed(cie.initial_instructions);
2040 var fde_stream: std.Io.Reader = .fixed(fde.instructions);
2041 const streams = [_]*std.Io.Reader{ &cie_stream, &fde_stream };
2042
2043 for (&streams, 0..) |stream, i| {
2044 while (stream.seek < stream.buffer.len) {
2045 const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian);
2046 prev_row = try self.step(allocator, cie, i == 0, instruction);
2047 if (pc < fde.pc_begin + self.current_row.offset) return prev_row;
2048 }
2049 }
1441 const ip_ptr = fp + @sizeOf(usize);
1442 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
1443 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
20501444
2051 return self.current_row;
2052 }
1445 (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
1446 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
1447 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
20531448
2054 pub fn runToNative(
2055 self: *VirtualMachine,
2056 allocator: std.mem.Allocator,
2057 pc: u64,
2058 cie: std.debug.Dwarf.Unwind.CommonInformationEntry,
2059 fde: std.debug.Dwarf.Unwind.FrameDescriptionEntry,
2060 ) !Row {
2061 return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), native_endian);
2062 }
1449 const regs: [5]u3 = .{
1450 frame.reg0,
1451 frame.reg1,
1452 frame.reg2,
1453 frame.reg3,
1454 frame.reg4,
1455 };
1456 for (regs, 0..) |reg, i| {
1457 if (reg == 0) continue;
1458 const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize);
1459 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
1460 (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
1461 }
20631462
2064 fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void {
2065 if (!self.current_row.copy_on_write) return;
1463 break :ip new_ip;
1464 },
1465 .STACK_IMMD,
1466 .STACK_IND,
1467 => ip: {
1468 const frameless = encoding.value.x86_64.frameless;
20661469
2067 const new_start = self.columns.items.len;
2068 if (self.current_row.columns.len > 0) {
2069 try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len);
2070 self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row));
2071 self.current_row.columns.start = new_start;
2072 }
2073 }
1470 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
1471 const stack_size: usize = stack_size: {
1472 if (encoding.mode.x86_64 == .STACK_IMMD) {
1473 break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize);
1474 }
1475 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
1476 const sub_offset_addr =
1477 text_base +
1478 entry.function_offset +
1479 frameless.stack.indirect.sub_offset;
1480 // `sub_offset_addr` points to the offset of the literal within the instruction
1481 const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
1482 break :stack_size sub_operand + @sizeOf(usize) * @as(usize, frameless.stack.indirect.stack_adjust);
1483 };
20741484
2075 /// Executes a single instruction.
2076 /// If this instruction is from the CIE, `is_initial` should be set.
2077 /// Returns the value of `current_row` before executing this instruction.
2078 pub fn step(
2079 self: *VirtualMachine,
2080 allocator: std.mem.Allocator,
2081 cie: std.debug.Dwarf.Unwind.CommonInformationEntry,
2082 is_initial: bool,
2083 instruction: Dwarf.call_frame.Instruction,
2084 ) !Row {
2085 // CIE instructions must be run before FDE instructions
2086 assert(!is_initial or self.cie_row == null);
2087 if (!is_initial and self.cie_row == null) {
2088 self.cie_row = self.current_row;
2089 self.current_row.copy_on_write = true;
2090 }
1485 // Decode the Lehmer-coded sequence of registers.
1486 // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
20911487
2092 const prev_row = self.current_row;
2093 switch (instruction) {
2094 .set_loc => |i| {
2095 if (i.address <= self.current_row.offset) return error.InvalidOperation;
2096 // TODO: Check cie.segment_selector_size != 0 for DWARFV4
2097 self.current_row.offset = i.address;
2098 },
2099 inline .advance_loc,
2100 .advance_loc1,
2101 .advance_loc2,
2102 .advance_loc4,
2103 => |i| {
2104 self.current_row.offset += i.delta * cie.code_alignment_factor;
2105 self.current_row.copy_on_write = true;
2106 },
2107 inline .offset,
2108 .offset_extended,
2109 .offset_extended_sf,
2110 => |i| {
2111 try self.resolveCopyOnWrite(allocator);
2112 const column = try self.getOrAddColumn(allocator, i.register);
2113 column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor };
2114 },
2115 inline .restore,
2116 .restore_extended,
2117 => |i| {
2118 try self.resolveCopyOnWrite(allocator);
2119 if (self.cie_row) |cie_row| {
2120 const column = try self.getOrAddColumn(allocator, i.register);
2121 column.rule = for (self.rowColumns(cie_row)) |cie_column| {
2122 if (cie_column.register == i.register) break cie_column.rule;
2123 } else .{ .default = {} };
2124 } else return error.InvalidOperation;
2125 },
2126 .nop => {},
2127 .undefined => |i| {
2128 try self.resolveCopyOnWrite(allocator);
2129 const column = try self.getOrAddColumn(allocator, i.register);
2130 column.rule = .{ .undefined = {} };
2131 },
2132 .same_value => |i| {
2133 try self.resolveCopyOnWrite(allocator);
2134 const column = try self.getOrAddColumn(allocator, i.register);
2135 column.rule = .{ .same_value = {} };
2136 },
2137 .register => |i| {
2138 try self.resolveCopyOnWrite(allocator);
2139 const column = try self.getOrAddColumn(allocator, i.register);
2140 column.rule = .{ .register = i.target_register };
2141 },
2142 .remember_state => {
2143 try self.stack.append(allocator, self.current_row.columns);
2144 self.current_row.copy_on_write = true;
2145 },
2146 .restore_state => {
2147 const restored_columns = self.stack.pop() orelse return error.InvalidOperation;
2148 self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len);
2149 try self.columns.ensureUnusedCapacity(allocator, restored_columns.len);
2150
2151 self.current_row.columns.start = self.columns.items.len;
2152 self.current_row.columns.len = restored_columns.len;
2153 self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]);
2154 },
2155 .def_cfa => |i| {
2156 try self.resolveCopyOnWrite(allocator);
2157 self.current_row.cfa = .{
2158 .register = i.register,
2159 .rule = .{ .val_offset = @intCast(i.offset) },
2160 };
2161 },
2162 .def_cfa_sf => |i| {
2163 try self.resolveCopyOnWrite(allocator);
2164 self.current_row.cfa = .{
2165 .register = i.register,
2166 .rule = .{ .val_offset = i.offset * cie.data_alignment_factor },
2167 };
2168 },
2169 .def_cfa_register => |i| {
2170 try self.resolveCopyOnWrite(allocator);
2171 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
2172 self.current_row.cfa.register = i.register;
2173 },
2174 .def_cfa_offset => |i| {
2175 try self.resolveCopyOnWrite(allocator);
2176 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
2177 self.current_row.cfa.rule = .{
2178 .val_offset = @intCast(i.offset),
2179 };
2180 },
2181 .def_cfa_offset_sf => |i| {
2182 try self.resolveCopyOnWrite(allocator);
2183 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
2184 self.current_row.cfa.rule = .{
2185 .val_offset = i.offset * cie.data_alignment_factor,
2186 };
2187 },
2188 .def_cfa_expression => |i| {
2189 try self.resolveCopyOnWrite(allocator);
2190 self.current_row.cfa.register = undefined;
2191 self.current_row.cfa.rule = .{
2192 .expression = i.block,
2193 };
2194 },
2195 .expression => |i| {
2196 try self.resolveCopyOnWrite(allocator);
2197 const column = try self.getOrAddColumn(allocator, i.register);
2198 column.rule = .{
2199 .expression = i.block,
1488 // Decode the variable-based permutation number into its digits. Each digit represents
1489 // an index into the list of register numbers that weren't yet used in the sequence at
1490 // the time the digit was added.
1491 const reg_count = frameless.stack_reg_count;
1492 const ip_ptr = ip_ptr: {
1493 var digits: [6]u3 = undefined;
1494 var accumulator: usize = frameless.stack_reg_permutation;
1495 var base: usize = 2;
1496 for (0..reg_count) |i| {
1497 const div = accumulator / base;
1498 digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
1499 accumulator = div;
1500 base += 1;
1501 }
1502
1503 var registers: [6]u3 = undefined;
1504 var used_indices: [6]bool = @splat(false);
1505 for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
1506 var unused_count: u8 = 0;
1507 const unused_index = for (used_indices, 0..) |used, index| {
1508 if (!used) {
1509 if (target_unused_index == unused_count) break index;
1510 unused_count += 1;
1511 }
1512 } else unreachable;
1513 registers[i] = @intCast(unused_index + 1);
1514 used_indices[unused_index] = true;
1515 }
1516
1517 var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
1518 for (0..reg_count) |i| {
1519 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
1520 (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1521 reg_addr += @sizeOf(usize);
1522 }
1523
1524 break :ip_ptr reg_addr;
22001525 };
1526
1527 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
1528 const new_sp = ip_ptr + @sizeOf(usize);
1529
1530 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
1531 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
1532
1533 break :ip new_ip;
22011534 },
2202 .val_offset => |i| {
2203 try self.resolveCopyOnWrite(allocator);
2204 const column = try self.getOrAddColumn(allocator, i.register);
2205 column.rule = .{
2206 .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor,
2207 };
1535 .DWARF => {
1536 const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame);
1537 return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf));
22081538 },
2209 .val_offset_sf => |i| {
2210 try self.resolveCopyOnWrite(allocator);
2211 const column = try self.getOrAddColumn(allocator, i.register);
2212 column.rule = .{
2213 .val_offset = i.offset * cie.data_alignment_factor,
2214 };
1539 },
1540 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
1541 .OLD => return error.UnimplementedUnwindEncoding,
1542 .FRAMELESS => ip: {
1543 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
1544 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
1545 const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*;
1546 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
1547 break :ip new_ip;
22151548 },
2216 .val_expression => |i| {
2217 try self.resolveCopyOnWrite(allocator);
2218 const column = try self.getOrAddColumn(allocator, i.register);
2219 column.rule = .{
2220 .val_expression = i.block,
2221 };
1549 .DWARF => {
1550 const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame);
1551 return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf));
22221552 },
2223 }
1553 .FRAME => ip: {
1554 const frame = encoding.value.arm64.frame;
22241555
2225 return prev_row;
2226 }
2227};
1556 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
1557 const ip_ptr = fp + @sizeOf(usize);
22281558
2229/// Returns the ABI-defined default value this register has in the unwinding table
2230/// before running any of the CIE instructions. The DWARF spec defines these as having
2231/// the .undefined rule by default, but allows ABI authors to override that.
2232fn getRegDefaultValue(reg_number: u8, context: *UnwindContext, out: []u8) !void {
2233 switch (builtin.cpu.arch) {
2234 .aarch64, .aarch64_be => {
2235 // Callee-saved registers are initialized as if they had the .same_value rule
2236 if (reg_number >= 19 and reg_number <= 28) {
2237 const src = try regBytes(context.thread_context, reg_number, context.reg_context);
2238 if (src.len != out.len) return error.RegisterSizeMismatch;
2239 @memcpy(out, src);
2240 return;
2241 }
2242 },
2243 else => {},
2244 }
1559 var reg_addr = fp - @sizeOf(usize);
1560 inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
1561 if (@field(frame.x_reg_pairs, field.name) != 0) {
1562 (try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1563 reg_addr += @sizeOf(usize);
1564 (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1565 reg_addr += @sizeOf(usize);
1566 }
1567 }
22451568
2246 @memset(out, undefined);
2247}
1569 inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| {
1570 if (@field(frame.d_reg_pairs, field.name) != 0) {
1571 // Only the lower half of the 128-bit V registers are restored during unwinding
1572 {
1573 const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 8 + i, context.reg_context));
1574 dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1575 }
1576 reg_addr += @sizeOf(usize);
1577 {
1578 const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 9 + i, context.reg_context));
1579 dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1580 }
1581 reg_addr += @sizeOf(usize);
1582 }
1583 }
22481584
2249/// Since register rules are applied (usually) during a panic,
2250/// checked addition / subtraction is used so that we can return
2251/// an error and fall back to FP-based unwinding.
2252fn applyOffset(base: usize, offset: i64) !usize {
2253 return if (offset >= 0)
2254 try std.math.add(usize, base, @as(usize, @intCast(offset)))
2255 else
2256 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
1585 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
1586 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
1587
1588 (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
1589 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
1590
1591 break :ip new_ip;
1592 },
1593 },
1594 else => comptime unreachable, // unimplemented
1595 };
1596
1597 context.pc = stripInstructionPtrAuthCode(new_ip);
1598 if (context.pc > 0) context.pc -= 1;
1599 return new_ip;
22571600}
lib/std/dwarf/EH.zig+28-23
......@@ -1,27 +1,32 @@
1pub const PE = struct {
2 pub const absptr = 0x00;
1pub const PE = packed struct(u8) {
2 type: Type,
3 rel: Rel,
34
4 pub const size_mask = 0x7;
5 pub const sign_mask = 0x8;
6 pub const type_mask = size_mask | sign_mask;
5 /// This is a special encoding which does not correspond to named `type`/`rel` values.
6 pub const omit: PE = @bitCast(@as(u8, 0xFF));
77
8 pub const uleb128 = 0x01;
9 pub const udata2 = 0x02;
10 pub const udata4 = 0x03;
11 pub const udata8 = 0x04;
12 pub const sleb128 = 0x09;
13 pub const sdata2 = 0x0A;
14 pub const sdata4 = 0x0B;
15 pub const sdata8 = 0x0C;
8 pub const Type = enum(u4) {
9 absptr = 0x0,
10 uleb128 = 0x1,
11 udata2 = 0x2,
12 udata4 = 0x3,
13 udata8 = 0x4,
14 sleb128 = 0x9,
15 sdata2 = 0xA,
16 sdata4 = 0xB,
17 sdata8 = 0xC,
18 _,
19 };
1620
17 pub const rel_mask = 0x70;
18 pub const pcrel = 0x10;
19 pub const textrel = 0x20;
20 pub const datarel = 0x30;
21 pub const funcrel = 0x40;
22 pub const aligned = 0x50;
23
24 pub const indirect = 0x80;
25
26 pub const omit = 0xff;
21 pub const Rel = enum(u4) {
22 abs = 0x0,
23 pcrel = 0x1,
24 textrel = 0x2,
25 datarel = 0x3,
26 funcrel = 0x4,
27 aligned = 0x5,
28 /// Undocumented GCC extension
29 indirect = 0x8,
30 _,
31 };
2732};
lib/std/macho.zig+84-34
......@@ -839,62 +839,112 @@ pub const nlist = extern struct {
839839
840840pub const nlist_64 = extern struct {
841841 n_strx: u32,
842 n_type: u8,
842 n_type: packed union {
843 bits: packed struct(u8) {
844 ext: bool,
845 type: enum(u3) {
846 undf = 0,
847 abs = 1,
848 sect = 7,
849 pbud = 6,
850 indr = 5,
851 _,
852 },
853 pext: bool,
854 /// Any non-zero value indicates this is an stab, so the `stab` field should be used.
855 is_stab: u3,
856 },
857 stab: enum(u8) {
858 gsym = N_GSYM,
859 fname = N_FNAME,
860 fun = N_FUN,
861 stsym = N_STSYM,
862 lcsym = N_LCSYM,
863 bnsym = N_BNSYM,
864 ast = N_AST,
865 opt = N_OPT,
866 rsym = N_RSYM,
867 sline = N_SLINE,
868 ensym = N_ENSYM,
869 ssym = N_SSYM,
870 so = N_SO,
871 oso = N_OSO,
872 lsym = N_LSYM,
873 bincl = N_BINCL,
874 sol = N_SOL,
875 params = N_PARAMS,
876 version = N_VERSION,
877 olevel = N_OLEVEL,
878 psym = N_PSYM,
879 eincl = N_EINCL,
880 entry = N_ENTRY,
881 lbrac = N_LBRAC,
882 excl = N_EXCL,
883 rbrac = N_RBRAC,
884 bcomm = N_BCOMM,
885 ecomm = N_ECOMM,
886 ecoml = N_ECOML,
887 leng = N_LENG,
888 _,
889 },
890 },
843891 n_sect: u8,
844 n_desc: u16,
892 n_desc: packed struct(u16) {
893 _pad0: u3 = 0,
894 arm_thumb_def: bool,
895 _pad1: u1 = 0,
896 /// The meaning of this bit is contextual.
897 /// See `N_DESC_DISCARDED` and `N_NO_DEAD_STRIP`.
898 discarded_or_no_dead_strip: bool,
899 weak_ref: bool,
900 /// The meaning of this bit is contextual.
901 /// See `N_WEAK_DEF` and `N_REF_TO_WEAK`.
902 weak_def_or_ref_to_weak: bool,
903 symbol_resolver: bool,
904 alt_entry: bool,
905 _pad2: u6 = 0,
906 },
845907 n_value: u64,
846908
909 // MLUGG TODO DELETE
847910 pub fn stab(sym: nlist_64) bool {
848 return N_STAB & sym.n_type != 0;
849 }
850
851 pub fn pext(sym: nlist_64) bool {
852 return N_PEXT & sym.n_type != 0;
853 }
854
855 pub fn ext(sym: nlist_64) bool {
856 return N_EXT & sym.n_type != 0;
911 return sym.n_type.bits.is_stab != 0;
857912 }
858
913 // MLUGG TODO DELETE
859914 pub fn sect(sym: nlist_64) bool {
860 const type_ = N_TYPE & sym.n_type;
861 return type_ == N_SECT;
915 return sym.n_type.type == .sect;
862916 }
863
917 // MLUGG TODO DELETE
864918 pub fn undf(sym: nlist_64) bool {
865 const type_ = N_TYPE & sym.n_type;
866 return type_ == N_UNDF;
919 return sym.n_type.type == .undf;
867920 }
868
921 // MLUGG TODO DELETE
869922 pub fn indr(sym: nlist_64) bool {
870 const type_ = N_TYPE & sym.n_type;
871 return type_ == N_INDR;
923 return sym.n_type.type == .indr;
872924 }
873
925 // MLUGG TODO DELETE
874926 pub fn abs(sym: nlist_64) bool {
875 const type_ = N_TYPE & sym.n_type;
876 return type_ == N_ABS;
927 return sym.n_type.type == .abs;
877928 }
878
929 // MLUGG TODO DELETE
879930 pub fn weakDef(sym: nlist_64) bool {
880 return sym.n_desc & N_WEAK_DEF != 0;
931 return sym.n_desc.weak_def_or_ref_to_weak;
881932 }
882
933 // MLUGG TODO DELETE
883934 pub fn weakRef(sym: nlist_64) bool {
884 return sym.n_desc & N_WEAK_REF != 0;
935 return sym.n_desc.weak_ref;
885936 }
886
937 // MLUGG TODO DELETE
887938 pub fn discarded(sym: nlist_64) bool {
888 return sym.n_desc & N_DESC_DISCARDED != 0;
939 return sym.n_desc.discarded_or_no_dead_strip;
889940 }
890
941 // MLUGG TODO DELETE
891942 pub fn noDeadStrip(sym: nlist_64) bool {
892 return sym.n_desc & N_NO_DEAD_STRIP != 0;
943 return sym.n_desc.discarded_or_no_dead_strip;
893944 }
894945
895946 pub fn tentative(sym: nlist_64) bool {
896 if (!sym.undf()) return false;
897 return sym.n_value != 0;
947 return sym.n_type.type == .undf and sym.n_value != 0;
898948 }
899949};
900950
......@@ -2046,7 +2096,7 @@ pub const unwind_info_compressed_second_level_page_header = extern struct {
20462096 // encodings array
20472097};
20482098
2049pub const UnwindInfoCompressedEntry = packed struct {
2099pub const UnwindInfoCompressedEntry = packed struct(u32) {
20502100 funcOffset: u24,
20512101 encodingIndex: u8,
20522102};
src/link/Elf/eh_frame.zig+5-54
......@@ -455,72 +455,23 @@ pub fn writeEhFrameRelocs(elf_file: *Elf, relocs: *std.array_list.Managed(elf.El
455455}
456456
457457pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void {
458 const comp = elf_file.base.comp;
459 const gpa = comp.gpa;
460
461458 try writer.writeByte(1); // version
462 try writer.writeByte(DW_EH_PE.pcrel | DW_EH_PE.sdata4);
463 try writer.writeByte(DW_EH_PE.udata4);
464 try writer.writeByte(DW_EH_PE.datarel | DW_EH_PE.sdata4);
459 try writer.writeByte(DW_EH_PE.pcrel | DW_EH_PE.sdata4); // eh_frame_ptr_enc
460 // Building the lookup table would be expensive work on every `flush` -- omit it.
461 try writer.writeByte(DW_EH_PE.omit); // fde_count_enc
462 try writer.writeByte(DW_EH_PE.omit); // table_enc
465463
466464 const shdrs = elf_file.sections.items(.shdr);
467465 const eh_frame_shdr = shdrs[elf_file.section_indexes.eh_frame.?];
468466 const eh_frame_hdr_shdr = shdrs[elf_file.section_indexes.eh_frame_hdr.?];
469 const num_fdes = @as(u32, @intCast(@divExact(eh_frame_hdr_shdr.sh_size - eh_frame_hdr_header_size, 8)));
470 const existing_size = existing_size: {
471 const zo = elf_file.zigObjectPtr() orelse break :existing_size 0;
472 const sym = zo.symbol(zo.eh_frame_index orelse break :existing_size 0);
473 break :existing_size sym.atom(elf_file).?.size;
474 };
475467 try writer.writeInt(
476468 u32,
477469 @as(u32, @bitCast(@as(
478470 i32,
479 @truncate(@as(i64, @intCast(eh_frame_shdr.sh_addr + existing_size)) - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)) - 4),
471 @truncate(@as(i64, @intCast(eh_frame_shdr.sh_addr)) - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)) - 4),
480472 ))),
481473 .little,
482474 );
483 try writer.writeInt(u32, num_fdes, .little);
484
485 const Entry = extern struct {
486 init_addr: u32,
487 fde_addr: u32,
488
489 pub fn lessThan(ctx: void, lhs: @This(), rhs: @This()) bool {
490 _ = ctx;
491 return lhs.init_addr < rhs.init_addr;
492 }
493 };
494
495 var entries = std.array_list.Managed(Entry).init(gpa);
496 defer entries.deinit();
497 try entries.ensureTotalCapacityPrecise(num_fdes);
498
499 for (elf_file.objects.items) |index| {
500 const object = elf_file.file(index).?.object;
501 for (object.fdes.items) |fde| {
502 if (!fde.alive) continue;
503
504 const relocs = fde.relocs(object);
505 assert(relocs.len > 0); // Should this be an error? Things are completely broken anyhow if this trips...
506 const rel = relocs[0];
507 const ref = object.resolveSymbol(rel.r_sym(), elf_file);
508 const sym = elf_file.symbol(ref).?;
509 const P = @as(i64, @intCast(fde.address(elf_file)));
510 const S = @as(i64, @intCast(sym.address(.{}, elf_file)));
511 const A = rel.r_addend;
512 entries.appendAssumeCapacity(.{
513 .init_addr = @bitCast(@as(i32, @truncate(S + A - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))),
514 .fde_addr = @as(
515 u32,
516 @bitCast(@as(i32, @truncate(P - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))),
517 ),
518 });
519 }
520 }
521
522 std.mem.sort(Entry, entries.items, {}, Entry.lessThan);
523 try writer.writeSliceEndian(Entry, entries.items, .little);
524475}
525476
526477const eh_frame_hdr_header_size: usize = 12;