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 {...@@ -1083,26 +1083,27 @@ pub const Coff = struct {
1083 age: u32 = undefined,1083 age: u32 = undefined,
10841084
1085 // The lifetime of `data` must be longer than the lifetime of the returned Coff1085 // The lifetime of `data` must be longer than the lifetime of the returned Coff
1086 pub fn init(data: []const u8, is_loaded: bool) !Coff {1086 pub fn init(data: []const u8, is_loaded: bool) error{ EndOfStream, MissingPEHeader }!Coff {
1087 const pe_pointer_offset = 0x3C;1087 const pe_pointer_offset = 0x3C;
1088 const pe_magic = "PE\x00\x00";1088 const pe_magic = "PE\x00\x00";
10891089
1090 var reader: std.Io.Reader = .fixed(data);1090 if (data.len < pe_pointer_offset + 4) return error.EndOfStream;
1091 reader.seek = pe_pointer_offset;1091 const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little);
1092 const coff_header_offset = try reader.takeInt(u32, .little);1092 if (data.len < header_offset + 4) return error.EndOfStream;
1093 reader.seek = coff_header_offset;1093 const is_image = mem.eql(u8, data[header_offset..][0..4], pe_magic);
1094 const is_image = mem.eql(u8, pe_magic, try reader.takeArray(4));
10951094
1096 var coff = @This(){1095 const coff: Coff = .{
1097 .data = data,1096 .data = data,
1098 .is_image = is_image,1097 .is_image = is_image,
1099 .is_loaded = is_loaded,1098 .is_loaded = is_loaded,
1100 .coff_header_offset = coff_header_offset,1099 .coff_header_offset = o: {
1100 if (is_image) break :o header_offset + 4;
1101 break :o header_offset;
1102 },
1101 };1103 };
11021104
1103 // Do some basic validation upfront1105 // Do some basic validation upfront
1104 if (is_image) {1106 if (is_image) {
1105 coff.coff_header_offset = coff.coff_header_offset + 4;
1106 const coff_header = coff.getCoffHeader();1107 const coff_header = coff.getCoffHeader();
1107 if (coff_header.size_of_optional_header == 0) return error.MissingPEHeader;1108 if (coff_header.size_of_optional_header == 0) return error.MissingPEHeader;
1108 }1109 }
lib/std/debug.zig+62-71
...@@ -153,6 +153,7 @@ pub const SourceLocation = struct {...@@ -153,6 +153,7 @@ pub const SourceLocation = struct {
153};153};
154154
155pub const Symbol = struct {155pub const Symbol = struct {
156 // MLUGG TODO: remove the defaults and audit everywhere. also grep for '???' across std
156 name: []const u8 = "???",157 name: []const u8 = "???",
157 compile_unit_name: []const u8 = "???",158 compile_unit_name: []const u8 = "???",
158 source_location: ?SourceLocation = null,159 source_location: ?SourceLocation = null,
...@@ -232,15 +233,14 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {...@@ -232,15 +233,14 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
232}233}
233234
234/// TODO multithreaded awareness235/// TODO multithreaded awareness
235var self_debug_info: ?SelfInfo = null;236/// Marked `inline` to propagate a comptime-known error to callers.
236237pub inline fn getSelfDebugInfo() !*SelfInfo {
237pub fn getSelfDebugInfo() !*SelfInfo {238 if (builtin.strip_debug_info) return error.MissingDebugInfo;
238 if (self_debug_info) |*info| {239 if (!SelfInfo.target_supported) return error.UnsupportedOperatingSystem;
239 return info;240 const S = struct {
240 } else {241 var self_info: SelfInfo = .init;
241 self_debug_info = try SelfInfo.open(getDebugInfoAllocator());242 };
242 return &self_debug_info.?;243 return &S.self_info;
243 }
244}244}
245245
246/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.246/// 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 {...@@ -446,10 +446,7 @@ pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void {
446 defer it.deinit();446 defer it.deinit();
447447
448 // DWARF unwinding on aarch64-macos is not complete so we need to get pc address from mcontext448 // 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)449 const pc_addr = it.unwind_state.?.dwarf_context.pc;
450 context.mcontext.ss.pc
451 else
452 it.unwind_state.?.dwarf_context.pc;
453 printSourceAtAddress(debug_info, stderr, pc_addr, tty_config) catch return;450 printSourceAtAddress(debug_info, stderr, pc_addr, tty_config) catch return;
454451
455 while (it.next()) |return_address| {452 while (it.next()) |return_address| {
...@@ -460,7 +457,7 @@ pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void {...@@ -460,7 +457,7 @@ pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void {
460 // an overflow. We do not need to signal `StackIterator` as it will correctly detect this457 // an overflow. We do not need to signal `StackIterator` as it will correctly detect this
461 // condition on the subsequent iteration and return `null` thus terminating the loop.458 // condition on the subsequent iteration and return `null` thus terminating the loop.
462 // same behaviour for x86-windows-msvc459 // 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;
464 printSourceAtAddress(debug_info, stderr, address, tty_config) catch return;461 printSourceAtAddress(debug_info, stderr, address, tty_config) catch return;
465 } else printLastUnwindError(&it, debug_info, stderr, tty_config);462 } else printLastUnwindError(&it, debug_info, stderr, tty_config);
466 }463 }
...@@ -758,7 +755,7 @@ pub fn writeStackTrace(...@@ -758,7 +755,7 @@ pub fn writeStackTrace(
758 frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len;755 frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len;
759 }) {756 }) {
760 const return_address = stack_trace.instruction_addresses[frame_index];757 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);
762 }759 }
763760
764 if (stack_trace.index > stack_trace.instruction_addresses.len) {761 if (stack_trace.index > stack_trace.instruction_addresses.len) {
...@@ -808,16 +805,11 @@ pub const StackIterator = struct {...@@ -808,16 +805,11 @@ pub const StackIterator = struct {
808 }805 }
809806
810 pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t, fp: usize) !StackIterator {807 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
816 if (SelfInfo.supports_unwinding) {808 if (SelfInfo.supports_unwinding) {
817 var iterator = init(first_address, fp);809 var iterator = init(first_address, fp);
818 iterator.unwind_state = .{810 iterator.unwind_state = .{
819 .debug_info = debug_info,811 .debug_info = debug_info,
820 .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context),812 .dwarf_context = try SelfInfo.UnwindContext.init(getDebugInfoAllocator(), context),
821 };813 };
822 return iterator;814 return iterator;
823 }815 }
...@@ -890,7 +882,7 @@ pub const StackIterator = struct {...@@ -890,7 +882,7 @@ pub const StackIterator = struct {
890 if (!unwind_state.failed) {882 if (!unwind_state.failed) {
891 if (unwind_state.dwarf_context.pc == 0) return null;883 if (unwind_state.dwarf_context.pc == 0) return null;
892 defer it.fp = unwind_state.dwarf_context.getFp() catch 0;884 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| {
894 return return_address;886 return return_address;
895 } else |err| {887 } else |err| {
896 unwind_state.last_error = err;888 unwind_state.last_error = err;
...@@ -1039,19 +1031,6 @@ pub fn writeStackTraceWindows(...@@ -1039,19 +1031,6 @@ pub fn writeStackTraceWindows(
1039 }1031 }
1040}1032}
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
1055fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: tty.Config) void {1034fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: tty.Config) void {
1056 if (!have_ucontext) return;1035 if (!have_ucontext) return;
1057 if (it.getLastError()) |unwind_error| {1036 if (it.getLastError()) |unwind_error| {
...@@ -1059,32 +1038,48 @@ fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writ...@@ -1059,32 +1038,48 @@ fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writ
1059 }1038 }
1060}1039}
10611040
1062fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, err: UnwindError, tty_config: tty.Config) !void {1041fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, unwind_err: UnwindError, tty_config: tty.Config) !void {
1063 const module_name = debug_info.getModuleNameForAddress(address) orelse "???";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 };
1064 try tty_config.setColor(writer, .dim);1046 try tty_config.setColor(writer, .dim);
1065 if (err == error.MissingDebugInfo) {1047 if (unwind_err == error.MissingDebugInfo) {
1066 try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });1048 try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });
1067 } else {1049 } 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 });
1069 }1051 }
1070 try tty_config.setColor(writer, .reset);1052 try tty_config.setColor(writer, .reset);
1071}1053}
10721054
1073pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {1055pub 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) {1056 const gpa = getDebugInfoAllocator();
1075 error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),1057 if (debug_info.getSymbolAtAddress(gpa, address)) |symbol_info| {
1076 else => return err,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,
1077 };1075 };
1078 defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name);
1079
1080 return printLineInfo(1076 return printLineInfo(
1081 writer,1077 writer,
1082 symbol_info.source_location,1078 null,
1083 address,1079 address,
1084 symbol_info.name,1080 "???",
1085 symbol_info.compile_unit_name,1081 compile_unit_name,
1086 tty_config,1082 tty_config,
1087 printLineFromFileAnyOs,
1088 );1083 );
1089}1084}
10901085
...@@ -1095,7 +1090,6 @@ fn printLineInfo(...@@ -1095,7 +1090,6 @@ fn printLineInfo(
1095 symbol_name: []const u8,1090 symbol_name: []const u8,
1096 compile_unit_name: []const u8,1091 compile_unit_name: []const u8,
1097 tty_config: tty.Config,1092 tty_config: tty.Config,
1098 comptime printLineFromFile: anytype,
1099) !void {1093) !void {
1100 nosuspend {1094 nosuspend {
1101 try tty_config.setColor(writer, .bold);1095 try tty_config.setColor(writer, .bold);
...@@ -1136,7 +1130,7 @@ fn printLineInfo(...@@ -1136,7 +1130,7 @@ fn printLineInfo(
1136 }1130 }
1137}1131}
11381132
1139fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !void {1133fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
1140 // Need this to always block even in async I/O mode, because this could potentially1134 // Need this to always block even in async I/O mode, because this could potentially
1141 // be called from e.g. the event loop code crashing.1135 // be called from e.g. the event loop code crashing.
1142 var f = try fs.cwd().openFile(source_location.file_name, .{});1136 var f = try fs.cwd().openFile(source_location.file_name, .{});
...@@ -1190,7 +1184,7 @@ fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !voi...@@ -1190,7 +1184,7 @@ fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !voi
1190 }1184 }
1191}1185}
11921186
1193test printLineFromFileAnyOs {1187test printLineFromFile {
1194 var aw: Writer.Allocating = .init(std.testing.allocator);1188 var aw: Writer.Allocating = .init(std.testing.allocator);
1195 defer aw.deinit();1189 defer aw.deinit();
1196 const output_stream = &aw.writer;1190 const output_stream = &aw.writer;
...@@ -1212,9 +1206,9 @@ test printLineFromFileAnyOs {...@@ -1212,9 +1206,9 @@ test printLineFromFileAnyOs {
1212 defer allocator.free(path);1206 defer allocator.free(path);
1213 try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });1207 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 });
1218 try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());1212 try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());
1219 aw.clearRetainingCapacity();1213 aw.clearRetainingCapacity();
1220 }1214 }
...@@ -1230,11 +1224,11 @@ test printLineFromFileAnyOs {...@@ -1230,11 +1224,11 @@ test printLineFromFileAnyOs {
1230 ,1224 ,
1231 });1225 });
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 });
1234 try expectEqualStrings("1\n", aw.written());1228 try expectEqualStrings("1\n", aw.written());
1235 aw.clearRetainingCapacity();1229 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 });
1238 try expectEqualStrings("3\n", aw.written());1232 try expectEqualStrings("3\n", aw.written());
1239 aw.clearRetainingCapacity();1233 aw.clearRetainingCapacity();
1240 }1234 }
...@@ -1253,7 +1247,7 @@ test printLineFromFileAnyOs {...@@ -1253,7 +1247,7 @@ test printLineFromFileAnyOs {
1253 try writer.splatByteAll('a', overlap);1247 try writer.splatByteAll('a', overlap);
1254 try writer.flush();1248 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 });
1257 try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());1251 try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());
1258 aw.clearRetainingCapacity();1252 aw.clearRetainingCapacity();
1259 }1253 }
...@@ -1267,7 +1261,7 @@ test printLineFromFileAnyOs {...@@ -1267,7 +1261,7 @@ test printLineFromFileAnyOs {
1267 const writer = &file_writer.interface;1261 const writer = &file_writer.interface;
1268 try writer.splatByteAll('a', std.heap.page_size_max);1262 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 });
1271 try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());1265 try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());
1272 aw.clearRetainingCapacity();1266 aw.clearRetainingCapacity();
1273 }1267 }
...@@ -1281,19 +1275,19 @@ test printLineFromFileAnyOs {...@@ -1281,19 +1275,19 @@ test printLineFromFileAnyOs {
1281 const writer = &file_writer.interface;1275 const writer = &file_writer.interface;
1282 try writer.splatByteAll('a', 3 * std.heap.page_size_max);1276 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 });
1287 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());1281 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());
1288 aw.clearRetainingCapacity();1282 aw.clearRetainingCapacity();
12891283
1290 try writer.writeAll("a\na");1284 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 });
1293 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());1287 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());
1294 aw.clearRetainingCapacity();1288 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 });
1297 try expectEqualStrings("a\n", aw.written());1291 try expectEqualStrings("a\n", aw.written());
1298 aw.clearRetainingCapacity();1292 aw.clearRetainingCapacity();
1299 }1293 }
...@@ -1309,26 +1303,23 @@ test printLineFromFileAnyOs {...@@ -1309,26 +1303,23 @@ test printLineFromFileAnyOs {
1309 try writer.splatByteAll('\n', real_file_start);1303 try writer.splatByteAll('\n', real_file_start);
1310 try writer.writeAll("abc\ndef");1304 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 });
1313 try expectEqualStrings("abc\n", aw.written());1307 try expectEqualStrings("abc\n", aw.written());
1314 aw.clearRetainingCapacity();1308 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 });
1317 try expectEqualStrings("def\n", aw.written());1311 try expectEqualStrings("def\n", aw.written());
1318 aw.clearRetainingCapacity();1312 aw.clearRetainingCapacity();
1319 }1313 }
1320}1314}
13211315
1322/// TODO multithreaded awareness1316/// TODO multithreaded awareness
1323var debug_info_allocator: ?mem.Allocator = null;1317var debug_info_arena: ?std.heap.ArenaAllocator = null;
1324var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;
1325fn getDebugInfoAllocator() mem.Allocator {1318fn getDebugInfoAllocator() mem.Allocator {
1326 if (debug_info_allocator) |a| return a;1319 if (debug_info_arena == null) {
13271320 debug_info_arena = .init(std.heap.page_allocator);
1328 debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);1321 }
1329 const allocator = debug_info_arena_allocator.allocator();1322 return debug_info_arena.?.allocator();
1330 debug_info_allocator = allocator;
1331 return allocator;
1332}1323}
13331324
1334/// Whether or not the current target can print useful debug information when a segfault occurs.1325/// 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 {...@@ -78,17 +78,6 @@ pub const Section = struct {
78 debug_addr,78 debug_addr,
79 debug_names,79 debug_names,
80 };80 };
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 }
92};81};
9382
94pub const Abbrev = struct {83pub const Abbrev = struct {
...@@ -342,10 +331,6 @@ pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 {...@@ -342,10 +331,6 @@ pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 {
342 return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;331 return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;
343}332}
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
349pub fn deinit(di: *Dwarf, gpa: Allocator) void {334pub fn deinit(di: *Dwarf, gpa: Allocator) void {
350 for (di.sections) |opt_section| {335 for (di.sections) |opt_section| {
351 if (opt_section) |s| if (s.owned) gpa.free(s.data);336 if (opt_section) |s| if (s.owned) gpa.free(s.data);
...@@ -364,8 +349,6 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void {...@@ -364,8 +349,6 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void {
364 }349 }
365 di.compile_unit_list.deinit(gpa);350 di.compile_unit_list.deinit(gpa);
366 di.func_list.deinit(gpa);351 di.func_list.deinit(gpa);
367 di.cie_map.deinit(gpa);
368 di.fde_list.deinit(gpa);
369 di.ranges.deinit(gpa);352 di.ranges.deinit(gpa);
370 di.* = undefined;353 di.* = undefined;
371}354}
...@@ -983,8 +966,8 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit:...@@ -983,8 +966,8 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit:
983 },966 },
984 0,967 0,
985 };968 };
986 _ = addr_size;969 if (seg_size != 0) return bad(); // unsupported
987 _ = seg_size;970 _ = addr_size; // TODO: ignoring this is incorrect, we should use it to decide address lengths
988971
989 const prologue_length = try readAddress(&fr, unit_header.format, endian);972 const prologue_length = try readAddress(&fr, unit_header.format, endian);
990 const prog_start_offset = fr.seek + prologue_length;973 const prog_start_offset = fr.seek + prologue_length;
...@@ -1472,44 +1455,27 @@ pub const ElfModule = struct {...@@ -1472,44 +1455,27 @@ pub const ElfModule = struct {
1472 mapped_memory: ?[]align(std.heap.page_size_min) const u8,1455 mapped_memory: ?[]align(std.heap.page_size_min) const u8,
1473 external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,1456 external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,
14741457
1475 pub const Lookup = struct {1458 pub const init: ElfModule = .{
1476 base_address: usize,1459 .unwind = .{
1477 name: []const u8,1460 .debug_frame = null,
1478 build_id: ?[]const u8,1461 .eh_frame = null,
1479 gnu_eh_frame: ?[]const u8,1462 },
1463 .dwarf = .{},
1464 .mapped_memory = null,
1465 .external_mapped_memory = null,
1480 };1466 };
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
1503 pub fn deinit(self: *@This(), allocator: Allocator) void {1468 pub fn deinit(self: *@This(), allocator: Allocator) void {
1504 self.dwarf.deinit(allocator);1469 self.dwarf.deinit(allocator);
1505 std.posix.munmap(self.mapped_memory);1470 std.posix.munmap(self.mapped_memory);
1506 if (self.external_mapped_memory) |m| std.posix.munmap(m);1471 if (self.external_mapped_memory) |m| std.posix.munmap(m);
1507 }1472 }
15081473
1509 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, endian: Endian, base_address: usize, address: usize) !std.debug.Symbol {1474 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, endian: Endian, load_offset: usize, address: usize) !std.debug.Symbol {
1510 // Translate the VA into an address into this object1475 // Translate the runtime address into a virtual address into the module
1511 const relocated_address = address - base_address;1476 // MLUGG TODO: this clearly tells us that the logic should live near SelfInfo...
1512 return self.dwarf.getSymbol(allocator, endian, relocated_address);1477 const vaddr = address - load_offset;
1478 return self.dwarf.getSymbol(allocator, endian, vaddr);
1513 }1479 }
15141480
1515 pub fn getDwarfUnwindForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf.Unwind {1481 pub fn getDwarfUnwindForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf.Unwind {
...@@ -1548,7 +1514,7 @@ pub const ElfModule = struct {...@@ -1548,7 +1514,7 @@ pub const ElfModule = struct {
1548 mapped_mem: []align(std.heap.page_size_min) const u8,1514 mapped_mem: []align(std.heap.page_size_min) const u8,
1549 build_id: ?[]const u8,1515 build_id: ?[]const u8,
1550 expected_crc: ?u32,1516 expected_crc: ?u32,
1551 parent_sections: *Dwarf.SectionArray,1517 parent_sections: ?*Dwarf.SectionArray,
1552 parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,1518 parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
1553 elf_filename: ?[]const u8,1519 elf_filename: ?[]const u8,
1554 ) LoadError!void {1520 ) LoadError!void {
...@@ -1577,10 +1543,12 @@ pub const ElfModule = struct {...@@ -1577,10 +1543,12 @@ pub const ElfModule = struct {
1577 var sections: Dwarf.SectionArray = @splat(null);1543 var sections: Dwarf.SectionArray = @splat(null);
15781544
1579 // Combine section list. This takes ownership over any owned sections from the parent scope.1545 // Combine section list. This takes ownership over any owned sections from the parent scope.
1580 for (parent_sections, &sections) |*parent, *section_elem| {1546 if (parent_sections) |ps| {
1581 if (parent.*) |*p| {1547 for (ps, &sections) |*parent, *section_elem| {
1582 section_elem.* = p.*;1548 if (parent.*) |*p| {
1583 p.owned = false;1549 section_elem.* = p.*;
1550 p.owned = false;
1551 }
1584 }1552 }
1585 }1553 }
1586 errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data);1554 errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data);
...@@ -1647,7 +1615,6 @@ pub const ElfModule = struct {...@@ -1647,7 +1615,6 @@ pub const ElfModule = struct {
1647 // Attempt to load debug info from an external file1615 // Attempt to load debug info from an external file
1648 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html1616 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
1649 if (missing_debug_info) {1617 if (missing_debug_info) {
1650
1651 // Only allow one level of debug info nesting1618 // Only allow one level of debug info nesting
1652 if (parent_mapped_mem) |_| {1619 if (parent_mapped_mem) |_| {
1653 return error.MissingDebugInfo;1620 return error.MissingDebugInfo;
...@@ -1775,6 +1742,7 @@ pub const ElfModule = struct {...@@ -1775,6 +1742,7 @@ pub const ElfModule = struct {
17751742
1776 em.mapped_memory = parent_mapped_mem orelse mapped_mem;1743 em.mapped_memory = parent_mapped_mem orelse mapped_mem;
1777 em.external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null;1744 em.external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null;
1745 em.dwarf.sections = sections;
1778 try em.dwarf.open(gpa, endian);1746 try em.dwarf.open(gpa, endian);
1779 }1747 }
17801748
...@@ -1844,7 +1812,8 @@ pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]cons...@@ -1844,7 +1812,8 @@ pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]cons
1844 return ptr[start..end];1812 return ptr[start..end];
1845}1813}
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`!
1848 return switch (format) {1817 return switch (format) {
1849 .@"32" => try r.takeInt(u32, endian),1818 .@"32" => try r.takeInt(u32, endian),
1850 .@"64" => try r.takeInt(u64, endian),1819 .@"64" => try r.takeInt(u64, endian),
...@@ -1852,6 +1821,8 @@ pub fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {...@@ -1852,6 +1821,8 @@ pub fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
1852}1821}
18531822
1854fn nativeFormat() std.dwarf.Format {1823fn 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.
1855 return switch (@sizeOf(usize)) {1826 return switch (@sizeOf(usize)) {
1856 4 => .@"32",1827 4 => .@"32",
1857 8 => .@"64",1828 8 => .@"64",
lib/std/debug/Dwarf/Unwind.zig+496-506
...@@ -1,632 +1,622 @@...@@ -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 we3/// The contents of the `.debug_frame` section as specified by DWARF. This might be a more reliable
4/// find that `.eh_frame_hdr` is incomplete.4/// stack unwind mechanism in some cases, or it may be present when `.eh_frame` is not, but fetching
5eh_frame_hdr: ?ExceptionFrameHeader = null,5/// the data requires loading the binary, so it is not a viable approach for fast stack trace
6/// These lookup tables are only used if `eh_frame_hdr` is null6/// capturing within a process.
7cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .empty,7debug_frame: ?struct {
8/// Sorted by start_pc
9fde_list: std.ArrayList(FrameDescriptionEntry) = .empty,
10
11pub const Section = struct {
12 data: []const u8,8 data: []const u8,
139 /// Offsets into `data` of FDEs, sorted by ascending `pc_begin`.
14 pub const Id = enum {10 sorted_fdes: []SortedFdeEntry,
15 debug_frame,11},
16 eh_frame,12
17 eh_frame_hdr,13/// Data associated with the `.eh_frame` and `.eh_frame_hdr` sections as defined by LSB Core. The
18 };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,
19};33};
2034
21const num_sections = std.enums.directEnumArrayLen(Section.Id, 0);35const Section = enum { debug_frame, eh_frame };
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}
2736
28/// This represents the decoded .eh_frame_hdr header37/// This represents the decoded .eh_frame_hdr header
29pub const ExceptionFrameHeader = struct {38pub const EhFrameHeader = struct {
30 eh_frame_ptr: usize,39 vaddr: u64,
31 table_enc: u8,40 eh_frame_vaddr: u64,
32 fde_count: usize,41 search_table: ?struct {
33 entries: []const u8,42 /// The byte offset of the search table into the `.eh_frame_hdr` section.
3443 offset: u8,
35 pub fn entrySize(table_enc: u8) !u8 {44 encoding: EH.PE,
36 return switch (table_enc & EH.PE.type_mask) {45 fde_count: usize,
37 EH.PE.udata2,46 entries: []const u8,
38 EH.PE.sdata2,47 },
39 => 4,48
40 EH.PE.udata4,49 pub fn entrySize(table_enc: EH.PE, addr_size_bytes: u8) !u8 {
41 EH.PE.sdata4,50 return switch (table_enc.type) {
42 => 8,51 .absptr => 2 * addr_size_bytes,
43 EH.PE.udata8,52 .udata2, .sdata2 => 4,
44 EH.PE.sdata8,53 .udata4, .sdata4 => 8,
45 => 16,54 .udata8, .sdata8 => 16,
46 // This is a binary search table, so all entries must be the same length55 .uleb128, .sleb128 => return bad(), // this is a binary search table; all entries must be the same size
47 else => return bad(),56 _ => return bad(),
48 };57 };
49 }58 }
5059
51 pub fn findEntry(60 pub fn parse(
52 self: ExceptionFrameHeader,61 eh_frame_hdr_vaddr: u64,
53 eh_frame_len: usize,62 eh_frame_hdr_bytes: []const u8,
54 eh_frame_hdr_ptr: usize,63 addr_size_bytes: u8,
55 pc: usize,
56 cie: *CommonInformationEntry,
57 fde: *FrameDescriptionEntry,
58 endian: Endian,64 endian: Endian,
59 ) !void {65 ) !EhFrameHeader {
60 const entry_size = try entrySize(self.table_enc);66 var r: Reader = .fixed(eh_frame_hdr_bytes);
6167
62 var left: usize = 0;68 const version = try r.takeByte();
63 var len: usize = self.fde_count;69 if (version != 1) return bad();
64 var fbr: Reader = .fixed(self.entries);
6570
66 while (len > 1) {71 const eh_frame_ptr_enc: EH.PE = @bitCast(try r.takeByte());
67 const mid = left + len / 2;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;75 const eh_frame_ptr = try readEhPointer(&r, eh_frame_ptr_enc, addr_size_bytes, .{
70 const pc_begin = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{76 .pc_rel_base = eh_frame_hdr_vaddr + r.seek,
71 .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),77 }, endian);
72 .follow_indirect = true,
73 .data_rel_base = eh_frame_hdr_ptr,
74 }, endian) orelse return bad();
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);
76 if (pc < pc_begin) {121 if (pc < pc_begin) {
77 len /= 2;122 len /= 2;
78 } else {123 } else {
79 left = mid;124 left = mid;
80 if (pc == pc_begin) break;
81 len -= len / 2;125 len -= len / 2;
82 }126 }
83 }127 }
84128 if (len == 0) return null;
85 if (len == 0) return missing();129 var entry_reader: Reader = .fixed(table.entries[left * entry_size ..][0..entry_size]);
86 fbr.seek = left * entry_size;130 // Skip past `pc_begin`; we're now interested in the fde offset
87131 _ = try readEhPointerAbs(&entry_reader, table.encoding.type, addr_size_bytes, endian);
88 // Read past the pc_begin field of the entry132 const fde_ptr = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
89 _ = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{133 .pc_rel_base = table_vaddr + left * entry_size,
90 .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),134 .data_rel_base = eh_frame_hdr.vaddr,
91 .follow_indirect = true,135 }, endian);
92 .data_rel_base = eh_frame_hdr_ptr,136 return std.math.sub(u64, fde_ptr, eh_frame_hdr.eh_frame_vaddr) catch bad(); // offset into .eh_frame
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();
139 }137 }
140};138};
141139
142pub const EntryHeader = struct {140pub const EntryHeader = union(enum) {
143 /// Offset of the length field in the backing buffer141 cie: struct {
144 length_offset: usize,142 format: Format,
145 format: Format,143 /// Remaining bytes in the CIE. These are parseable by `CommonInformationEntry.parse`.
146 type: union(enum) {144 bytes_len: u64,
147 cie,145 },
148 /// Value is the offset of the corresponding CIE146 fde: struct {
149 fde: u64,147 format: Format,
150 terminator,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,
151 },152 },
152 /// The entry's contents, not including the ID field153 /// The `.eh_frame` format includes terminators which indicate that the last CIE/FDE has been
153 entry_bytes: []const u8,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 itself158 pub fn read(r: *Reader, header_section_offset: u64, section: Section, endian: Endian) !EntryHeader {
156 pub fn entryLength(self: EntryHeader) usize {159 const unit_header = try Dwarf.readUnitHeader(r, endian);
157 return self.entry_bytes.len + @as(u8, if (self.format == .@"64") 8 else 4);160 if (unit_header.unit_length == 0) return .terminator;
158 }
159161
160 /// Reads a header for either an FDE or a CIE, then advances the fbr to the162 // TODO MLUGG: seriously, just... check the formats of everything in BOTH LSB Core and DWARF. this is a fucking *mess*. maybe add spec references.
161 /// position after the trailing structure.163
162 ///164 // Next is a value which will disambiguate CIEs and FDEs. Annoyingly, LSB Core makes this
163 /// `fbr` must be backed by either the .eh_frame or .debug_frame sections.165 // value always 4-byte, whereas DWARF makes it depend on the `dwarf.Format`.
164 ///166 const cie_ptr_or_id_size: u8 = switch (section) {
165 /// TODO that's a bad API, don't do that. this function should neither require167 .eh_frame => 4,
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,
187 .debug_frame => switch (unit_header.format) {168 .debug_frame => switch (unit_header.format) {
188 .@"32" => CommonInformationEntry.dwarf32_id,169 .@"32" => 4,
189 .@"64" => CommonInformationEntry.dwarf64_id,170 .@"64" => 8,
190 },171 },
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),
191 else => unreachable,176 else => unreachable,
192 };177 };
178 const remaining_bytes = unit_header.unit_length - cie_ptr_or_id_size;
193179
194 return .{180 // If this entry is a CIE, then `cie_ptr_or_id` will have this value, which is different
195 .length_offset = length_offset,181 // between the DWARF `.debug_frame` section and the LSB Core `.eh_frame` section.
196 .format = unit_header.format,182 const cie_id: u64 = switch (section) {
197 .type = if (id == cie_id) .cie else .{ .fde = switch (dwarf_section) {183 .eh_frame => 0,
198 .eh_frame => try std.math.sub(u64, start_offset, id),184 .debug_frame => switch (unit_header.format) {
199 .debug_frame => id,185 .@"32" => maxInt(u32),
200 else => unreachable,186 .@"64" => maxInt(u64),
201 } },187 },
202 .entry_bytes = entry_bytes,
203 };188 };
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 } };
204 }207 }
205};208};
206209
207pub const CommonInformationEntry = struct {210pub 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,
220 version: u8,211 version: u8,
221 address_size: u8,
222 format: Format,
223212
224 // Only present in version 4213 /// In version 4, CIEs can specify the address size used in the CIE and associated FDEs.
225 segment_selector_size: ?u8,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
227 code_alignment_factor: u32,220 code_alignment_factor: u32,
228 data_alignment_factor: i32,221 data_alignment_factor: i32,
229 return_address_register: u8,222 return_address_register: u8,
230223
231 aug_str: []const u8,224 fde_pointer_enc: EH.PE,
232 aug_data: []const u8,225 is_signal_frame: bool,
233 lsda_pointer_enc: u8,
234 personality_enc: ?u8,
235 personality_routine_pointer: ?u64,
236 fde_pointer_enc: u8,
237 initial_instructions: []const u8,
238226
239 pub fn isSignalFrame(self: CommonInformationEntry) bool {227 augmentation_kind: AugmentationKind,
240 for (self.aug_str) |c| if (c == 'S') return true;
241 return false;
242 }
243228
244 pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool {229 initial_instructions: []const u8,
245 for (self.aug_str) |c| if (c == 'B') return true;
246 return false;
247 }
248230
249 pub fn mteTaggedFrame(self: CommonInformationEntry) bool {231 pub const AugmentationKind = enum { none, gcc_eh, lsb_z };
250 for (self.aug_str) |c| if (c == 'G') return true;
251 return false;
252 }
253232
254 /// This function expects to read the CIE starting with the version field.233 /// This function expects to read the CIE starting with the version field.
255 /// The returned struct references memory backed by cie_bytes.234 /// 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`.
259 ///235 ///
260 /// `length_offset` specifies the offset of this CIE's length field in the236 /// `length_offset` specifies the offset of this CIE's length field in the
261 /// .eh_frame / .debug_frame section.237 /// .eh_frame / .debug_frame section.
262 pub fn parse(238 pub fn parse(
263 cie_bytes: []const u8,239 cie_bytes: []const u8,
264 pc_rel_offset: i64,240 section: Section,
265 is_runtime: bool,241 default_addr_size_bytes: u8,
266 format: Format,
267 dwarf_section: Section.Id,
268 length_offset: u64,
269 addr_size_bytes: u8,
270 endian: Endian,
271 ) !CommonInformationEntry {242 ) !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);246 const version = try r.takeByte();
275247 switch (section) {
276 const version = try fbr.takeByte();
277 switch (dwarf_section) {
278 .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion,248 .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion,
279 .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion,249 .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion,
280 else => return error.UnsupportedDwarfSection,
281 }250 }
282251
283 var has_eh_data = false;252 const aug_str = try r.takeSentinel(0);
284 var has_aug_data = false;253 const aug_kind: AugmentationKind = aug: {
285254 if (aug_str.len == 0) break :aug .none;
286 var aug_str_len: usize = 0;255 if (aug_str[0] == 'z') break :aug .lsb_z;
287 const aug_str_start = fbr.seek;256 if (std.mem.eql(u8, aug_str, "eh")) break :aug .gcc_eh;
288 var aug_byte = try fbr.takeByte();257 // We can't finish parsing the CIE if we don't know what its augmentation means.
289 while (aug_byte != 0) : (aug_byte = try fbr.takeByte()) {258 return bad();
290 switch (aug_byte) {259 };
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 }
305260
306 if (has_eh_data) {261 switch (aug_kind) {
307 // legacy data created by older versions of gcc - unsupported here262 .none => {}, // no extra data
308 for (0..addr_size_bytes) |_| _ = try fbr.takeByte();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
309 }265 }
310266
311 const address_size = if (version == 4) try fbr.takeByte() else addr_size_bytes;267 const addr_size_bytes = if (version == 4) try r.takeByte() else default_addr_size_bytes;
312 const segment_selector_size = if (version == 4) try fbr.takeByte() else null;268 const segment_selector_size: u8 = if (version == 4) try r.takeByte() else 0;
313269 const code_alignment_factor = try r.takeLeb128(u32);
314 const code_alignment_factor = try fbr.takeLeb128(u32);270 const data_alignment_factor = try r.takeLeb128(i32);
315 const data_alignment_factor = try fbr.takeLeb128(i32);271 const return_address_register = if (version == 1) try r.takeByte() else try r.takeLeb128(u8);
316 const return_address_register = if (version == 1) try fbr.takeByte() else try fbr.takeLeb128(u8);272
317273 // This is where LSB's augmentation might add some data.
318 var lsda_pointer_enc: u8 = EH.PE.omit;274 const fde_pointer_enc: EH.PE, const is_signal_frame: bool = aug: {
319 var personality_enc: ?u8 = null;275 const default_fde_pointer_enc: EH.PE = .{ .type = .absptr, .rel = .abs };
320 var personality_routine_pointer: ?u64 = null;276 if (aug_kind != .lsb_z) break :aug .{ default_fde_pointer_enc, false };
321 var fde_pointer_enc: u8 = EH.PE.absptr;277 const aug_data_len = try r.takeLeb128(u32);
322278 var aug_data: Reader = .fixed(try r.take(aug_data_len));
323 var aug_data: []const u8 = &[_]u8{};279 var fde_pointer_enc: EH.PE = default_fde_pointer_enc;
324 const aug_str = if (has_aug_data) blk: {280 var is_signal_frame = false;
325 const aug_data_len = try fbr.takeLeb128(usize);281 for (aug_str[1..]) |byte| switch (byte) {
326 const aug_data_start = fbr.seek;282 'L' => _ = try aug_data.takeByte(), // we ignore the LSDA pointer
327 aug_data = cie_bytes[aug_data_start..][0..aug_data_len];283 'P' => {
328284 const enc: EH.PE = @bitCast(try aug_data.takeByte());
329 const aug_str = cie_bytes[aug_str_start..][0..aug_str_len];285 const endian: Endian = .little; // irrelevant because we're discarding the value anyway
330 for (aug_str[1..]) |byte| {286 _ = try readEhPointerAbs(&r, enc.type, addr_size_bytes, endian); // we ignore the personality routine; endianness is irrelevant since we're discarding
331 switch (byte) {287 },
332 'L' => {288 'R' => fde_pointer_enc = @bitCast(try aug_data.takeByte()),
333 lsda_pointer_enc = try fbr.takeByte();289 'S' => is_signal_frame = true,
334 },290 'B', 'G' => {},
335 'P' => {291 else => return bad(),
336 personality_enc = try fbr.takeByte();292 };
337 personality_routine_pointer = try readEhPointer(&fbr, personality_enc.?, addr_size_bytes, .{293 break :aug .{ fde_pointer_enc, is_signal_frame };
338 .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[fbr.seek]), pc_rel_offset),294 };
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{};
354295
355 const initial_instructions = cie_bytes[fbr.seek..];
356 return .{296 return .{
357 .length_offset = length_offset,
358 .version = version,297 .version = version,
359 .address_size = address_size,298 .addr_size_bytes = addr_size_bytes,
360 .format = format,
361 .segment_selector_size = segment_selector_size,299 .segment_selector_size = segment_selector_size,
362 .code_alignment_factor = code_alignment_factor,300 .code_alignment_factor = code_alignment_factor,
363 .data_alignment_factor = data_alignment_factor,301 .data_alignment_factor = data_alignment_factor,
364 .return_address_register = return_address_register,302 .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,
370 .fde_pointer_enc = fde_pointer_enc,303 .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(),
372 };307 };
373 }308 }
374};309};
375310
376pub const FrameDescriptionEntry = struct {311pub const FrameDescriptionEntry = struct {
377 // Offset into eh_frame where the CIE for this FDE is stored
378 cie_length_offset: u64,
379
380 pc_begin: u64,312 pc_begin: u64,
381 pc_range: u64,313 pc_range: u64,
382 lsda_pointer: ?u64,
383 aug_data: []const u8,
384 instructions: []const u8,314 instructions: []const u8,
385315
386 /// This function expects to read the FDE starting at the PC Begin field.316 /// This function expects to read the FDE starting at the PC Begin field.
387 /// The returned struct references memory backed by `fde_bytes`.317 /// 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.
398 pub fn parse(318 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,
399 fde_bytes: []const u8,323 fde_bytes: []const u8,
400 pc_rel_offset: i64,
401 is_runtime: bool,
402 cie: CommonInformationEntry,324 cie: CommonInformationEntry,
403 addr_size_bytes: u8,
404 endian: Endian,325 endian: Endian,
405 ) !FrameDescriptionEntry {326 ) !FrameDescriptionEntry {
406 if (addr_size_bytes > 8) return error.InvalidAddrSize;327 if (cie.segment_selector_size != 0) return error.UnsupportedAddrSize;
407328
408 var fbr: Reader = .fixed(fde_bytes);329 var r: Reader = .fixed(fde_bytes);
409330
410 const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{331 const pc_begin = try readEhPointer(&r, cie.fde_pointer_enc, cie.addr_size_bytes, .{
411 .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset),332 .pc_rel_base = fde_vaddr,
412 .follow_indirect = is_runtime,333 }, endian);
413 }, endian) orelse return bad();334
414335 // I swear I'm not kidding when I say that PC Range is encoded with `cie.fde_pointer_enc`, but ignoring `rel`.
415 const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{336 const pc_range = switch (try readEhPointerAbs(&r, cie.fde_pointer_enc.type, cie.addr_size_bytes, endian)) {
416 .pc_rel_base = 0,337 .unsigned => |x| x,
417 .follow_indirect = false,338 .signed => |x| cast(u64, x) orelse return bad(),
418 }, endian) orelse return bad();339 };
419340
420 var aug_data: []const u8 = &[_]u8{};341 switch (cie.augmentation_kind) {
421 const lsda_pointer = if (cie.aug_str.len > 0) blk: {342 .none, .gcc_eh => {},
422 const aug_data_len = try fbr.takeLeb128(usize);343 .lsb_z => {
423 const aug_data_start = fbr.seek;344 // There is augmentation data, but it's irrelevant to us -- it
424 aug_data = fde_bytes[aug_data_start..][0..aug_data_len];345 // only contains the LSDA pointer, which we don't care about.
425346 const aug_data_len = try r.takeLeb128(u64);
426 const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit)347 _ = try r.discardAll(aug_data_len);
427 try readEhPointer(&fbr, cie.lsda_pointer_enc, addr_size_bytes, .{348 },
428 .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset),349 }
429 .follow_indirect = is_runtime,350
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..];
439 return .{351 return .{
440 .cie_length_offset = cie.length_offset,
441 .pc_begin = pc_begin,352 .pc_begin = pc_begin,
442 .pc_range = pc_range,353 .pc_range = pc_range,
443 .lsda_pointer = lsda_pointer,354 .instructions = r.buffered(),
444 .aug_data = aug_data,
445 .instructions = instructions,
446 };355 };
447 }356 }
448};357};
449358
450/// If `.eh_frame_hdr` is present, then only the header needs to be parsed. Otherwise, `.eh_frame`359pub fn scanDebugFrame(
451/// and `.debug_frame` are scanned and a sorted list of FDEs is built for binary searching during360 unwind: *Unwind,
452/// unwinding. Even if `.eh_frame_hdr` is used, we may find during unwinding that it's incomplete,361 gpa: Allocator,
453/// in which case we build the sorted list of FDEs at that point.362 section_vaddr: u64,
454///363 section_bytes: []const u8,
455/// See also `scanCieFdeInfo`.364 addr_size_bytes: u8,
456pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void {365 endian: Endian,
457 const endian = di.endian;366) void {
458367 assert(unwind.debug_frame == null);
459 if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {368
460 var fbr: Reader = .fixed(eh_frame_hdr);369 var fbr: Reader = .fixed(section_bytes);
461370 var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
462 const version = try fbr.takeByte();371 defer fde_list.deinit(gpa);
463 if (version != 1) break :blk;372 while (fbr.seek < fbr.buffer.len) {
464373 const entry_offset = fbr.seek;
465 const eh_frame_ptr_enc = try fbr.takeByte();374 switch (try EntryHeader.read(&fbr, fbr.seek, .debug_frame, endian)) {
466 if (eh_frame_ptr_enc == EH.PE.omit) break :blk;375 // Ignore CIEs; we only need them to parse the FDEs!
467 const fde_count_enc = try fbr.takeByte();376 .cie => |info| {
468 if (fde_count_enc == EH.PE.omit) break :blk;377 try fbr.discardAll(info.bytes_len);
469 const table_enc = try fbr.takeByte();378 continue;
470 if (table_enc == EH.PE.omit) break :blk;379 },
471380 .fde => |info| {
472 const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{381 const cie: CommonInformationEntry = cie: {
473 .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]),382 var cie_reader: Reader = .fixed(section_bytes[info.cie_offset..]);
474 .follow_indirect = true,383 const cie_info = switch (try EntryHeader.read(&cie_reader, info.cie_offset, .debug_frame, endian)) {
475 }, endian) orelse return bad()) orelse return bad();384 .cie => |cie_info| cie_info,
476385 .fde, .terminator => return bad(), // This is meant to be a CIE
477 const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{386 };
478 .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]),387 break :cie try .parse(try cie_reader.take(cie_info.bytes_len), .debug_frame, addr_size_bytes);
479 .follow_indirect = true,388 };
480 }, endian) orelse return bad()) orelse return bad();389 const fde: FrameDescriptionEntry = try .parse(
481390 section_vaddr + fbr.seek,
482 const entry_size = try ExceptionFrameHeader.entrySize(table_enc);391 try fbr.take(info.bytes_len),
483 const entries_len = fde_count * entry_size;392 cie,
484 if (entries_len > eh_frame_hdr.len - fbr.seek) return bad();393 endian,
485394 );
486 di.eh_frame_hdr = .{395 try fde_list.append(.{
487 .eh_frame_ptr = eh_frame_ptr,396 .pc_begin = fde.pc_begin,
488 .table_enc = table_enc,397 .fde_offset = entry_offset, // *not* `fde_offset`, because we need to include the entry header
489 .fde_count = fde_count,398 });
490 .entries = eh_frame_hdr[fbr.seek..][0..entries_len],399 },
491 };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 already435 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 };
494 return;442 return;
495 }443 }
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 };
498}499}
499500
500/// Scan `.eh_frame` and `.debug_frame` and build a sorted list of FDEs for binary searching during501/// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must
501/// unwinding.502/// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`.
502pub fn scanCieFdeInfo(unwind: *Unwind, allocator: Allocator, endian: Endian, base_address: usize) !void {503pub fn findFdeOffset(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: Endian) !?u64 {
503 const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame };504 // We'll break from this block only if we have a manually-constructed search table.
504 for (frame_sections) |frame_section| {505 const sorted_fdes: []const SortedFdeEntry = fdes: {
505 if (unwind.section(frame_section)) |section_data| {506 if (unwind.debug_frame) |df| break :fdes df.sorted_fdes;
506 var fbr: Reader = .fixed(section_data);507 if (unwind.eh_frame) |eh_frame| {
507 while (fbr.seek < fbr.buffer.len) {508 if (eh_frame.sorted_fdes) |fdes| break :fdes fdes;
508 const entry_header = try EntryHeader.read(&fbr, frame_section, endian);509 // Use the search table from the `.eh_frame_hdr` section rather than one of our own
509 switch (entry_header.type) {510 return eh_frame.header.findEntry(pc, addr_size_bytes, endian);
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);
545 }511 }
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 };
547}559}
548560
549const EhPointerContext = struct {561const EhPointerContext = struct {
550 // The address of the pointer field itself562 // The address of the pointer field itself
551 pc_rel_base: u64,563 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
558 // These relative addressing modes are only used in specific cases, and565 // These relative addressing modes are only used in specific cases, and
559 // might not be available / required in all parsing contexts566 // might not be available / required in all parsing contexts
560 data_rel_base: ?u64 = null,567 data_rel_base: ?u64 = null,
561 text_rel_base: ?u64 = null,568 text_rel_base: ?u64 = null,
562 function_rel_base: ?u64 = null,569 function_rel_base: ?u64 = null,
563};570};
564571/// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`.
565fn readEhPointer(fbr: *Reader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !?u64 {572fn readEhPointerAbs(r: *Reader, enc_ty: EH.PE.Type, addr_size_bytes: u8, endian: Endian) !union(enum) {
566 if (enc == EH.PE.omit) return null;573 signed: i64,
567574 unsigned: u64,
568 const value: union(enum) {575} {
569 signed: i64,576 return switch (enc_ty) {
570 unsigned: u64,577 .absptr => .{
571 } = switch (enc & EH.PE.type_mask) {
572 EH.PE.absptr => .{
573 .unsigned = switch (addr_size_bytes) {578 .unsigned = switch (addr_size_bytes) {
574 2 => try fbr.takeInt(u16, endian),579 2 => try r.takeInt(u16, endian),
575 4 => try fbr.takeInt(u32, endian),580 4 => try r.takeInt(u32, endian),
576 8 => try fbr.takeInt(u64, endian),581 8 => try r.takeInt(u64, endian),
577 else => return error.InvalidAddrSize,582 else => return error.UnsupportedAddrSize,
578 },583 },
579 },584 },
580 EH.PE.uleb128 => .{ .unsigned = try fbr.takeLeb128(u64) },585 .uleb128 => .{ .unsigned = try r.takeLeb128(u64) },
581 EH.PE.udata2 => .{ .unsigned = try fbr.takeInt(u16, endian) },586 .udata2 => .{ .unsigned = try r.takeInt(u16, endian) },
582 EH.PE.udata4 => .{ .unsigned = try fbr.takeInt(u32, endian) },587 .udata4 => .{ .unsigned = try r.takeInt(u32, endian) },
583 EH.PE.udata8 => .{ .unsigned = try fbr.takeInt(u64, endian) },588 .udata8 => .{ .unsigned = try r.takeInt(u64, endian) },
584 EH.PE.sleb128 => .{ .signed = try fbr.takeLeb128(i64) },589 .sleb128 => .{ .signed = try r.takeLeb128(i64) },
585 EH.PE.sdata2 => .{ .signed = try fbr.takeInt(i16, endian) },590 .sdata2 => .{ .signed = try r.takeInt(i16, endian) },
586 EH.PE.sdata4 => .{ .signed = try fbr.takeInt(i32, endian) },591 .sdata4 => .{ .signed = try r.takeInt(i32, endian) },
587 EH.PE.sdata8 => .{ .signed = try fbr.takeInt(i64, endian) },592 .sdata8 => .{ .signed = try r.takeInt(i64, endian) },
588 else => return bad(),593 else => return bad(),
589 };594 };
590595}
591 const base = switch (enc & EH.PE.rel_mask) {596/// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`.
592 EH.PE.pcrel => ctx.pc_rel_base,597fn readEhPointer(fbr: *Reader, enc: EH.PE, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !u64 {
593 EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified,598 const offset = try readEhPointerAbs(fbr, enc.type, addr_size_bytes, endian);
594 EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified,599 const base = switch (enc.rel) {
595 EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified,600 .abs, .aligned => 0,
596 else => null,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(),
597 };607 };
598608 return switch (offset) {
599 const ptr: u64 = if (base) |b| switch (value) {609 .signed => |s| @intCast(try std.math.add(i64, s, @as(i64, @intCast(base)))),
600 .signed => |s| @intCast(try std.math.add(i64, s, @as(i64, @intCast(b)))),
601 // absptr can actually contain signed values in some cases (aarch64 MachO)610 // absptr can actually contain signed values in some cases (aarch64 MachO)
602 .unsigned => |u| u +% b,611 .unsigned => |u| u +% base,
603 } else switch (value) {
604 .signed => |s| @as(u64, @intCast(s)),
605 .unsigned => |u| u,
606 };612 };
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 }
622}613}
623614
624fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize {615/// Like `Reader.fixed`, but when the length of the data is unknown and we just want to allow
625 if (pc_rel_offset < 0) {616/// reading indefinitely.
626 return std.math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset)));617fn maxSlice(ptr: [*]const u8) []const u8 {
627 } else {618 const len = std.math.maxInt(usize) - @intFromPtr(ptr);
628 return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset)));619 return ptr[0..len];
629 }
630}620}
631621
632const Allocator = std.mem.Allocator;622const 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 @@...@@ -1,12 +1,5 @@
1const builtin = @import("builtin");
2const std = @import("../../std.zig");1const std = @import("../../std.zig");
3const mem = std.mem;2const Reader = std.Io.Reader;
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();
103
11/// TODO merge with std.dwarf.CFA4/// TODO merge with std.dwarf.CFA
12const Opcode = enum(u8) {5const Opcode = enum(u8) {
...@@ -51,9 +44,13 @@ const Opcode = enum(u8) {...@@ -51,9 +44,13 @@ const Opcode = enum(u8) {
51 pub const hi_user = 0x3f;44 pub const hi_user = 0x3f;
52};45};
5346
54fn readBlock(reader: *std.Io.Reader) ![]const u8 {47/// The returned slice points into `reader.buffer`.
48fn readBlock(reader: *Reader) ![]const u8 {
55 const block_len = try reader.takeLeb128(usize);49 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 };
57}54}
5855
59pub const Instruction = union(Opcode) {56pub const Instruction = union(Opcode) {
...@@ -140,8 +137,9 @@ pub const Instruction = union(Opcode) {...@@ -140,8 +137,9 @@ pub const Instruction = union(Opcode) {
140 block: []const u8,137 block: []const u8,
141 },138 },
142139
140 /// `reader` must be a `Reader.fixed` so that regions of its buffer are never invalidated.
143 pub fn read(141 pub fn read(
144 reader: *std.Io.Reader,142 reader: *Reader,
145 addr_size_bytes: u8,143 addr_size_bytes: u8,
146 endian: std.builtin.Endian,144 endian: std.builtin.Endian,
147 ) !Instruction {145 ) !Instruction {
...@@ -173,16 +171,14 @@ pub const Instruction = union(Opcode) {...@@ -173,16 +171,14 @@ pub const Instruction = union(Opcode) {
173 .restore,171 .restore,
174 => unreachable,172 => unreachable,
175 .nop => .{ .nop = {} },173 .nop => .{ .nop = {} },
176 .set_loc => .{174 .set_loc => .{ .set_loc = .{
177 .set_loc = .{175 .address = switch (addr_size_bytes) {
178 .address = switch (addr_size_bytes) {176 2 => try reader.takeInt(u16, endian),
179 2 => try reader.takeInt(u16, endian),177 4 => try reader.takeInt(u32, endian),
180 4 => try reader.takeInt(u32, endian),178 8 => try reader.takeInt(u64, endian),
181 8 => try reader.takeInt(u64, endian),179 else => return error.UnsupportedAddrSize,
182 else => return error.InvalidAddrSize,
183 },
184 },180 },
185 },181 } },
186 .advance_loc1 => .{182 .advance_loc1 => .{
187 .advance_loc1 = .{ .delta = try reader.takeByte() },183 .advance_loc1 = .{ .delta = try reader.takeByte() },
188 },184 },
lib/std/debug/SelfInfo.zig+1114-1771
...@@ -13,7 +13,6 @@ const windows = std.os.windows;...@@ -13,7 +13,6 @@ const windows = std.os.windows;
13const macho = std.macho;13const macho = std.macho;
14const fs = std.fs;14const fs = std.fs;
15const coff = std.coff;15const coff = std.coff;
16const pdb = std.pdb;
17const assert = std.debug.assert;16const assert = std.debug.assert;
18const posix = std.posix;17const posix = std.posix;
19const elf = std.elf;18const elf = std.elf;
...@@ -22,86 +21,37 @@ const Pdb = std.debug.Pdb;...@@ -22,86 +21,37 @@ const Pdb = std.debug.Pdb;
22const File = std.fs.File;21const File = std.fs.File;
23const math = std.math;22const math = std.math;
24const testing = std.testing;23const testing = std.testing;
25const StackIterator = std.debug.StackIterator;
26const regBytes = Dwarf.abi.regBytes;24const regBytes = Dwarf.abi.regBytes;
27const regValueNative = Dwarf.abi.regValueNative;25const regValueNative = Dwarf.abi.regValueNative;
2826
29const SelfInfo = @This();27const SelfInfo = @This();
3028
31const root = @import("root");29/// MLUGG TODO: what if this field had a less stupid name...
3230address_map: std.AutoHashMapUnmanaged(usize, Module.DebugInfo),
33allocator: Allocator,31
34address_map: std.AutoHashMapUnmanaged(usize, Module),32module_cache: if (native_os == .windows) std.ArrayListUnmanaged(windows.MODULEENTRY32) else void,
35modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void,33
3634pub const target_supported: bool = switch (native_os) {
37pub const OpenError = error{35 .linux,
38 MissingDebugInfo,36 .freebsd,
39 UnsupportedOperatingSystem,37 .netbsd,
40} || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).@"fn".return_type.?).error_union.error_set;38 .dragonfly,
4139 .openbsd,
42pub fn open(allocator: Allocator) OpenError!SelfInfo {40 .macos,
43 if (builtin.strip_debug_info)41 .solaris,
44 return error.MissingDebugInfo;42 .illumos,
45 switch (native_os) {43 .windows,
46 .linux,44 => true,
47 .freebsd,45 else => false,
48 .netbsd,46};
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 }
10047
101 return debug_info;48pub const init: SelfInfo = .{
102}49 .address_map = .empty,
50 .module_cache = if (native_os == .windows) .empty,
51};
10352
104pub fn deinit(self: *SelfInfo) void {53pub 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...????
105 var it = self.address_map.iterator();55 var it = self.address_map.iterator();
106 while (it.next()) |entry| {56 while (it.next()) |entry| {
107 const mdi = entry.value_ptr.*;57 const mdi = entry.value_ptr.*;
...@@ -118,49 +68,91 @@ pub fn deinit(self: *SelfInfo) void {...@@ -118,49 +68,91 @@ pub fn deinit(self: *SelfInfo) void {
118 }68 }
119}69}
12070
121fn lookupModuleForAddress(self: *SelfInfo, address: usize) !Module.Lookup {71fn lookupModuleForAddress(self: *SelfInfo, gpa: Allocator, address: usize) !Module {
122 if (builtin.target.os.tag.isDarwin()) {72 if (builtin.target.os.tag.isDarwin()) {
123 return self.lookupModuleDyld(address);73 return self.lookupModuleDyld(address);
124 } else if (native_os == .windows) {74 } else if (native_os == .windows) {
125 return self.lookupModuleWin32(address);75 return self.lookupModuleWin32(gpa, address);
126 } else if (native_os == .haiku) {76 } else if (native_os == .haiku) {
127 return self.lookupModuleHaiku(address);77 @panic("TODO implement lookup module for Haiku");
128 } else if (builtin.target.cpu.arch.isWasm()) {78 } else if (builtin.target.cpu.arch.isWasm()) {
129 return self.lookupModuleWasm(address);79 @panic("TODO implement lookup module for Wasm");
130 } else {80 } else {
131 return self.lookupModuleDl(address);81 return self.lookupModuleDl(address);
132 }82 }
133}83}
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 {
136 if (builtin.target.os.tag.isDarwin()) {111 if (builtin.target.os.tag.isDarwin()) {
137 @compileError("TODO");112 // MLUGG TODO HACKHACK
113 try loadMachODebugInfo(gpa, module, di);
138 } else if (native_os == .windows) {114 } else if (native_os == .windows) {
139 @compileError("TODO");115 comptime unreachable; // not supported
140 } else if (native_os == .haiku) {116 } else if (native_os == .haiku) {
141 @compileError("TODO");117 comptime unreachable; // not supported
142 } else if (builtin.target.cpu.arch.isWasm()) {118 } else if (builtin.target.cpu.arch.isWasm()) {
143 @compileError("TODO");119 comptime unreachable; // not supported
144 } else {120 } else {
145 if (module.mapped_memory == null) {121 eh_frame: {
146 var sections: Dwarf.SectionArray = @splat(null);122 if (di.unwind.eh_frame != null) break :eh_frame; // already loaded
147 try readElfDebugInfo(module, self.allocator, if (lookup.name.len > 0) lookup.name else null, lookup.build_id, &sections);123 const eh_frame_hdr_bytes = module.gnu_eh_frame orelse break :eh_frame;
148 assert(module.mapped_memory != null);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 );
149 }139 }
150 }140 }
151}141}
152142
153pub fn unwindFrame(self: *SelfInfo, context: *UnwindContext) !usize {143pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
154 const lookup = try self.lookupModuleForAddress(context.pc);144 comptime assert(target_supported);
155 const gop = try self.address_map.getOrPut(self.allocator, lookup.base_address);145 const module = try self.lookupModuleForAddress(gpa, context.pc);
156 if (!gop.found_existing) gop.value_ptr.* = .init(&lookup);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);
157 if (native_os.isDarwin()) {149 if (native_os.isDarwin()) {
158 // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding150 // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding
159 // via DWARF before attempting to use the compact unwind info will produce incorrect results.151 // via DWARF before attempting to use the compact unwind info will produce incorrect results.
160 if (gop.value_ptr.unwind_info) |unwind_info| {152 if (gop.value_ptr.unwind_info) |unwind_info| {
161 if (unwindFrameMachO(153 if (unwindFrameMachO(
162 self.allocator,154 module.text_base,
163 lookup.base_address,155 module.load_offset,
164 context,156 context,
165 unwind_info,157 unwind_info,
166 gop.value_ptr.eh_frame,158 gop.value_ptr.eh_frame,
...@@ -169,292 +161,42 @@ pub fn unwindFrame(self: *SelfInfo, context: *UnwindContext) !usize {...@@ -169,292 +161,42 @@ pub fn unwindFrame(self: *SelfInfo, context: *UnwindContext) !usize {
169 } else |err| {161 } else |err| {
170 if (err != error.RequiresDWARFUnwind) return err;162 if (err != error.RequiresDWARFUnwind) return err;
171 }163 }
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);
173 }169 }
174 if (try gop.value_ptr.getDwarfUnwindForAddress(self.allocator, context.pc)) |unwind| {170 return error.MissingDebugInfo;
175 return unwindFrameDwarf(self.allocator, unwind, lookup.base_address, context, null);
176 } else return error.MissingDebugInfo;
177}171}
178172
179pub fn getSymbolAtAddress(self: *SelfInfo, address: usize) !std.debug.Symbol {173pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {
180 const lookup = try self.lookupModuleForAddress(address);174 comptime assert(target_supported);
181 const gop = try self.address_map.getOrPut(self.allocator, lookup.base_address);175 const module = try self.lookupModuleForAddress(gpa, address);
182 if (!gop.found_existing) gop.value_ptr.* = .init(&lookup);176 const gop = try self.address_map.getOrPut(gpa, module.key());
183 try self.loadModuleDebugInfo(&lookup, gop.value_ptr);177 if (!gop.found_existing) gop.value_ptr.* = .init;
184 return gop.value_ptr.getSymbolAtAddress(self.allocator, native_endian, lookup.base_address, address);178 try loadModuleDebugInfo(gpa, &module, gop.value_ptr);
179 return module.getSymbolAtAddress(gpa, gop.value_ptr, address);
185}180}
186181
187/// Returns the module name for a given address.182/// Returns the module name for a given address.
188/// This can be called when getModuleForAddress fails, so implementations should provide183/// This can be called when getModuleForAddress fails, so implementations should provide
189/// a path that doesn't rely on any side-effects of a prior successful module lookup.184/// 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 {185pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {
191 return if (self.lookupModuleForAddress(address)) |lookup| lookup.name else |err| switch (err) {186 comptime assert(target_supported);
192 error.MissingDebugInfo => null,187 const module = try self.lookupModuleForAddress(gpa, address);
193 };188 return module.name;
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;
437}189}
438190
439fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {191fn lookupModuleDl(self: *SelfInfo, address: usize) !Module {
440 var ctx: struct {192 _ = self; // MLUGG
441 // Input193 const DlIterContext = struct {
194 /// input
442 address: usize,195 address: usize,
443 // Output196 /// output
444 lookup: Module.Lookup,197 module: Module,
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);
455198
456 posix.dl_iterate_phdr(&ctx, error{Found}, struct {199 fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void {
457 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
458 _ = size;200 _ = size;
459 // The base address is too high201 // The base address is too high
460 if (context.address < info.addr)202 if (context.address < info.addr)
...@@ -468,10 +210,13 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {...@@ -468,10 +210,13 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
468 const seg_start = info.addr +% phdr.p_vaddr;210 const seg_start = info.addr +% phdr.p_vaddr;
469 const seg_end = seg_start + phdr.p_memsz;211 const seg_end = seg_start + phdr.p_memsz;
470 if (context.address >= seg_start and context.address < seg_end) {212 if (context.address >= seg_start and context.address < seg_end) {
471 // Android libc uses NULL instead of an empty string to mark the213 context.module = .{
472 // main program214 .load_offset = info.addr,
473 context.lookup.name = mem.sliceTo(info.name, 0) orelse "";215 // Android libc uses NULL instead of "" to mark the main program
474 context.lookup.base_address = info.addr;216 .name = mem.sliceTo(info.name, 0) orelse "",
217 .build_id = null,
218 .gnu_eh_frame = null,
219 };
475 break;220 break;
476 }221 }
477 } else return;222 } else return;
...@@ -480,17 +225,20 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {...@@ -480,17 +225,20 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
480 switch (phdr.p_type) {225 switch (phdr.p_type) {
481 elf.PT_NOTE => {226 elf.PT_NOTE => {
482 // Look for .note.gnu.build-id227 // Look for .note.gnu.build-id
483 const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];228 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
484 const name_size = mem.readInt(u32, note_bytes[0..4], native_endian);229 var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
485 if (name_size != 4) continue;230 const name_size = r.takeInt(u32, native_endian) catch continue;
486 const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian);231 const desc_size = r.takeInt(u32, native_endian) catch continue;
487 const note_type = mem.readInt(u32, note_bytes[8..12], native_endian);232 const note_type = r.takeInt(u32, native_endian) catch continue;
233 const name = r.take(name_size) catch continue;
488 if (note_type != elf.NT_GNU_BUILD_ID) continue;234 if (note_type != elf.NT_GNU_BUILD_ID) continue;
489 if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;235 if (!mem.eql(u8, name, "GNU\x00")) continue;
490 context.lookup.build_id = note_bytes[16..][0..desc_size];236 const desc = r.take(desc_size) catch continue;
237 context.module.build_id = desc;
491 },238 },
492 elf.PT_GNU_EH_FRAME => {239 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];
494 },242 },
495 else => {},243 else => {},
496 }244 }
...@@ -499,425 +247,558 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {...@@ -499,425 +247,558 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
499 // Stop the iteration247 // Stop the iteration
500 return error.Found;248 return error.Found;
501 }249 }
502 }.callback) catch |err| switch (err) {
503 error.Found => return ctx.lookup,
504 };250 };
505 if (true) return error.MissingDebugInfo;251 var ctx: DlIterContext = .{
506252 .address = address,
507 if (self.address_map.get(ctx.lookup.base_address)) |obj_di| {253 .module = undefined,
508 return obj_di;254 };
509 }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);261fn lookupModuleDyld(self: *SelfInfo, address: usize) !Module {
512 if (ctx.lookup.gnu_eh_frame) |eh_frame_hdr| {262 _ = self; // MLUGG
513 // This is a special case - pointer offsets inside .eh_frame_hdr263 const image_count = std.c._dyld_image_count();
514 // are encoded relative to its base address, so we must use the264 for (0..image_count) |image_idx| {
515 // version that is already memory mapped, and not the one that265 const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;
516 // will be mapped separately from the ELF file.266 const text_base = @intFromPtr(header);
517 sections[@intFromEnum(Dwarf.Unwind.Section.Id.eh_frame_hdr)] = .{267 if (address < text_base) continue;
518 .data = eh_frame_hdr,268 const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx));
519 .owned = false,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,
520 };296 };
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;
521 }307 }
522308 return error.MissingDebugInfo;
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;
534}309}
535310
536fn lookupModuleHaiku(self: *SelfInfo, address: usize) !*Module {311fn lookupModuleWin32(self: *SelfInfo, gpa: Allocator, address: usize) !Module {
537 _ = self;312 if (self.lookupModuleWin32Cache(address)) |m| return m;
538 _ = address;
539 @panic("TODO implement lookup module for Haiku");
540}
541313
542fn lookupModuleWasm(self: *SelfInfo, address: usize) !*Module {314 {
543 _ = self;315 // Check a new module hasn't been loaded
544 _ = address;316 self.module_cache.clearRetainingCapacity();
545 @panic("TODO implement lookup module for Wasm");
546}
547317
548pub const Module = switch (native_os) {318 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
549 .macos, .ios, .watchos, .tvos, .visionos => struct {319 if (handle == windows.INVALID_HANDLE_VALUE) {
550 base_address: usize,320 return windows.unexpectedError(windows.GetLastError());
551 vmaddr_slide: usize,321 }
552 mapped_memory: []align(std.heap.page_size_min) const u8,322 defer windows.CloseHandle(handle);
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 };
566323
567 pub fn deinit(self: *@This(), allocator: Allocator) void {324 var entry: windows.MODULEENTRY32 = undefined;
568 var it = self.ofiles.iterator();325 entry.dwSize = @sizeOf(windows.MODULEENTRY32);
569 while (it.next()) |entry| {326 if (windows.kernel32.Module32First(handle, &entry) != 0) {
570 const ofile = entry.value_ptr;327 try self.module_cache.append(gpa, entry);
571 ofile.di.deinit(allocator);328 while (windows.kernel32.Module32Next(handle, &entry) != 0) {
572 ofile.addr_table.deinit();329 try self.module_cache.append(gpa, entry);
573 }330 }
574 self.ofiles.deinit();
575 allocator.free(self.symbols);
576 posix.munmap(self.mapped_memory);
577 }331 }
332 }
578333
579 fn loadOFile(self: *@This(), allocator: Allocator, o_file_path: []const u8) !*OFileInfo {334 if (self.lookupModuleWin32Cache(address)) |m| return m;
580 const o_file = try fs.cwd().openFile(o_file_path, .{});335 return error.MissingDebugInfo;
581 const mapped_mem = try mapWholeFile(o_file);336}
582337fn lookupModuleWin32Cache(self: *SelfInfo, address: usize) ?Module {
583 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));338 for (self.module_cache.items) |*entry| {
584 if (hdr.magic != std.macho.MH_MAGIC_64)339 const base_address = @intFromPtr(entry.modBaseAddr);
585 return error.InvalidDebugInfo;340 if (address >= base_address and address < base_address + entry.modBaseSize) {
586341 return .{
587 var segcmd: ?macho.LoadCommandIterator.LoadCommand = null;342 .base_address = base_address,
588 var symtabcmd: ?macho.symtab_command = null;343 .size = entry.modBaseSize,
589 var it = macho.LoadCommandIterator{344 .name = std.mem.sliceTo(&entry.szModule, 0),
590 .ncmds = hdr.ncmds,345 .handle = entry.hModule,
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 => {},
597 };346 };
347 }
348 }
349 return null;
350}
598351
599 if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo;352fn readCoffDebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
600353 const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
601 // Parse symbols354 const mapped = mapped_ptr[0..module.size];
602 const strtab = @as(355 var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
603 [*]const u8,356 // The string table is not mapped into memory by the loader, so if a section name is in the
604 @ptrCast(&mapped_mem[symtabcmd.?.stroff]),357 // string table then we have to map the full image file from disk. This can happen when
605 )[0 .. symtabcmd.?.strsize - 1 :0];358 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
606 const symtab = @as(359 if (coff_obj.strtabRequired()) {
607 [*]const macho.nlist_64,360 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
608 @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])),361 name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
609 )[0..symtabcmd.?.nsyms];362 const process_handle = windows.GetCurrentProcess();
610363 const len = windows.kernel32.GetModuleFileNameExW(
611 // TODO handle tentative (common) symbols364 process_handle,
612 var addr_table = std.StringHashMap(u64).init(allocator);365 module.handle,
613 try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len)));366 name_buffer[4..],
614 for (symtab) |sym| {367 windows.PATH_MAX_WIDE,
615 if (sym.n_strx == 0) continue;368 );
616 if (sym.undf() or sym.tentative() or sym.abs()) continue;369 if (len == 0) return error.MissingDebugInfo;
617 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);370 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
618 // TODO is it possible to have a symbol collision?371 error.FileNotFound => return error.MissingDebugInfo,
619 addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);372 else => |e| return e,
620 }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;415 if (coff_obj.getSectionByName(".debug_info")) |_| {
623 if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{416 di.dwarf = .{};
624 .data = eh_frame,
625 .owned = false,
626 };
627417
628 for (segcmd.?.getSections()) |sect| {418 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
629 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;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;428 try di.dwarf.?.open(gpa, native_endian);
632 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {429 }
633 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i;
634 }
635 if (section_index == null) continue;
636430
637 const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size);431 if (try coff_obj.getPdbPath()) |raw_path| pdb: {
638 sections[section_index.?] = .{432 const path = blk: {
639 .data = section_bytes,433 if (fs.path.isAbsolute(raw_path)) {
640 .virtual_address = @intCast(sect.addr),434 break :blk raw_path;
641 .owned = false,435 } else {
642 };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 });
643 }439 }
440 };
441 defer if (path.ptr != raw_path.ptr) gpa.free(path);
644442
645 const missing_debug_info =443 di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
646 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or444 error.FileNotFound, error.IsDir => break :pdb,
647 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or445 else => return err,
648 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or446 };
649 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;447 try di.pdb.?.parseInfoStream();
650 if (missing_debug_info) return error.MissingDebugInfo;448 try di.pdb.?.parseDbiStream();
651
652 var di: Dwarf = .{
653 .endian = .little,
654 .sections = sections,
655 .is_macho = true,
656 };
657449
658 try Dwarf.open(&di, allocator);450 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
659 const info = OFileInfo{451 return error.InvalidDebugInfo;
660 .di = di,
661 .addr_table = addr_table,
662 };
663452
664 // Add the debug info to the cache453 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
665 const result = try self.ofiles.getOrPut(o_file_path);454 }
666 assert(!result.found_existing);455}
667 result.value_ptr.* = info;
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;
670 }468 }
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 {473 // offset of `address` from start of `symbol`
673 const result = try self.getOFileInfoForAddress(allocator, address);474 const address_symbol_offset = vaddr - symbol.addr;
674 if (result.symbol == null) return .{};
675475
676 // Take the symbol name from the N_FUN STAB entry, we're going to476 // Take the symbol name from the N_FUN STAB entry, we're going to
677 // use it if we fail to find the DWARF infos477 // use it if we fail to find the DWARF infos
678 const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0);478 const stab_symbol = mem.sliceTo(di.strings[symbol.strx..], 0);
679 if (result.o_file_info == null) return .{ .name = stab_symbol };479 const o_file_path = mem.sliceTo(di.strings[symbol.ofile..], 0);
680480
681 // Translate again the address, this time into an address inside the481 const o_file: *DebugInfo.OFile = of: {
682 // .o file482 const gop = try di.ofiles.getOrPut(gpa, o_file_path);
683 const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{483 if (!gop.found_existing) {
684 .name = "???",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;
685 };496 };
686497
687 const addr_off = result.relocated_address - result.symbol.?.addr;498 const symbol_ofile_vaddr = o_file.addr_table.get(stab_symbol) orelse return .{ .name = stab_symbol };
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 }
716499
717 pub fn getOFileInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !struct {500 const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) {
718 relocated_address: usize,501 error.MissingDebugInfo, error.InvalidDebugInfo => return .{ .name = stab_symbol },
719 symbol: ?*const MachoSymbol = null,502 else => |e| return e,
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,
728 };503 };
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
744 return .{505 return .{
745 .relocated_address = relocated_address,506 .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr) orelse stab_symbol,
746 .symbol = symbol,507 .compile_unit_name = compile_unit.die.getAttrString(
747 .o_file_info = o_file_info,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 },
748 };525 };
749 }526 }
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 {548 fn deinit(di: *DebugInfo, gpa: Allocator) void {
752 return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;549 for (di.ofiles.values()) |*ofile| {
753 }550 ofile.dwarf.deinit(gpa);
754 },551 ofile.addr_table.deinit(gpa);
755 .uefi, .windows => struct {552 }
756 base_address: usize,553 di.ofiles.deinit();
757 pdb: ?Pdb,554 gpa.free(di.symbols);
758 dwarf: ?Dwarf,555 posix.munmap(di.mapped_memory);
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);
774 }556 }
775557
776 self.* = undefined;558 fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
777 }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 {562 if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
780 var coff_section: *align(1) const coff.SectionHeader = undefined;563 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
781 const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| {564 if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo;
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 };
795565
796 const module = (try self.pdb.?.getModule(mod_index)) orelse566 const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
797 return error.InvalidDebugInfo;567 var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
798 const obj_basename = fs.path.basename(module.obj_file_name);568 var symtab_cmd: ?macho.symtab_command = null;
799569 var it: macho.LoadCommandIterator = .{
800 const symbol_name = self.pdb.?.getSymbolName(570 .ncmds = hdr.ncmds,
801 module,571 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
802 relocated_address - coff_section.virtual_address,572 };
803 ) orelse "???";573 while (it.next()) |cmd| switch (cmd.cmd()) {
804 const opt_line_info = try self.pdb.?.getLineNumberInfo(574 .SEGMENT_64 => seg_cmd = cmd,
805 module,575 .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
806 relocated_address - coff_section.virtual_address,576 else => {},
807 );577 };
578 break :cmds .{
579 seg_cmd orelse return error.MissingDebugInfo,
580 symtab_cmd orelse return error.MissingDebugInfo,
581 };
582 };
808583
809 return .{584 if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
810 .name = symbol_name,585 if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo;
811 .compile_unit_name = obj_basename,586 const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
812 .source_location = opt_line_info,587
813 };588 const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
814 }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 {610 var sections: Dwarf.SectionArray = @splat(null);
817 // Translate the VA into an address into this object611 for (seg_cmd.getSections()) |sect| {
818 const relocated_address = address - self.base_address;612 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
819613
820 if (self.pdb != null) {614 const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
821 if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol;615 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
822 }616 } else continue;
823617
824 if (self.dwarf) |*dwarf| {618 const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size);
825 const dwarf_address = relocated_address + self.coff_image_base;619 sections[section_index] = .{
826 return dwarf.getSymbol(allocator, dwarf_address);620 .data = section_bytes,
827 }621 .virtual_address = @intCast(sect.addr),
622 .owned = false,
623 };
624 }
828625
829 return .{};626 const missing_debug_info =
830 }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 {633 var dwarf: Dwarf = .{ .sections = sections };
833 _ = allocator;634 errdefer dwarf.deinit(gpa);
834 _ = address;635 try dwarf.open(gpa, native_endian);
835636
836 return switch (self.debug_data) {637 return .{
837 .dwarf => |*dwarf| dwarf,638 .dwarf = dwarf,
838 else => null,639 .addr_table = addr_table.move(),
839 };640 };
840 }641 }
642 };
841 },643 },
842 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => Dwarf.ElfModule,
843 .wasi, .emscripten => struct {644 .wasi, .emscripten => struct {
844 pub fn deinit(self: *@This(), allocator: Allocator) void {645 const DebugInfo = struct {
845 _ = self;646 const init: DebugInfo = .{};
846 _ = allocator;647 fn getSymbolAtAddress(di: *DebugInfo, gpa: Allocator, base_address: usize, address: usize) !std.debug.Symbol {
847 }648 _ = di;
848649 _ = gpa;
849 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {650 _ = base_address;
850 _ = self;651 _ = address;
851 _ = allocator;652 unreachable;
852 _ = address;653 }
853 return .{};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
854 }663 }
855664 const DebugInfo = Dwarf.ElfModule;
856 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {665 fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
857 _ = self;666 return di.getSymbolAtAddress(gpa, native_endian, mod.load_offset, address);
858 _ = allocator;
859 _ = address;
860 return null;
861 }667 }
862 },668 },
863 else => Dwarf,669 .uefi, .windows => struct {
864};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?691 dwarf: ?Dwarf,
867/// Why are both stored in the `SelfInfo` struct?692
868/// Boy, it sure would be nice if someone added documentation comments for this693 pdb: ?Pdb,
869/// struct explaining it.694 /// Populated iff `pdb != null`; otherwise `&.{}`.
870pub const WindowsModule = struct {695 coff_section_headers: []coff.SectionHeader,
871 base_address: usize,696
872 size: u32,697 const init: DebugInfo = .{
873 name: []const u8,698 .coff_image_base = undefined,
874 handle: windows.HMODULE,699 .mapped_file = null,
875700 .dwarf = null,
876 // Set when the image file needed to be mapped from disk701 .pdb = null,
877 mapped_file: ?struct {702 .coff_section_headers = &.{},
878 file: File,703 };
879 section_handle: windows.HANDLE,704
880 section_view: []const u8,705 fn deinit(di: *DebugInfo, gpa: Allocator) void {
881706 if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
882 pub fn deinit(self: @This()) void {707 if (di.pdb) |*pdb| pdb.deinit();
883 const process_handle = windows.GetCurrentProcess();708 gpa.free(di.coff_section_headers);
884 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrCast(@constCast(self.section_view.ptr))) == .SUCCESS);709 if (di.mapped_file) |mapped| mapped.deinit();
885 windows.CloseHandle(self.section_handle);710 }
886 self.file.close();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;
887 }764 }
888 } = null,765 },
889};766};
890767
891/// This takes ownership of macho_file: users of this function should not close768fn loadMachODebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
892/// it themselves, even on error.769 const mapped_mem = mapFileOrSelfExe(module.name) catch |err| switch (err) {
893/// TODO it's weird to take ownership even on error, rework this code.770 error.FileNotFound => return error.MissingDebugInfo,
894fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {771 error.FileTooBig => return error.InvalidDebugInfo,
895 const mapped_mem = try mapWholeFile(macho_file);772 else => |e| return e,
773 };
774 errdefer posix.munmap(mapped_mem);
896775
897 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));776 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
898 if (hdr.magic != macho.MH_MAGIC_64)777 if (hdr.magic != macho.MH_MAGIC_64)
899 return error.InvalidDebugInfo;778 return error.InvalidDebugInfo;
900779
901 var it = macho.LoadCommandIterator{780 const symtab: macho.symtab_command = symtab: {
902 .ncmds = hdr.ncmds,781 var it: macho.LoadCommandIterator = .{
903 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],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;
904 };790 };
905 const symtab = while (it.next()) |cmd| switch (cmd.cmd()) {791
906 .SYMTAB => break cmd.cast(macho.symtab_command).?,792 const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab.symoff..]);
907 else => {},793 const syms = syms_ptr[0..symtab.nsyms];
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];
914 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];794 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
918 var ofile: u32 = undefined;800 var ofile: u32 = undefined;
919 var last_sym: MachoSymbol = undefined;801 var last_sym: MachoSymbol = undefined;
920 var symbol_index: usize = 0;
921 var state: enum {802 var state: enum {
922 init,803 init,
923 oso_open,804 oso_open,
...@@ -929,64 +810,53 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {...@@ -929,64 +810,53 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
929 } = .init;810 } = .init;
930811
931 for (syms) |*sym| {812 for (syms) |*sym| {
932 if (!sym.stab()) continue;813 if (sym.n_type.bits.is_stab == 0) continue;
933814
934 // TODO handle globals N_GSYM, and statics N_STSYM815 // TODO handle globals N_GSYM, and statics N_STSYM
935 switch (sym.n_type) {816 switch (sym.n_type.stab) {
936 macho.N_OSO => {817 .oso => switch (state) {
937 switch (state) {818 .init, .oso_close => {
938 .init, .oso_close => {819 state = .oso_open;
939 state = .oso_open;820 ofile = sym.n_strx;
940 ofile = sym.n_strx;821 },
941 },822 else => return error.InvalidDebugInfo,
942 else => return error.InvalidDebugInfo,
943 }
944 },823 },
945 macho.N_BNSYM => {824 .bnsym => switch (state) {
946 switch (state) {825 .oso_open, .ensym => {
947 .oso_open, .ensym => {826 state = .bnsym;
948 state = .bnsym;827 last_sym = .{
949 last_sym = .{828 .strx = 0,
950 .strx = 0,829 .addr = sym.n_value,
951 .addr = sym.n_value,830 .size = 0,
952 .size = 0,831 .ofile = ofile,
953 .ofile = ofile,832 };
954 };833 },
955 },834 else => return error.InvalidDebugInfo,
956 else => return error.InvalidDebugInfo,
957 }
958 },835 },
959 macho.N_FUN => {836 .fun => switch (state) {
960 switch (state) {837 .bnsym => {
961 .bnsym => {838 state = .fun_strx;
962 state = .fun_strx;839 last_sym.strx = sym.n_strx;
963 last_sym.strx = sym.n_strx;840 },
964 },841 .fun_strx => {
965 .fun_strx => {842 state = .fun_size;
966 state = .fun_size;843 last_sym.size = @intCast(sym.n_value);
967 last_sym.size = @as(u32, @intCast(sym.n_value));844 },
968 },845 else => return error.InvalidDebugInfo,
969 else => return error.InvalidDebugInfo,
970 }
971 },846 },
972 macho.N_ENSYM => {847 .ensym => switch (state) {
973 switch (state) {848 .fun_size => {
974 .fun_size => {849 state = .ensym;
975 state = .ensym;850 symbols.appendAssumeCapacity(last_sym);
976 symbols_buf[symbol_index] = last_sym;851 },
977 symbol_index += 1;852 else => return error.InvalidDebugInfo,
978 },
979 else => return error.InvalidDebugInfo,
980 }
981 },853 },
982 macho.N_SO => {854 .so => switch (state) {
983 switch (state) {855 .init, .oso_close => {},
984 .init, .oso_close => {},856 .oso_open, .ensym => {
985 .oso_open, .ensym => {857 state = .oso_close;
986 state = .oso_close;858 },
987 },859 else => return error.InvalidDebugInfo,
988 else => return error.InvalidDebugInfo,
989 }
990 },860 },
991 else => {},861 else => {},
992 }862 }
...@@ -998,560 +868,187 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {...@@ -998,560 +868,187 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
998 else => return error.InvalidDebugInfo,868 else => return error.InvalidDebugInfo,
999 }869 }
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
1003 // Even though lld emits symbols in ascending order, this debug code874 // Even though lld emits symbols in ascending order, this debug code
1004 // should work for programs linked in any valid way.875 // should work for programs linked in any valid way.
1005 // This sort is so that we can binary search later.876 // 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 .{879 di.* = .{
1009 .base_address = undefined,880 .unwind_info = module.unwind_info,
1010 .vmaddr_slide = undefined,881 .eh_frame = module.eh_frame,
1011 .mapped_memory = mapped_mem,882 .mapped_memory = mapped_mem,
1012 .ofiles = Module.OFileTable.init(allocator),883 .symbols = symbols_slice,
1013 .symbols = symbols,
1014 .strings = strings,884 .strings = strings,
885 .ofiles = .empty,
1015 };886 };
1016}887}
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
1112const MachoSymbol = struct {889const MachoSymbol = struct {
1113 strx: u32,890 strx: u32,
1114 addr: u64,891 addr: u64,
1115 size: u32,892 size: u32,
1116 ofile: u32,893 ofile: u32,
1117
1118 /// Returns the address from the macho file
1119 fn address(self: MachoSymbol) u64 {
1120 return self.addr;
1121 }
1122
1123 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {894 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
1124 _ = context;895 _ = context;
1125 return lhs.addr < rhs.addr;896 return lhs.addr < rhs.addr;
1126 }897 }
1127};898 /// Assumes that `symbols` is sorted in order of ascending `addr`.
1128899 fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
1129/// Takes ownership of file, even on error.900 if (symbols.len == 0) return null; // no potential match
1130/// TODO it's weird to take ownership even on error, rework this code.901 if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
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: {
1215 var left: usize = 0;902 var left: usize = 0;
1216 var len: usize = indices.len;903 var len: usize = symbols.len;
1217
1218 while (len > 1) {904 while (len > 1) {
1219 const mid = left + len / 2;905 const mid = left + len / 2;
1220 const offset = indices[mid].functionOffset;906 if (address < symbols[mid].addr) {
1221 if (mapped_pc < offset) {
1222 len /= 2;907 len /= 2;
1223 } else {908 } else {
1224 left = mid;909 left = mid;
1225 if (mapped_pc == offset) break;
1226 len -= len / 2;910 len -= len / 2;
1227 }911 }
1228 }912 }
913 return &symbols[left];
914 }
1229915
1230 // Last index is a sentinel containing the highest address as its functionOffset916 test find {
1231 if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo;917 const symbols: []const MachoSymbol = &.{
1232 break :blk &indices[left];918 .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
1233 };919 .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
1234920 .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
1235 const common_encodings = std.mem.bytesAsSlice(921 };
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 }
1275922
1276 break :blk .{923 try testing.expectEqual(null, find(symbols, 0));
1277 .function_offset = entries[left].functionOffset,924 try testing.expectEqual(null, find(symbols, 99));
1278 .raw_encoding = entries[left].encoding,925 try testing.expectEqual(&symbols[0], find(symbols, 100).?);
1279 };926 try testing.expectEqual(&symbols[0], find(symbols, 150).?);
1280 },927 try testing.expectEqual(&symbols[0], find(symbols, 199).?);
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 );
1286928
1287 const entries = std.mem.bytesAsSlice(929 try testing.expectEqual(&symbols[1], find(symbols, 200).?);
1288 macho.UnwindInfoCompressedEntry,930 try testing.expectEqual(&symbols[1], find(symbols, 250).?);
1289 unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)],931 try testing.expectEqual(&symbols[1], find(symbols, 299).?);
1290 );
1291 if (entries.len == 0) return error.InvalidUnwindInfo;
1292932
1293 var left: usize = 0;933 try testing.expectEqual(&symbols[2], find(symbols, 300).?);
1294 var len: usize = entries.len;934 try testing.expectEqual(&symbols[2], find(symbols, 301).?);
1295 while (len > 1) {935 try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
1296 const mid = left + len / 2;936 }
1297 const offset = second_level_index.functionOffset + entries[mid].funcOffset;937};
1298 if (mapped_pc < offset) {938test {
1299 len /= 2;939 _ = MachoSymbol;
1300 } else {940}
1301 left = mid;
1302 if (mapped_pc == offset) break;
1303 len -= len / 2;
1304 }
1305 }
1306941
1307 const entry = entries[left];942pub const UnwindContext = struct {
1308 const function_offset = second_level_index.functionOffset + entry.funcOffset;943 gpa: Allocator,
1309 if (entry.encodingIndex < header.commonEncodingsArrayCount) {944 cfa: ?usize,
1310 if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo;945 pc: usize,
1311 break :blk .{946 thread_context: *std.debug.ThreadContext,
1312 .function_offset = function_offset,947 reg_context: Dwarf.abi.RegisterContext,
1313 .raw_encoding = common_encodings[entry.encodingIndex],948 vm: Dwarf.Unwind.VirtualMachine,
1314 };949 stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
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 };
1334950
1335 if (entry.raw_encoding == 0) return error.NoUnwindInfo;951 pub fn init(gpa: Allocator, thread_context: *std.debug.ThreadContext) !UnwindContext {
1336 const reg_context = Dwarf.abi.RegisterContext{952 comptime assert(supports_unwinding);
1337 .eh_frame = false,
1338 .is_macho = true,
1339 };
1340953
1341 const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);954 const pc = stripInstructionPtrAuthCode(
1342 const new_ip = switch (builtin.cpu.arch) {955 (try regValueNative(thread_context, ip_reg_num, null)).*,
1343 .x86_64 => switch (encoding.mode.x86_64) {956 );
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 };
1353957
1354 const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize);958 const context_copy = try gpa.create(std.debug.ThreadContext);
1355 var max_reg: usize = 0;959 std.debug.copyContext(thread_context, context_copy);
1356 inline for (regs, 0..) |reg, i| {
1357 if (reg > 0) max_reg = i;
1358 }
1359960
1360 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;961 return .{
1361 const new_sp = fp + 2 * @sizeOf(usize);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);972 pub fn deinit(self: *UnwindContext) void {
1364 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;973 self.vm.deinit(self.gpa);
1365 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;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;979 pub fn getFp(self: *const UnwindContext) !usize {
1368 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;980 return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*;
1369 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;981 }
1370982
1371 for (regs, 0..) |reg, i| {983 /// Resolves the register rule and places the result into `out` (see regBytes)
1372 if (reg == 0) continue;984 pub fn resolveRegisterRule(
1373 const addr = fp - frame_offset + i * @sizeOf(usize);985 context: *UnwindContext,
1374 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);986 col: Dwarf.Unwind.VirtualMachine.Column,
1375 (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;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;
1376 }1001 }
13771002 @memset(out, undefined);
1378 break :blk new_ip;
1379 },1003 },
1380 .STACK_IMMD,1004 .undefined => {
1381 .STACK_IND,1005 @memset(out, undefined);
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;
1449 },1006 },
1450 .DWARF => {1007 .same_value => {
1451 return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));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);
1452 },1013 },
1453 },1014 .offset => |offset| {
1454 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {1015 if (context.cfa) |cfa| {
1455 .OLD => return error.UnimplementedUnwindEncoding,1016 const addr = try applyOffset(cfa, offset);
1456 .FRAMELESS => blk: {1017 const ptr: *const usize = @ptrFromInt(addr);
1457 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;1018 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
1458 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;1019 } else return error.InvalidCFA;
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;
1462 },1020 },
1463 .DWARF => {1021 .val_offset => |offset| {
1464 return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));1022 if (context.cfa) |cfa| {
1023 mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian);
1024 } else return error.InvalidCFA;
1465 },1025 },
1466 .FRAME => blk: {1026 .register => |register| {
1467 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;1027 const src = try regBytes(context.thread_context, register, context.reg_context);
1468 const ip_ptr = fp + @sizeOf(usize);1028 if (src.len != out.len) return error.RegisterSizeMismatch;
14691029 @memcpy(out, try regBytes(context.thread_context, register, context.reg_context));
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;
1503 },1030 },
1504 },1031 .expression => |expression| {
1505 else => return error.UnimplementedArch,1032 context.stack_machine.reset();
1506 };1033 const value = try context.stack_machine.run(expression, context.gpa, expression_context, context.cfa.?);
15071034 const addr = if (value) |v| blk: {
1508 context.pc = stripInstructionPtrAuthCode(new_ip);1035 if (v != .generic) return error.InvalidExpressionValue;
1509 if (context.pc > 0) context.pc -= 1;1036 break :blk v.generic;
1510 return new_ip;1037 } else return error.NoExpressionValue;
1511}1038
15121039 const ptr: *usize = @ptrFromInt(addr);
1513pub const UnwindContext = struct {1040 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
1514 allocator: Allocator,1041 },
1515 cfa: ?usize,1042 .val_expression => |expression| {
1516 pc: usize,1043 context.stack_machine.reset();
1517 thread_context: *std.debug.ThreadContext,1044 const value = try context.stack_machine.run(expression, context.gpa, expression_context, context.cfa.?);
1518 reg_context: Dwarf.abi.RegisterContext,1045 if (value) |v| {
1519 vm: VirtualMachine,1046 if (v != .generic) return error.InvalidExpressionValue;
1520 stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),1047 mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian);
15211048 } else return error.NoExpressionValue;
1522 pub fn init(1049 },
1523 allocator: Allocator,1050 .architectural => return error.UnimplementedRegisterRule,
1524 thread_context: *std.debug.ThreadContext,1051 }
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)).*;
1555 }1052 }
1556};1053};
15571054
...@@ -1584,113 +1081,30 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {...@@ -1584,113 +1081,30 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
1584/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info1081/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
1585/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.1082/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
1586fn unwindFrameDwarf(1083fn unwindFrameDwarf(
1587 allocator: Allocator,1084 unwind: *const Dwarf.Unwind,
1588 unwind: *Dwarf.Unwind,1085 load_offset: usize,
1589 base_address: usize,
1590 context: *UnwindContext,1086 context: *UnwindContext,
1591 explicit_fde_offset: ?usize,1087 explicit_fde_offset: ?usize,
1592) !usize {1088) !usize {
1593 if (!supports_unwinding) return error.UnsupportedCpuArchitecture;1089 if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
1594 if (context.pc == 0) return 0;1090 if (context.pc == 0) return 0;
15951091
1596 // Find the FDE and CIE1092 const pc_vaddr = context.pc - load_offset;
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;
16031093
1604 const fde_entry_header = try Dwarf.Unwind.EntryHeader.read(&fbr, .eh_frame, native_endian);1094 const fde_offset = explicit_fde_offset orelse try unwind.findFdeOffset(
1605 if (fde_entry_header.type != .fde) return error.MissingFDE;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;1101 // Check if this FDE *actually* includes the address.
1608 fbr.seek = @intCast(cie_offset);1102 if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo;
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 };
16891103
1690 // Do not set `compile_unit` because the spec states that CFIs1104 // Do not set `compile_unit` because the spec states that CFIs
1691 // may not reference other debug sections anyway.1105 // may not reference other debug sections anyway.
1692 var expression_context: Dwarf.expression.Context = .{1106 var expression_context: Dwarf.expression.Context = .{
1693 .format = cie.format,1107 .format = format,
1694 .thread_context = context.thread_context,1108 .thread_context = context.thread_context,
1695 .reg_context = context.reg_context,1109 .reg_context = context.reg_context,
1696 .cfa = context.cfa,1110 .cfa = context.cfa,
...@@ -1700,7 +1114,7 @@ fn unwindFrameDwarf(...@@ -1700,7 +1114,7 @@ fn unwindFrameDwarf(
1700 context.reg_context.eh_frame = cie.version != 4;1114 context.reg_context.eh_frame = cie.version != 4;
1701 context.reg_context.is_macho = native_os.isDarwin();1115 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);
1704 context.cfa = switch (row.cfa.rule) {1118 context.cfa = switch (row.cfa.rule) {
1705 .val_offset => |offset| blk: {1119 .val_offset => |offset| blk: {
1706 const register = row.cfa.register orelse return error.InvalidCFARule;1120 const register = row.cfa.register orelse return error.InvalidCFARule;
...@@ -1711,7 +1125,7 @@ fn unwindFrameDwarf(...@@ -1711,7 +1125,7 @@ fn unwindFrameDwarf(
1711 context.stack_machine.reset();1125 context.stack_machine.reset();
1712 const value = try context.stack_machine.run(1126 const value = try context.stack_machine.run(
1713 expr,1127 expr,
1714 context.allocator,1128 context.gpa,
1715 expression_context,1129 expression_context,
1716 context.cfa,1130 context.cfa,
1717 );1131 );
...@@ -1728,9 +1142,9 @@ fn unwindFrameDwarf(...@@ -1728,9 +1142,9 @@ fn unwindFrameDwarf(
17281142
1729 // Buffering the modifications is done because copying the thread context is not portable,1143 // Buffering the modifications is done because copying the thread context is not portable,
1730 // some implementations (ie. darwin) use internal pointers to the mcontext.1144 // 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);
1732 defer arena.deinit();1146 defer arena.deinit();
1733 const update_allocator = arena.allocator();1147 const update_arena = arena.allocator();
17341148
1735 const RegisterUpdate = struct {1149 const RegisterUpdate = struct {
1736 // Backed by thread_context1150 // Backed by thread_context
...@@ -1749,17 +1163,16 @@ fn unwindFrameDwarf(...@@ -1749,17 +1163,16 @@ fn unwindFrameDwarf(
1749 }1163 }
17501164
1751 const dest = try regBytes(context.thread_context, register, context.reg_context);1165 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;1169 const new_update = try update_arena.create(RegisterUpdate);
1755 update_tail = try update_allocator.create(RegisterUpdate);1170 new_update.* = .{
1756 update_tail.?.* = .{
1757 .dest = dest,1171 .dest = dest,
1758 .src = src,1172 .src = src,
1759 .prev = prev,1173 .prev = update_tail,
1760 };1174 };
17611175 update_tail = new_update;
1762 try column.resolveValue(context, expression_context, src);
1763 }1176 }
1764 }1177 }
17651178
...@@ -1792,7 +1205,7 @@ fn unwindFrameDwarf(...@@ -1792,7 +1205,7 @@ fn unwindFrameDwarf(
1792 // The exception to this rule is signal frames, where we return execution would be returned to the instruction1205 // The exception to this rule is signal frames, where we return execution would be returned to the instruction
1793 // that triggered the handler.1206 // that triggered the handler.
1794 const return_address = context.pc;1207 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
1797 return return_address;1210 return return_address;
1798}1211}
...@@ -1843,415 +1256,345 @@ pub fn supportsUnwinding(target: *const std.Target) bool {...@@ -1843,415 +1256,345 @@ pub fn supportsUnwinding(target: *const std.Target) bool {
1843 };1256 };
1844}1257}
18451258
1846fn unwindFrameMachODwarf(1259/// Since register rules are applied (usually) during a panic,
1847 allocator: Allocator,1260/// checked addition / subtraction is used so that we can return
1848 base_address: usize,1261/// an error and fall back to FP-based unwinding.
1849 context: *UnwindContext,1262fn applyOffset(base: usize, offset: i64) !usize {
1850 eh_frame: []const u8,1263 return if (offset >= 0)
1851 fde_offset: usize,1264 try std.math.add(usize, base, @as(usize, @intCast(offset)))
1852) !usize {1265 else
1853 var di: Dwarf = .{1266 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
1854 .endian = native_endian,1267}
1855 .is_macho = true,
1856 };
1857 defer di.deinit(context.allocator);
18581268
1859 di.sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{1269/// Uses `mmap` to map the file at `opt_path` (or, if `null`, the self executable image) into memory.
1860 .data = eh_frame,1270fn mapFileOrSelfExe(opt_path: ?[]const u8) ![]align(std.heap.page_size_min) const u8 {
1861 .owned = false,1271 const file = if (opt_path) |path|
1862 };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 );
1865}1287}
18661288
1867/// This is a virtual machine that runs DWARF call frame instructions.1289/// Unwind a frame using MachO compact unwind info (from __unwind_info).
1868pub const VirtualMachine = struct {1290/// If the compact encoding can't encode a way to unwind a frame, it will
1869 /// See section 6.4.1 of the DWARF5 specification for details on each1291/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
1870 const RegisterRule = union(enum) {1292fn unwindFrameMachO(
1871 // The spec says that the default rule for each column is the undefined rule.1293 text_base: usize,
1872 // However, it also allows ABI / compiler authors to specify alternate defaults, so1294 load_offset: usize,
1873 // there is a distinction made here.1295 context: *UnwindContext,
1874 default: void,1296 unwind_info: []const u8,
1875 undefined: void,1297 eh_frame: ?[]const u8,
1876 same_value: void,1298) !usize {
1877 // offset(N)1299 if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
1878 offset: i64,1300 const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
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 };
18901301
1891 /// Each row contains unwinding rules for a set of registers.1302 const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry);
1892 pub const Row = struct {1303 if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidUnwindInfo;
1893 /// Offset from `FrameDescriptionEntry.pc_begin`1304 const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
1894 offset: u64 = 0,1305 if (indices.len == 0) return error.MissingUnwindInfo;
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 };
19041306
1905 pub const Column = struct {1307 // MLUGG TODO HACKHACK -- Unwind needs a slight refactor to make this work well
1906 register: ?u8 = null,1308 const opt_dwarf_unwind: ?Dwarf.Unwind = if (eh_frame) |eh_frame_data| .{
1907 rule: RegisterRule = .{ .default = {} },1309 .debug_frame = null,
19081310 .eh_frame = .{
1909 /// Resolves the register rule and places the result into `out` (see regBytes)1311 .header = .{
1910 pub fn resolveValue(1312 .vaddr = undefined,
1911 self: Column,1313 .eh_frame_vaddr = @intFromPtr(eh_frame_data.ptr) - load_offset,
1912 context: *SelfInfo.UnwindContext,1314 .search_table = null,
1913 expression_context: std.debug.Dwarf.expression.Context,1315 },
1914 out: []u8,1316 .eh_frame_data = eh_frame_data,
1915 ) !void {1317 .sorted_fdes = null,
1916 switch (self.rule) {1318 },
1917 .default => {1319 } else null;
1918 const register = self.register orelse return error.InvalidRegister;1320
1919 try getRegDefaultValue(register, context, out);1321 // offset of the PC into the `__TEXT` segment
1920 },1322 const pc_text_offset = context.pc - text_base;
1921 .undefined => {1323
1922 @memset(out, undefined);1324 const start_offset: u32, const first_level_offset: u32 = index: {
1923 },1325 var left: usize = 0;
1924 .same_value => {1326 var len: usize = indices.len;
1925 // TODO: This copy could be eliminated if callers always copy the state then call this function to update it1327 while (len > 1) {
1926 const register = self.register orelse return error.InvalidRegister;1328 const mid = left + len / 2;
1927 const src = try regBytes(context.thread_context, register, context.reg_context);1329 if (pc_text_offset < indices[mid].functionOffset) {
1928 if (src.len != out.len) return error.RegisterSizeMismatch;1330 len /= 2;
1929 @memcpy(out, src);1331 } else {
1930 },1332 left = mid;
1931 .offset => |offset| {1333 len -= len / 2;
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,
1968 }1334 }
1969 }1335 }
1336 break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset };
1970 };1337 };
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 {1341 const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t);
1973 /// Index into `columns` of the first column in this row.1342 if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidUnwindInfo;
1974 start: usize = undefined,1343 const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
1975 len: u8 = 0,1344 unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
1976 };1345 );
19771346
1978 columns: std.ArrayListUnmanaged(Column) = .empty,1347 if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidUnwindInfo;
1979 stack: std.ArrayListUnmanaged(ColumnRange) = .empty,1348 const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
1980 current_row: Row = .{},
19811349
1982 /// The result of executing the CIE's initial_instructions1350 const entry: struct {
1983 cie_row: ?Row = null,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 {1365 var left: usize = 0;
1986 self.stack.deinit(allocator);1366 var len: usize = entries.len;
1987 self.columns.deinit(allocator);1367 while (len > 1) {
1988 self.* = undefined;1368 const mid = left + len / 2;
1989 }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 {1392 var left: usize = 0;
1992 self.stack.clearRetainingCapacity();1393 var len: usize = entries.len;
1993 self.columns.clearRetainingCapacity();1394 while (len > 1) {
1994 self.current_row = .{};1395 const mid = left + len / 2;
1995 self.cie_row = null;1396 if (pc_text_offset < first_level_offset + entries[mid].funcOffset) {
1996 }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 columns1405 const function_offset = first_level_offset + entry.funcOffset;
1999 pub fn rowColumns(self: VirtualMachine, row: Row) []Column {1406 if (entry.encodingIndex < common_encodings.len) {
2000 if (row.columns.len == 0) return &.{};1407 break :entry .{
2001 return self.columns.items[row.columns.start..][0..row.columns.len];1408 .function_offset = function_offset,
2002 }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.1413 const local_index = entry.encodingIndex - common_encodings.len;
2005 fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column {1414 const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t);
2006 for (self.rowColumns(self.current_row)) |*c| {1415 if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidUnwindInfo;
2007 if (c.register == register) return c;1416 const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
2008 }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) {1428 if (entry.raw_encoding == 0) return error.NoUnwindInfo;
2011 self.current_row.columns.start = self.columns.items.len;1429 const reg_context: Dwarf.abi.RegisterContext = .{ .eh_frame = false, .is_macho = true };
2012 }
2013 self.current_row.columns.len += 1;
20141430
2015 const column = try self.columns.addOne(allocator);1431 const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
2016 column.* = .{1432 const new_ip = switch (builtin.cpu.arch) {
2017 .register = register,1433 .x86_64 => switch (encoding.mode.x86_64) {
2018 };1434 .OLD => return error.UnimplementedUnwindEncoding,
1435 .RBP_FRAME => ip: {
1436 const frame = encoding.value.x86_64.frame;
20191437
2020 return column;1438 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
2021 }1439 const new_sp = fp + 2 * @sizeOf(usize);
20221440
2023 /// Runs the CIE instructions, then the FDE instructions. Execution halts1441 const ip_ptr = fp + @sizeOf(usize);
2024 /// once the row that corresponds to `pc` is known, and the row is returned.1442 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
2025 pub fn runTo(1443 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
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 }
20501444
2051 return self.current_row;1445 (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
2052 }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(1449 const regs: [5]u3 = .{
2055 self: *VirtualMachine,1450 frame.reg0,
2056 allocator: std.mem.Allocator,1451 frame.reg1,
2057 pc: u64,1452 frame.reg2,
2058 cie: std.debug.Dwarf.Unwind.CommonInformationEntry,1453 frame.reg3,
2059 fde: std.debug.Dwarf.Unwind.FrameDescriptionEntry,1454 frame.reg4,
2060 ) !Row {1455 };
2061 return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), native_endian);1456 for (regs, 0..) |reg, i| {
2062 }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 {1463 break :ip new_ip;
2065 if (!self.current_row.copy_on_write) return;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;1470 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
2068 if (self.current_row.columns.len > 0) {1471 const stack_size: usize = stack_size: {
2069 try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len);1472 if (encoding.mode.x86_64 == .STACK_IMMD) {
2070 self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row));1473 break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize);
2071 self.current_row.columns.start = new_start;1474 }
2072 }1475 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
2073 }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.1485 // Decode the Lehmer-coded sequence of registers.
2076 /// If this instruction is from the CIE, `is_initial` should be set.1486 // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
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 }
20911487
2092 const prev_row = self.current_row;1488 // Decode the variable-based permutation number into its digits. Each digit represents
2093 switch (instruction) {1489 // an index into the list of register numbers that weren't yet used in the sequence at
2094 .set_loc => |i| {1490 // the time the digit was added.
2095 if (i.address <= self.current_row.offset) return error.InvalidOperation;1491 const reg_count = frameless.stack_reg_count;
2096 // TODO: Check cie.segment_selector_size != 0 for DWARFV41492 const ip_ptr = ip_ptr: {
2097 self.current_row.offset = i.address;1493 var digits: [6]u3 = undefined;
2098 },1494 var accumulator: usize = frameless.stack_reg_permutation;
2099 inline .advance_loc,1495 var base: usize = 2;
2100 .advance_loc1,1496 for (0..reg_count) |i| {
2101 .advance_loc2,1497 const div = accumulator / base;
2102 .advance_loc4,1498 digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
2103 => |i| {1499 accumulator = div;
2104 self.current_row.offset += i.delta * cie.code_alignment_factor;1500 base += 1;
2105 self.current_row.copy_on_write = true;1501 }
2106 },1502
2107 inline .offset,1503 var registers: [6]u3 = undefined;
2108 .offset_extended,1504 var used_indices: [6]bool = @splat(false);
2109 .offset_extended_sf,1505 for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
2110 => |i| {1506 var unused_count: u8 = 0;
2111 try self.resolveCopyOnWrite(allocator);1507 const unused_index = for (used_indices, 0..) |used, index| {
2112 const column = try self.getOrAddColumn(allocator, i.register);1508 if (!used) {
2113 column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor };1509 if (target_unused_index == unused_count) break index;
2114 },1510 unused_count += 1;
2115 inline .restore,1511 }
2116 .restore_extended,1512 } else unreachable;
2117 => |i| {1513 registers[i] = @intCast(unused_index + 1);
2118 try self.resolveCopyOnWrite(allocator);1514 used_indices[unused_index] = true;
2119 if (self.cie_row) |cie_row| {1515 }
2120 const column = try self.getOrAddColumn(allocator, i.register);1516
2121 column.rule = for (self.rowColumns(cie_row)) |cie_column| {1517 var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
2122 if (cie_column.register == i.register) break cie_column.rule;1518 for (0..reg_count) |i| {
2123 } else .{ .default = {} };1519 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
2124 } else return error.InvalidOperation;1520 (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
2125 },1521 reg_addr += @sizeOf(usize);
2126 .nop => {},1522 }
2127 .undefined => |i| {1523
2128 try self.resolveCopyOnWrite(allocator);1524 break :ip_ptr reg_addr;
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,
2200 };1525 };
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;
2201 },1534 },
2202 .val_offset => |i| {1535 .DWARF => {
2203 try self.resolveCopyOnWrite(allocator);1536 const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame);
2204 const column = try self.getOrAddColumn(allocator, i.register);1537 return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf));
2205 column.rule = .{
2206 .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor,
2207 };
2208 },1538 },
2209 .val_offset_sf => |i| {1539 },
2210 try self.resolveCopyOnWrite(allocator);1540 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
2211 const column = try self.getOrAddColumn(allocator, i.register);1541 .OLD => return error.UnimplementedUnwindEncoding,
2212 column.rule = .{1542 .FRAMELESS => ip: {
2213 .val_offset = i.offset * cie.data_alignment_factor,1543 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
2214 };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;
2215 },1548 },
2216 .val_expression => |i| {1549 .DWARF => {
2217 try self.resolveCopyOnWrite(allocator);1550 const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame);
2218 const column = try self.getOrAddColumn(allocator, i.register);1551 return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf));
2219 column.rule = .{
2220 .val_expression = i.block,
2221 };
2222 },1552 },
2223 }1553 .FRAME => ip: {
1554 const frame = encoding.value.arm64.frame;
22241555
2225 return prev_row;1556 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
2226 }1557 const ip_ptr = fp + @sizeOf(usize);
2227};
22281558
2229/// Returns the ABI-defined default value this register has in the unwinding table1559 var reg_addr = fp - @sizeOf(usize);
2230/// before running any of the CIE instructions. The DWARF spec defines these as having1560 inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
2231/// the .undefined rule by default, but allows ABI authors to override that.1561 if (@field(frame.x_reg_pairs, field.name) != 0) {
2232fn getRegDefaultValue(reg_number: u8, context: *UnwindContext, out: []u8) !void {1562 (try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
2233 switch (builtin.cpu.arch) {1563 reg_addr += @sizeOf(usize);
2234 .aarch64, .aarch64_be => {1564 (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
2235 // Callee-saved registers are initialized as if they had the .same_value rule1565 reg_addr += @sizeOf(usize);
2236 if (reg_number >= 19 and reg_number <= 28) {1566 }
2237 const src = try regBytes(context.thread_context, reg_number, context.reg_context);1567 }
2238 if (src.len != out.len) return error.RegisterSizeMismatch;
2239 @memcpy(out, src);
2240 return;
2241 }
2242 },
2243 else => {},
2244 }
22451568
2246 @memset(out, undefined);1569 inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| {
2247}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,1585 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
2250/// checked addition / subtraction is used so that we can return1586 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
2251/// an error and fall back to FP-based unwinding.1587
2252fn applyOffset(base: usize, offset: i64) !usize {1588 (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
2253 return if (offset >= 0)1589 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
2254 try std.math.add(usize, base, @as(usize, @intCast(offset)))1590
2255 else1591 break :ip new_ip;
2256 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));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;
2257}1600}
lib/std/dwarf/EH.zig+28-23
...@@ -1,27 +1,32 @@...@@ -1,27 +1,32 @@
1pub const PE = struct {1pub const PE = packed struct(u8) {
2 pub const absptr = 0x00;2 type: Type,
3 rel: Rel,
34
4 pub const size_mask = 0x7;5 /// This is a special encoding which does not correspond to named `type`/`rel` values.
5 pub const sign_mask = 0x8;6 pub const omit: PE = @bitCast(@as(u8, 0xFF));
6 pub const type_mask = size_mask | sign_mask;
77
8 pub const uleb128 = 0x01;8 pub const Type = enum(u4) {
9 pub const udata2 = 0x02;9 absptr = 0x0,
10 pub const udata4 = 0x03;10 uleb128 = 0x1,
11 pub const udata8 = 0x04;11 udata2 = 0x2,
12 pub const sleb128 = 0x09;12 udata4 = 0x3,
13 pub const sdata2 = 0x0A;13 udata8 = 0x4,
14 pub const sdata4 = 0x0B;14 sleb128 = 0x9,
15 pub const sdata8 = 0x0C;15 sdata2 = 0xA,
16 sdata4 = 0xB,
17 sdata8 = 0xC,
18 _,
19 };
1620
17 pub const rel_mask = 0x70;21 pub const Rel = enum(u4) {
18 pub const pcrel = 0x10;22 abs = 0x0,
19 pub const textrel = 0x20;23 pcrel = 0x1,
20 pub const datarel = 0x30;24 textrel = 0x2,
21 pub const funcrel = 0x40;25 datarel = 0x3,
22 pub const aligned = 0x50;26 funcrel = 0x4,
2327 aligned = 0x5,
24 pub const indirect = 0x80;28 /// Undocumented GCC extension
2529 indirect = 0x8,
26 pub const omit = 0xff;30 _,
31 };
27};32};
lib/std/macho.zig+84-34
...@@ -839,62 +839,112 @@ pub const nlist = extern struct {...@@ -839,62 +839,112 @@ pub const nlist = extern struct {
839839
840pub const nlist_64 = extern struct {840pub const nlist_64 = extern struct {
841 n_strx: u32,841 n_strx: u32,
842 n_type: u8,842 n_type: packed union {
843 bits: packed struct(u8) {
844 ext: bool,
845 type: enum(u3) {
846 undf = 0,
847 abs = 1,
848 sect = 7,
849 pbud = 6,
850 indr = 5,
851 _,
852 },
853 pext: bool,
854 /// Any non-zero value indicates this is an stab, so the `stab` field should be used.
855 is_stab: u3,
856 },
857 stab: enum(u8) {
858 gsym = N_GSYM,
859 fname = N_FNAME,
860 fun = N_FUN,
861 stsym = N_STSYM,
862 lcsym = N_LCSYM,
863 bnsym = N_BNSYM,
864 ast = N_AST,
865 opt = N_OPT,
866 rsym = N_RSYM,
867 sline = N_SLINE,
868 ensym = N_ENSYM,
869 ssym = N_SSYM,
870 so = N_SO,
871 oso = N_OSO,
872 lsym = N_LSYM,
873 bincl = N_BINCL,
874 sol = N_SOL,
875 params = N_PARAMS,
876 version = N_VERSION,
877 olevel = N_OLEVEL,
878 psym = N_PSYM,
879 eincl = N_EINCL,
880 entry = N_ENTRY,
881 lbrac = N_LBRAC,
882 excl = N_EXCL,
883 rbrac = N_RBRAC,
884 bcomm = N_BCOMM,
885 ecomm = N_ECOMM,
886 ecoml = N_ECOML,
887 leng = N_LENG,
888 _,
889 },
890 },
843 n_sect: u8,891 n_sect: u8,
844 n_desc: u16,892 n_desc: packed struct(u16) {
893 _pad0: u3 = 0,
894 arm_thumb_def: bool,
895 _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 },
845 n_value: u64,907 n_value: u64,
846908
909 // MLUGG TODO DELETE
847 pub fn stab(sym: nlist_64) bool {910 pub fn stab(sym: nlist_64) bool {
848 return N_STAB & sym.n_type != 0;911 return sym.n_type.bits.is_stab != 0;
849 }
850
851 pub fn pext(sym: nlist_64) bool {
852 return N_PEXT & sym.n_type != 0;
853 }
854
855 pub fn ext(sym: nlist_64) bool {
856 return N_EXT & sym.n_type != 0;
857 }912 }
858913 // MLUGG TODO DELETE
859 pub fn sect(sym: nlist_64) bool {914 pub fn sect(sym: nlist_64) bool {
860 const type_ = N_TYPE & sym.n_type;915 return sym.n_type.type == .sect;
861 return type_ == N_SECT;
862 }916 }
863917 // MLUGG TODO DELETE
864 pub fn undf(sym: nlist_64) bool {918 pub fn undf(sym: nlist_64) bool {
865 const type_ = N_TYPE & sym.n_type;919 return sym.n_type.type == .undf;
866 return type_ == N_UNDF;
867 }920 }
868921 // MLUGG TODO DELETE
869 pub fn indr(sym: nlist_64) bool {922 pub fn indr(sym: nlist_64) bool {
870 const type_ = N_TYPE & sym.n_type;923 return sym.n_type.type == .indr;
871 return type_ == N_INDR;
872 }924 }
873925 // MLUGG TODO DELETE
874 pub fn abs(sym: nlist_64) bool {926 pub fn abs(sym: nlist_64) bool {
875 const type_ = N_TYPE & sym.n_type;927 return sym.n_type.type == .abs;
876 return type_ == N_ABS;
877 }928 }
878929 // MLUGG TODO DELETE
879 pub fn weakDef(sym: nlist_64) bool {930 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;
881 }932 }
882933 // MLUGG TODO DELETE
883 pub fn weakRef(sym: nlist_64) bool {934 pub fn weakRef(sym: nlist_64) bool {
884 return sym.n_desc & N_WEAK_REF != 0;935 return sym.n_desc.weak_ref;
885 }936 }
886937 // MLUGG TODO DELETE
887 pub fn discarded(sym: nlist_64) bool {938 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;
889 }940 }
890941 // MLUGG TODO DELETE
891 pub fn noDeadStrip(sym: nlist_64) bool {942 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;
893 }944 }
894945
895 pub fn tentative(sym: nlist_64) bool {946 pub fn tentative(sym: nlist_64) bool {
896 if (!sym.undf()) return false;947 return sym.n_type.type == .undf and sym.n_value != 0;
897 return sym.n_value != 0;
898 }948 }
899};949};
900950
...@@ -2046,7 +2096,7 @@ pub const unwind_info_compressed_second_level_page_header = extern struct {...@@ -2046,7 +2096,7 @@ pub const unwind_info_compressed_second_level_page_header = extern struct {
2046 // encodings array2096 // encodings array
2047};2097};
20482098
2049pub const UnwindInfoCompressedEntry = packed struct {2099pub const UnwindInfoCompressedEntry = packed struct(u32) {
2050 funcOffset: u24,2100 funcOffset: u24,
2051 encodingIndex: u8,2101 encodingIndex: u8,
2052};2102};
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...@@ -455,72 +455,23 @@ pub fn writeEhFrameRelocs(elf_file: *Elf, relocs: *std.array_list.Managed(elf.El
455}455}
456456
457pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void {457pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void {
458 const comp = elf_file.base.comp;
459 const gpa = comp.gpa;
460
461 try writer.writeByte(1); // version458 try writer.writeByte(1); // version
462 try writer.writeByte(DW_EH_PE.pcrel | DW_EH_PE.sdata4);459 try writer.writeByte(DW_EH_PE.pcrel | DW_EH_PE.sdata4); // eh_frame_ptr_enc
463 try writer.writeByte(DW_EH_PE.udata4);460 // Building the lookup table would be expensive work on every `flush` -- omit it.
464 try writer.writeByte(DW_EH_PE.datarel | DW_EH_PE.sdata4);461 try writer.writeByte(DW_EH_PE.omit); // fde_count_enc
462 try writer.writeByte(DW_EH_PE.omit); // table_enc
465463
466 const shdrs = elf_file.sections.items(.shdr);464 const shdrs = elf_file.sections.items(.shdr);
467 const eh_frame_shdr = shdrs[elf_file.section_indexes.eh_frame.?];465 const eh_frame_shdr = shdrs[elf_file.section_indexes.eh_frame.?];
468 const eh_frame_hdr_shdr = shdrs[elf_file.section_indexes.eh_frame_hdr.?];466 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 };
475 try writer.writeInt(467 try writer.writeInt(
476 u32,468 u32,
477 @as(u32, @bitCast(@as(469 @as(u32, @bitCast(@as(
478 i32,470 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),
480 ))),472 ))),
481 .little,473 .little,
482 );474 );
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);
524}475}
525476
526const eh_frame_hdr_header_size: usize = 12;477const eh_frame_hdr_header_size: usize = 12;