authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-02 12:12:54+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:49+01:00
loged6ed62c42dfad18facde164785a62faa305cb6c
tree9e44353b89263774cd7f55e026c4a91d53b70a82
parentb750e7cf9e2a1225b20ef7fdf53df9ef97cf8065
signaturelock-open Commit is signed but in an unrecognized format.

more stuff


3 files changed, 555 insertions(+), 609 deletions(-)

lib/std/debug/Dwarf.zig+9-6
...@@ -1449,6 +1449,7 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {...@@ -1449,6 +1449,7 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
1449 return str[casted_offset..last :0];1449 return str[casted_offset..last :0];
1450}1450}
14511451
1452// MLUGG TODO: i am dubious of this whole thing being here atp. look closely and see if it depends on being the self process
1452pub const ElfModule = struct {1453pub const ElfModule = struct {
1453 unwind: Dwarf.Unwind,1454 unwind: Dwarf.Unwind,
1454 dwarf: Dwarf,1455 dwarf: Dwarf,
...@@ -1456,10 +1457,7 @@ pub const ElfModule = struct {...@@ -1456,10 +1457,7 @@ pub const ElfModule = struct {
1456 external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,1457 external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,
14571458
1458 pub const init: ElfModule = .{1459 pub const init: ElfModule = .{
1459 .unwind = .{1460 .unwind = .init,
1460 .debug_frame = null,
1461 .eh_frame = null,
1462 },
1463 .dwarf = .{},1461 .dwarf = .{},
1464 .mapped_memory = null,1462 .mapped_memory = null,
1465 .external_mapped_memory = null,1463 .external_mapped_memory = null,
...@@ -1508,6 +1506,8 @@ pub const ElfModule = struct {...@@ -1508,6 +1506,8 @@ pub const ElfModule = struct {
1508 /// If the required sections aren't present but a reference to external debug1506 /// If the required sections aren't present but a reference to external debug
1509 /// info is, then this this function will recurse to attempt to load the debug1507 /// info is, then this this function will recurse to attempt to load the debug
1510 /// sections from an external file.1508 /// sections from an external file.
1509 ///
1510 /// MLUGG TODO: this should *return* a thing
1511 pub fn load(1511 pub fn load(
1512 em: *ElfModule,1512 em: *ElfModule,
1513 gpa: Allocator,1513 gpa: Allocator,
...@@ -1518,6 +1518,8 @@ pub const ElfModule = struct {...@@ -1518,6 +1518,8 @@ pub const ElfModule = struct {
1518 parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,1518 parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
1519 elf_filename: ?[]const u8,1519 elf_filename: ?[]const u8,
1520 ) LoadError!void {1520 ) LoadError!void {
1521 assert(em.mapped_memory == null);
1522
1521 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;1523 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
15221524
1523 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);1525 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
...@@ -1709,8 +1711,9 @@ pub const ElfModule = struct {...@@ -1709,8 +1711,9 @@ pub const ElfModule = struct {
1709 separate_debug_crc,1711 separate_debug_crc,
1710 &sections,1712 &sections,
1711 mapped_mem,1713 mapped_mem,
1712 )) |debug_info| {1714 )) |v| {
1713 return debug_info;1715 v;
1716 return;
1714 } else |_| {}1717 } else |_| {}
17151718
1716 // <exe_dir>/.debug/<gnu_debuglink>1719 // <exe_dir>/.debug/<gnu_debuglink>
lib/std/debug/Dwarf/Unwind.zig+177-212
...@@ -1,28 +1,35 @@...@@ -1,28 +1,35 @@
1//! MLUGG TODO DOCUMENT THIS
2
1pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");3pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");
24
3/// The contents of the `.debug_frame` section as specified by DWARF. This might be a more reliable5frame_section: ?struct {
4/// stack unwind mechanism in some cases, or it may be present when `.eh_frame` is not, but fetching6 id: Section,
5/// the data requires loading the binary, so it is not a viable approach for fast stack trace7 /// The virtual address of the start of the section. "Virtual address" refers to the address in
6/// capturing within a process.8 /// the binary (e.g. `sh_addr` in an ELF file); the equivalent runtime address may be relocated
7debug_frame: ?struct {9 /// in position-independent binaries.
8 data: []const u8,10 vaddr: u64,
9 /// Offsets into `data` of FDEs, sorted by ascending `pc_begin`.11 /// The full contents of the section. May have imprecise bounds depending on `section`.
10 sorted_fdes: []SortedFdeEntry,12 ///
13 /// For `.debug_frame`, the slice length is exactly equal to the section length. This is needed
14 /// to know the number of CIEs and FDEs.
15 ///
16 /// For `.eh_frame`, the slice length may exceed the section length, i.e. the slice may refer to
17 /// more bytes than are in the second. This restriction exists because `.eh_frame_hdr` only
18 /// includes the address of the loaded `.eh_frame` data, not its length. It is not a problem
19 /// because unlike `.debug_frame`, the end of the CIE/FDE list is signaled through a sentinel
20 /// value. If this slice does have bounds, they will still be checked, preventing crashes when
21 /// reading potentially-invalid `.eh_frame` data from files.
22 bytes: []const u8,
11},23},
1224
13/// Data associated with the `.eh_frame` and `.eh_frame_hdr` sections as defined by LSB Core. The25lookup: ?union(enum) {
14/// format of `.eh_frame` is an extension of that of DWARF's `.debug_frame` -- in fact it is almost26 eh_frame_hdr: struct {
15/// identical, though subtly different in a few places.27 /// Virtual address of the `.eh_frame_hdr` section.
16eh_frame: ?struct {28 vaddr: u64,
17 header: EhFrameHeader,29 table: EhFrameHeader.SearchTable,
18 /// Though this is a slice, it may be longer than the `.eh_frame` section. When unwinding30 },
19 /// through the runtime-loaded `.eh_frame_hdr` data, we are not told the size of the `.eh_frame`31 /// Offsets into `frame_section` of FDEs, sorted by ascending `pc_begin`.
20 /// section, so construct a slice referring to all of the rest of memory. The end of the section32 sorted_fdes: []SortedFdeEntry,
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},33},
2734
28const SortedFdeEntry = struct {35const SortedFdeEntry = struct {
...@@ -34,17 +41,61 @@ const SortedFdeEntry = struct {...@@ -34,17 +41,61 @@ const SortedFdeEntry = struct {
3441
35const Section = enum { debug_frame, eh_frame };42const Section = enum { debug_frame, eh_frame };
3643
44// MLUGG TODO deinit?
45pub const init: Unwind = .{
46 .frame_section = null,
47 .lookup = null,
48};
49
37/// This represents the decoded .eh_frame_hdr header50/// This represents the decoded .eh_frame_hdr header
38pub const EhFrameHeader = struct {51pub const EhFrameHeader = struct {
39 vaddr: u64,
40 eh_frame_vaddr: u64,52 eh_frame_vaddr: u64,
41 search_table: ?struct {53 search_table: ?SearchTable,
54
55 pub const SearchTable = struct {
42 /// The byte offset of the search table into the `.eh_frame_hdr` section.56 /// The byte offset of the search table into the `.eh_frame_hdr` section.
43 offset: u8,57 offset: u8,
44 encoding: EH.PE,58 encoding: EH.PE,
45 fde_count: usize,59 fde_count: usize,
46 entries: []const u8,60 entries: []const u8,
47 },61
62 /// Returns the vaddr of the FDE for `pc`, or `null` if no matching FDE was found.
63 fn findEntry(
64 table: *const SearchTable,
65 eh_frame_hdr_vaddr: u64,
66 pc: u64,
67 addr_size_bytes: u8,
68 endian: Endian,
69 ) !?u64 {
70 const table_vaddr = eh_frame_hdr_vaddr + table.offset;
71 const entry_size = try EhFrameHeader.entrySize(table.encoding, addr_size_bytes);
72 var left: usize = 0;
73 var len: usize = table.fde_count;
74 while (len > 1) {
75 const mid = left + len / 2;
76 var entry_reader: Reader = .fixed(table.entries[mid * entry_size ..][0..entry_size]);
77 const pc_begin = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
78 .pc_rel_base = table_vaddr + left * entry_size,
79 .data_rel_base = eh_frame_hdr_vaddr,
80 }, endian);
81 if (pc < pc_begin) {
82 len /= 2;
83 } else {
84 left = mid;
85 len -= len / 2;
86 }
87 }
88 if (len == 0) return null;
89 var entry_reader: Reader = .fixed(table.entries[left * entry_size ..][0..entry_size]);
90 // Skip past `pc_begin`; we're now interested in the fde offset
91 _ = try readEhPointerAbs(&entry_reader, table.encoding.type, addr_size_bytes, endian);
92 const fde_ptr = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
93 .pc_rel_base = table_vaddr + left * entry_size,
94 .data_rel_base = eh_frame_hdr_vaddr,
95 }, endian);
96 return fde_ptr;
97 }
98 };
4899
49 pub fn entrySize(table_enc: EH.PE, addr_size_bytes: u8) !u8 {100 pub fn entrySize(table_enc: EH.PE, addr_size_bytes: u8) !u8 {
50 return switch (table_enc.type) {101 return switch (table_enc.type) {
...@@ -76,65 +127,29 @@ pub const EhFrameHeader = struct {...@@ -76,65 +127,29 @@ pub const EhFrameHeader = struct {
76 .pc_rel_base = eh_frame_hdr_vaddr + r.seek,127 .pc_rel_base = eh_frame_hdr_vaddr + r.seek,
77 }, endian);128 }, endian);
78129
130 const table: ?SearchTable = table: {
131 if (fde_count_enc == EH.PE.omit) break :table null;
132 if (table_enc == EH.PE.omit) break :table null;
133 const fde_count = try readEhPointer(&r, fde_count_enc, addr_size_bytes, .{
134 .pc_rel_base = eh_frame_hdr_vaddr + r.seek,
135 }, endian);
136 const entry_size = try entrySize(table_enc, addr_size_bytes);
137 const bytes_offset = r.seek;
138 const bytes_len = cast(usize, fde_count * entry_size) orelse return error.EndOfStream;
139 const bytes = try r.take(bytes_len);
140 break :table .{
141 .encoding = table_enc,
142 .fde_count = @intCast(fde_count),
143 .entries = bytes,
144 .offset = @intCast(bytes_offset),
145 };
146 };
147
79 return .{148 return .{
80 .vaddr = eh_frame_hdr_vaddr,
81 .eh_frame_vaddr = eh_frame_ptr,149 .eh_frame_vaddr = eh_frame_ptr,
82 .search_table = table: {150 .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 };151 };
100 }152 }
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);
121 if (pc < pc_begin) {
122 len /= 2;
123 } else {
124 left = mid;
125 len -= len / 2;
126 }
127 }
128 if (len == 0) return null;
129 var entry_reader: Reader = .fixed(table.entries[left * entry_size ..][0..entry_size]);
130 // Skip past `pc_begin`; we're now interested in the fde offset
131 _ = try readEhPointerAbs(&entry_reader, table.encoding.type, addr_size_bytes, endian);
132 const fde_ptr = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
133 .pc_rel_base = table_vaddr + left * entry_size,
134 .data_rel_base = eh_frame_hdr.vaddr,
135 }, endian);
136 return std.math.sub(u64, fde_ptr, eh_frame_hdr.eh_frame_vaddr) catch bad(); // offset into .eh_frame
137 }
138};153};
139154
140pub const EntryHeader = union(enum) {155pub const EntryHeader = union(enum) {
...@@ -356,133 +371,84 @@ pub const FrameDescriptionEntry = struct {...@@ -356,133 +371,84 @@ pub const FrameDescriptionEntry = struct {
356 }371 }
357};372};
358373
359pub fn scanDebugFrame(374/// Load unwind information from the contents of an `.eh_frame` or `.debug_frame` section.
360 unwind: *Unwind,375///
361 gpa: Allocator,376/// If the `.eh_frame_hdr` section is available, consider instead using `loadFromEhFrameHdr`. This
362 section_vaddr: u64,377/// allows the implementation to use a search table embedded in that section if it is available.
363 section_bytes: []const u8,378pub fn loadFromSection(unwind: *Unwind, section: Section, section_vaddr: u64, section_bytes: []const u8) void {
364 addr_size_bytes: u8,379 assert(unwind.frame_section == null);
365 endian: Endian,380 assert(unwind.lookup == null);
366) void {381 unwind.frame_section = .{
367 assert(unwind.debug_frame == null);382 .id = section,
368383 .bytes = section_bytes,
369 var fbr: Reader = .fixed(section_bytes);384 .vaddr = section_vaddr,
370 var fde_list: std.ArrayList(SortedFdeEntry) = .empty;385 };
371 defer fde_list.deinit(gpa);
372 while (fbr.seek < fbr.buffer.len) {
373 const entry_offset = fbr.seek;
374 switch (try EntryHeader.read(&fbr, fbr.seek, .debug_frame, endian)) {
375 // Ignore CIEs; we only need them to parse the FDEs!
376 .cie => |info| {
377 try fbr.discardAll(info.bytes_len);
378 continue;
379 },
380 .fde => |info| {
381 const cie: CommonInformationEntry = cie: {
382 var cie_reader: Reader = .fixed(section_bytes[info.cie_offset..]);
383 const cie_info = switch (try EntryHeader.read(&cie_reader, info.cie_offset, .debug_frame, endian)) {
384 .cie => |cie_info| cie_info,
385 .fde, .terminator => return bad(), // This is meant to be a CIE
386 };
387 break :cie try .parse(try cie_reader.take(cie_info.bytes_len), .debug_frame, addr_size_bytes);
388 };
389 const fde: FrameDescriptionEntry = try .parse(
390 section_vaddr + fbr.seek,
391 try fbr.take(info.bytes_len),
392 cie,
393 endian,
394 );
395 try fde_list.append(.{
396 .pc_begin = fde.pc_begin,
397 .fde_offset = entry_offset, // *not* `fde_offset`, because we need to include the entry header
398 });
399 },
400 .terminator => return bad(), // DWARF `.debug_frame` isn't meant to have terminators
401 }
402 }
403 const fde_slice = try fde_list.toOwnedSlice(gpa);
404 errdefer comptime unreachable;
405 std.mem.sortUnstable(SortedFdeEntry, fde_slice, {}, struct {
406 fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool {
407 ctx;
408 return a.pc_begin < b.pc_begin;
409 }
410 }.lessThan);
411 unwind.debug_frame = .{ .data = section_bytes, .sorted_fdes = fde_slice };
412}386}
413387
414pub fn scanEhFrame(388/// Load unwind information from a header loaded from an `.eh_frame_hdr` section, and a pointer to
389/// the contents of the `.eh_frame` section.
390///
391/// This differs from `loadFromSection` because `.eh_frame_hdr` may embed a binary search table, and
392/// if it does, this function will use that for address lookups instead of constructing our own
393/// search table.
394pub fn loadFromEhFrameHdr(
415 unwind: *Unwind,395 unwind: *Unwind,
416 gpa: Allocator,
417 header: EhFrameHeader,396 header: EhFrameHeader,
397 section_vaddr: u64,
418 section_bytes_ptr: [*]const u8,398 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 {399) !void {
427 assert(unwind.eh_frame == null);400 assert(unwind.frame_section == null);
428401 assert(unwind.lookup == null);
429 const section_bytes: []const u8 = bytes: {402 unwind.frame_section = .{
430 // If the length is unknown, let the slice span from `section_bytes_ptr` to the end of memory.403 .id = .eh_frame,
431 const len = section_bytes_len orelse (std.math.maxInt(usize) - @intFromPtr(section_bytes_ptr));404 .bytes = maxSlice(section_bytes_ptr),
432 break :bytes section_bytes_ptr[0..len];405 .vaddr = header.eh_frame_vaddr,
433 };406 };
434407 if (header.search_table) |table| {
435 if (header.search_table != null) {408 unwind.lookup = .{ .eh_frame_hdr = .{
436 // No need to populate `sorted_fdes`, the header contains a search table.409 .vaddr = section_vaddr,
437 unwind.eh_frame = .{410 .table = table,
438 .header = header,411 } };
439 .eh_frame_data = section_bytes,
440 .sorted_fdes = null,
441 };
442 return;
443 }412 }
413}
444414
445 // We aren't told the length of this section. Luckily, we don't need it, because there will be415pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endian: Endian) !void {
446 // an `EntryHeader.terminator` after the last CIE/FDE. Just make a `Reader` which will give us416 const section = unwind.frame_section.?;
447 // alllll of the bytes!417 if (unwind.lookup != null) return;
448 var fbr: Reader = .fixed(section_bytes);
449418
419 var r: Reader = .fixed(section.bytes);
450 var fde_list: std.ArrayList(SortedFdeEntry) = .empty;420 var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
451 defer fde_list.deinit(gpa);421 defer fde_list.deinit(gpa);
452422
453 while (true) {423 const saw_terminator = while (r.seek < r.buffer.len) {
454 const entry_offset = fbr.seek;424 const entry_offset = r.seek;
455 switch (try EntryHeader.read(&fbr, fbr.seek, .eh_frame, endian)) {425 switch (try EntryHeader.read(&r, entry_offset, section.id, endian)) {
456 // Ignore CIEs; we only need them to parse the FDEs!426 .cie => |cie_info| {
457 .cie => |info| {427 // Ignore CIEs for now; we'll parse them when we read a corresponding FDE
458 try fbr.discardAll(info.bytes_len);428 try r.discardAll(cie_info.bytes_len);
459 continue;429 continue;
460 },430 },
461 .fde => |info| {431 .fde => |fde_info| {
462 const cie: CommonInformationEntry = cie: {432 var cie_r: Reader = .fixed(section.bytes[fde_info.cie_offset..]);
463 var cie_reader: Reader = .fixed(section_bytes[info.cie_offset..]);433 const cie_info = switch (try EntryHeader.read(&cie_r, fde_info.cie_offset, section.id, endian)) {
464 const cie_info = switch (try EntryHeader.read(&cie_reader, info.cie_offset, .eh_frame, endian)) {434 .cie => |cie_info| cie_info,
465 .cie => |cie_info| cie_info,435 .fde, .terminator => return bad(), // this is meant to be a CIE
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 };436 };
470 const fde: FrameDescriptionEntry = try .parse(437 const cie: CommonInformationEntry = try .parse(try cie_r.take(cie_info.bytes_len), section.id, addr_size_bytes);
471 header.eh_frame_vaddr + fbr.seek,438 const fde: FrameDescriptionEntry = try .parse(section.vaddr + r.seek, try r.take(fde_info.bytes_len), cie, endian);
472 try fbr.take(info.bytes_len),
473 cie,
474 endian,
475 );
476 try fde_list.append(gpa, .{439 try fde_list.append(gpa, .{
477 .pc_begin = fde.pc_begin,440 .pc_begin = fde.pc_begin,
478 .fde_offset = entry_offset, // *not* `fde_offset`, because we need to include the entry header441 .fde_offset = entry_offset,
479 });442 });
480 },443 },
481 // Unlike `.debug_frame`, the `.eh_frame` section does have a terminator CIE -- this is444 .terminator => break true,
482 // necessary because `header` doesn't include the length of the `.eh_frame` section
483 .terminator => break,
484 }445 }
446 } else false;
447 switch (section.id) {
448 .eh_frame => if (!saw_terminator) return bad(), // `.eh_frame` indicates the end of the CIE/FDE list with a sentinel entry
449 .debug_frame => if (saw_terminator) return bad(), // `.debug_frame` uses the section bounds and does not specify a sentinel entry
485 }450 }
451
486 const fde_slice = try fde_list.toOwnedSlice(gpa);452 const fde_slice = try fde_list.toOwnedSlice(gpa);
487 errdefer comptime unreachable;453 errdefer comptime unreachable;
488 std.mem.sortUnstable(SortedFdeEntry, fde_slice, {}, struct {454 std.mem.sortUnstable(SortedFdeEntry, fde_slice, {}, struct {
...@@ -491,26 +457,29 @@ pub fn scanEhFrame(...@@ -491,26 +457,29 @@ pub fn scanEhFrame(
491 return a.pc_begin < b.pc_begin;457 return a.pc_begin < b.pc_begin;
492 }458 }
493 }.lessThan);459 }.lessThan);
494 unwind.eh_frame = .{460 unwind.lookup = .{ .sorted_fdes = fde_slice };
495 .header = header,
496 .eh_frame_data = section_bytes,
497 .sorted_fdes = fde_slice,
498 };
499}461}
500462
463/// Given a program counter value, returns the offset of the corresponding FDE, or `null` if no
464/// matching FDE was found. The returned offset can be passed to `getFde` to load the data
465/// associated with the FDE.
466///
467/// Before calling this function, `prepareLookup` must return successfully.
468///
501/// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must469/// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must
502/// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`.470/// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`.
503pub fn findFdeOffset(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: Endian) !?u64 {471pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: Endian) !?u64 {
504 // We'll break from this block only if we have a manually-constructed search table.472 const sorted_fdes: []const SortedFdeEntry = switch (unwind.lookup.?) {
505 const sorted_fdes: []const SortedFdeEntry = fdes: {473 .eh_frame_hdr => |eh_frame_hdr| {
506 if (unwind.debug_frame) |df| break :fdes df.sorted_fdes;474 const fde_vaddr = try eh_frame_hdr.table.findEntry(
507 if (unwind.eh_frame) |eh_frame| {475 eh_frame_hdr.vaddr,
508 if (eh_frame.sorted_fdes) |fdes| break :fdes fdes;476 pc,
509 // Use the search table from the `.eh_frame_hdr` section rather than one of our own477 addr_size_bytes,
510 return eh_frame.header.findEntry(pc, addr_size_bytes, endian);478 endian,
511 }479 ) orelse return null;
512 // We have no available unwind info480 return std.math.sub(u64, fde_vaddr, unwind.frame_section.?.vaddr) catch bad(); // convert vaddr to offset
513 return null;481 },
482 .sorted_fdes => |sorted_fdes| sorted_fdes,
514 };483 };
515 const first_bad_idx = std.sort.partitionPoint(SortedFdeEntry, sorted_fdes, pc, struct {484 const first_bad_idx = std.sort.partitionPoint(SortedFdeEntry, sorted_fdes, pc, struct {
516 fn canIncludePc(target_pc: u64, entry: SortedFdeEntry) bool {485 fn canIncludePc(target_pc: u64, entry: SortedFdeEntry) bool {
...@@ -523,33 +492,29 @@ pub fn findFdeOffset(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian...@@ -523,33 +492,29 @@ pub fn findFdeOffset(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian
523 return sorted_fdes[first_bad_idx - 1].fde_offset;492 return sorted_fdes[first_bad_idx - 1].fde_offset;
524}493}
525494
526pub fn loadFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endian: Endian) !struct { Format, CommonInformationEntry, FrameDescriptionEntry } {495pub fn getFde(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: {496 const section = unwind.frame_section.?;
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 };
532497
533 var fde_reader: Reader = .fixed(section_bytes[fde_offset..]);498 var fde_reader: Reader = .fixed(section.bytes[fde_offset..]);
534 const fde_info = switch (try EntryHeader.read(&fde_reader, fde_offset, section, endian)) {499 const fde_info = switch (try EntryHeader.read(&fde_reader, fde_offset, section.id, endian)) {
535 .fde => |info| info,500 .fde => |info| info,
536 .cie, .terminator => return bad(), // This is meant to be an FDE501 .cie, .terminator => return bad(), // This is meant to be an FDE
537 };502 };
538503
539 const cie_offset = fde_info.cie_offset;504 const cie_offset = fde_info.cie_offset;
540 var cie_reader: Reader = .fixed(section_bytes[cie_offset..]);505 var cie_reader: Reader = .fixed(section.bytes[cie_offset..]);
541 const cie_info = switch (try EntryHeader.read(&cie_reader, cie_offset, section, endian)) {506 const cie_info = switch (try EntryHeader.read(&cie_reader, cie_offset, section.id, endian)) {
542 .cie => |info| info,507 .cie => |info| info,
543 .fde, .terminator => return bad(), // This is meant to be a CIE508 .fde, .terminator => return bad(), // This is meant to be a CIE
544 };509 };
545510
546 const cie: CommonInformationEntry = try .parse(511 const cie: CommonInformationEntry = try .parse(
547 try cie_reader.take(cie_info.bytes_len),512 try cie_reader.take(cie_info.bytes_len),
548 section,513 section.id,
549 addr_size_bytes,514 addr_size_bytes,
550 );515 );
551 const fde: FrameDescriptionEntry = try .parse(516 const fde: FrameDescriptionEntry = try .parse(
552 section_vaddr + fde_offset + fde_reader.seek,517 section.vaddr + fde_offset + fde_reader.seek,
553 try fde_reader.take(fde_info.bytes_len),518 try fde_reader.take(fde_info.bytes_len),
554 cie,519 cie,
555 endian,520 endian,
lib/std/debug/SelfInfo.zig+369-391
...@@ -26,10 +26,13 @@ const regValueNative = Dwarf.abi.regValueNative;...@@ -26,10 +26,13 @@ const regValueNative = Dwarf.abi.regValueNative;
2626
27const SelfInfo = @This();27const SelfInfo = @This();
2828
29/// MLUGG TODO: what if this field had a less stupid name...29modules: std.AutoHashMapUnmanaged(usize, struct {
30address_map: std.AutoHashMapUnmanaged(usize, Module.DebugInfo),30 di: Module.DebugInfo,
3131 loaded_debug: bool,
32module_cache: if (native_os == .windows) std.ArrayListUnmanaged(windows.MODULEENTRY32) else void,32 loaded_unwind: bool,
33 const init: @This() = .{ .di = .init, .loaded_debug = false, .loaded_unwind = false };
34}),
35lookup_cache: Module.LookupCache,
3336
34pub const target_supported: bool = switch (native_os) {37pub const target_supported: bool = switch (native_os) {
35 .linux,38 .linux,
...@@ -46,19 +49,19 @@ pub const target_supported: bool = switch (native_os) {...@@ -46,19 +49,19 @@ pub const target_supported: bool = switch (native_os) {
46};49};
4750
48pub const init: SelfInfo = .{51pub const init: SelfInfo = .{
49 .address_map = .empty,52 .modules = .empty,
50 .module_cache = if (native_os == .windows) .empty,53 .lookup_cache = if (Module.LookupCache != void) .init,
51};54};
5255
53pub fn deinit(self: *SelfInfo) void {56pub 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...????57 // 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...????
55 var it = self.address_map.iterator();58 var it = self.modules.iterator();
56 while (it.next()) |entry| {59 while (it.next()) |entry| {
57 const mdi = entry.value_ptr.*;60 const mdi = entry.value_ptr.*;
58 mdi.deinit(self.allocator);61 mdi.deinit(self.allocator);
59 self.allocator.destroy(mdi);62 self.allocator.destroy(mdi);
60 }63 }
61 self.address_map.deinit(self.allocator);64 self.modules.deinit(self.allocator);
62 if (native_os == .windows) {65 if (native_os == .windows) {
63 for (self.modules.items) |module| {66 for (self.modules.items) |module| {
64 self.allocator.free(module.name);67 self.allocator.free(module.name);
...@@ -68,94 +71,26 @@ pub fn deinit(self: *SelfInfo) void {...@@ -68,94 +71,26 @@ pub fn deinit(self: *SelfInfo) void {
68 }71 }
69}72}
7073
71fn lookupModuleForAddress(self: *SelfInfo, gpa: Allocator, address: usize) !Module {
72 if (builtin.target.os.tag.isDarwin()) {
73 return self.lookupModuleDyld(address);
74 } else if (native_os == .windows) {
75 return self.lookupModuleWin32(gpa, address);
76 } else if (native_os == .haiku) {
77 @panic("TODO implement lookup module for Haiku");
78 } else if (builtin.target.cpu.arch.isWasm()) {
79 @panic("TODO implement lookup module for Wasm");
80 } else {
81 return self.lookupModuleDl(address);
82 }
83}
84
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 {
111 if (builtin.target.os.tag.isDarwin()) {
112 // MLUGG TODO HACKHACK
113 try loadMachODebugInfo(gpa, module, di);
114 } else if (native_os == .windows) {
115 comptime unreachable; // not supported
116 } else if (native_os == .haiku) {
117 comptime unreachable; // not supported
118 } else if (builtin.target.cpu.arch.isWasm()) {
119 comptime unreachable; // not supported
120 } else {
121 eh_frame: {
122 if (di.unwind.eh_frame != null) break :eh_frame; // already loaded
123 const eh_frame_hdr_bytes = module.gnu_eh_frame orelse break :eh_frame;
124 const eh_frame_hdr: Dwarf.Unwind.EhFrameHeader = try .parse(
125 @intFromPtr(eh_frame_hdr_bytes.ptr) - module.load_offset,
126 eh_frame_hdr_bytes,
127 @sizeOf(usize),
128 native_endian,
129 );
130 const eh_frame_addr = module.load_offset + @as(usize, @intCast(eh_frame_hdr.eh_frame_vaddr));
131 try di.unwind.scanEhFrame(
132 gpa,
133 eh_frame_hdr,
134 @ptrFromInt(eh_frame_addr),
135 null,
136 @sizeOf(usize),
137 native_endian,
138 );
139 }
140 }
141}
142
143pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {74pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
144 comptime assert(target_supported);75 comptime assert(target_supported);
145 const module = try self.lookupModuleForAddress(gpa, context.pc);76 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc); // MLUGG TODO: don't take gpa
146 const gop = try self.address_map.getOrPut(gpa, module.load_offset);77 const gop = try self.modules.getOrPut(gpa, module.load_offset);
147 if (!gop.found_existing) gop.value_ptr.* = .init;78 if (!gop.found_existing) gop.value_ptr.* = .init;
148 try loadModuleUnwindInfo(gpa, &module, gop.value_ptr);79 if (!gop.value_ptr.loaded_unwind) {
80 try module.loadUnwindInfo(gpa, &gop.value_ptr.di);
81 gop.value_ptr.loaded_unwind = true;
82 }
83 // MLUGG TODO: the stuff below is impl!
149 if (native_os.isDarwin()) {84 if (native_os.isDarwin()) {
150 // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding85 // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding
151 // via DWARF before attempting to use the compact unwind info will produce incorrect results.86 // via DWARF before attempting to use the compact unwind info will produce incorrect results.
152 if (gop.value_ptr.unwind_info) |unwind_info| {87 if (gop.value_ptr.di.unwind_info) |unwind_info| {
153 if (unwindFrameMachO(88 if (unwindFrameMachO(
154 module.text_base,89 module.text_base,
155 module.load_offset,90 module.load_offset,
156 context,91 context,
157 unwind_info,92 unwind_info,
158 gop.value_ptr.eh_frame,93 gop.value_ptr.di.eh_frame,
159 )) |return_address| {94 )) |return_address| {
160 return return_address;95 return return_address;
161 } else |err| {96 } else |err| {
...@@ -164,7 +99,7 @@ pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !us...@@ -164,7 +99,7 @@ pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !us
164 }99 }
165 return error.MissingUnwindInfo;100 return error.MissingUnwindInfo;
166 }101 }
167 if (try gop.value_ptr.getDwarfUnwindForAddress(gpa, context.pc)) |unwind| {102 if (try gop.value_ptr.di.getDwarfUnwindForAddress(gpa, context.pc)) |unwind| {
168 return unwindFrameDwarf(unwind, module.load_offset, context, null);103 return unwindFrameDwarf(unwind, module.load_offset, context, null);
169 }104 }
170 return error.MissingDebugInfo;105 return error.MissingDebugInfo;
...@@ -172,11 +107,15 @@ pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !us...@@ -172,11 +107,15 @@ pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !us
172107
173pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {108pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {
174 comptime assert(target_supported);109 comptime assert(target_supported);
175 const module = try self.lookupModuleForAddress(gpa, address);110 const module: Module = try .lookup(&self.lookup_cache, gpa, address); // MLUGG TODO: don't take gpa
176 const gop = try self.address_map.getOrPut(gpa, module.key());111 const gop = try self.modules.getOrPut(gpa, module.key());
177 if (!gop.found_existing) gop.value_ptr.* = .init;112 if (!gop.found_existing) gop.value_ptr.* = .init;
178 try loadModuleDebugInfo(gpa, &module, gop.value_ptr);113 if (!gop.value_ptr.loaded_debug) {
179 return module.getSymbolAtAddress(gpa, gop.value_ptr, address);114 // MLUGG TODO: this overloads the name 'debug info' with including vs excluding unwind info
115 // figure out a better name for one or the other (i think the inner one is maybe 'symbol info' or something idk)
116 try module.loadDebugInfo(gpa, &gop.value_ptr.di);
117 }
118 return module.getSymbolAtAddress(gpa, &gop.value_ptr.di, address);
180}119}
181120
182/// Returns the module name for a given address.121/// Returns the module name for a given address.
...@@ -184,278 +123,12 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std....@@ -184,278 +123,12 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.
184/// a path that doesn't rely on any side-effects of a prior successful module lookup.123/// a path that doesn't rely on any side-effects of a prior successful module lookup.
185pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {124pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {
186 comptime assert(target_supported);125 comptime assert(target_supported);
187 const module = try self.lookupModuleForAddress(gpa, address);126 const module: Module = try .lookup(&self.lookup_cache, gpa, address); // MLUGG TODO: don't take gpa
188 return module.name;127 return module.name;
189}128}
190129
191fn lookupModuleDl(self: *SelfInfo, address: usize) !Module {
192 _ = self; // MLUGG
193 const DlIterContext = struct {
194 /// input
195 address: usize,
196 /// output
197 module: Module,
198
199 fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void {
200 _ = size;
201 // The base address is too high
202 if (context.address < info.addr)
203 return;
204
205 const phdrs = info.phdr[0..info.phnum];
206 for (phdrs) |*phdr| {
207 if (phdr.p_type != elf.PT_LOAD) continue;
208
209 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
210 const seg_start = info.addr +% phdr.p_vaddr;
211 const seg_end = seg_start + phdr.p_memsz;
212 if (context.address >= seg_start and context.address < seg_end) {
213 context.module = .{
214 .load_offset = info.addr,
215 // Android libc uses NULL instead of "" to mark the main program
216 .name = mem.sliceTo(info.name, 0) orelse "",
217 .build_id = null,
218 .gnu_eh_frame = null,
219 };
220 break;
221 }
222 } else return;
223
224 for (info.phdr[0..info.phnum]) |phdr| {
225 switch (phdr.p_type) {
226 elf.PT_NOTE => {
227 // Look for .note.gnu.build-id
228 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
229 var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
230 const name_size = r.takeInt(u32, native_endian) catch continue;
231 const desc_size = r.takeInt(u32, native_endian) catch continue;
232 const note_type = r.takeInt(u32, native_endian) catch continue;
233 const name = r.take(name_size) catch continue;
234 if (note_type != elf.NT_GNU_BUILD_ID) continue;
235 if (!mem.eql(u8, name, "GNU\x00")) continue;
236 const desc = r.take(desc_size) catch continue;
237 context.module.build_id = desc;
238 },
239 elf.PT_GNU_EH_FRAME => {
240 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
241 context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
242 },
243 else => {},
244 }
245 }
246
247 // Stop the iteration
248 return error.Found;
249 }
250 };
251 var ctx: DlIterContext = .{
252 .address = address,
253 .module = undefined,
254 };
255 posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
256 error.Found => return ctx.module,
257 };
258 return error.MissingDebugInfo;
259}
260
261fn lookupModuleDyld(self: *SelfInfo, address: usize) !Module {
262 _ = self; // MLUGG
263 const image_count = std.c._dyld_image_count();
264 for (0..image_count) |image_idx| {
265 const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;
266 const text_base = @intFromPtr(header);
267 if (address < text_base) continue;
268 const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx));
269
270 // Find the __TEXT segment
271 var it: macho.LoadCommandIterator = .{
272 .ncmds = header.ncmds,
273 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
274 };
275 const text_segment_cmd, const text_sections = while (it.next()) |load_cmd| {
276 if (load_cmd.cmd() != .SEGMENT_64) continue;
277 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
278 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
279 break .{ segment_cmd, load_cmd.getSections() };
280 } else continue;
281
282 const seg_start = load_offset + text_segment_cmd.vmaddr;
283 assert(seg_start == text_base);
284 const seg_end = seg_start + text_segment_cmd.vmsize;
285 if (address < seg_start or address >= seg_end) continue;
286
287 // We've found the matching __TEXT segment. This is the image we need, but we must look
288 // for unwind info in it before returning.
289
290 var result: Module = .{
291 .text_base = text_base,
292 .load_offset = load_offset,
293 .name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))),
294 .unwind_info = null,
295 .eh_frame = null,
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;
307 }
308 return error.MissingDebugInfo;
309}
310
311fn lookupModuleWin32(self: *SelfInfo, gpa: Allocator, address: usize) !Module {
312 if (self.lookupModuleWin32Cache(address)) |m| return m;
313
314 {
315 // Check a new module hasn't been loaded
316 self.module_cache.clearRetainingCapacity();
317
318 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
319 if (handle == windows.INVALID_HANDLE_VALUE) {
320 return windows.unexpectedError(windows.GetLastError());
321 }
322 defer windows.CloseHandle(handle);
323
324 var entry: windows.MODULEENTRY32 = undefined;
325 entry.dwSize = @sizeOf(windows.MODULEENTRY32);
326 if (windows.kernel32.Module32First(handle, &entry) != 0) {
327 try self.module_cache.append(gpa, entry);
328 while (windows.kernel32.Module32Next(handle, &entry) != 0) {
329 try self.module_cache.append(gpa, entry);
330 }
331 }
332 }
333
334 if (self.lookupModuleWin32Cache(address)) |m| return m;
335 return error.MissingDebugInfo;
336}
337fn lookupModuleWin32Cache(self: *SelfInfo, address: usize) ?Module {
338 for (self.module_cache.items) |*entry| {
339 const base_address = @intFromPtr(entry.modBaseAddr);
340 if (address >= base_address and address < base_address + entry.modBaseSize) {
341 return .{
342 .base_address = base_address,
343 .size = entry.modBaseSize,
344 .name = std.mem.sliceTo(&entry.szModule, 0),
345 .handle = entry.hModule,
346 };
347 }
348 }
349 return null;
350}
351
352fn readCoffDebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
353 const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
354 const mapped = mapped_ptr[0..module.size];
355 var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
356 // The string table is not mapped into memory by the loader, so if a section name is in the
357 // string table then we have to map the full image file from disk. This can happen when
358 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
359 if (coff_obj.strtabRequired()) {
360 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
361 name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
362 const process_handle = windows.GetCurrentProcess();
363 const len = windows.kernel32.GetModuleFileNameExW(
364 process_handle,
365 module.handle,
366 name_buffer[4..],
367 windows.PATH_MAX_WIDE,
368 );
369 if (len == 0) return error.MissingDebugInfo;
370 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
371 error.FileNotFound => return error.MissingDebugInfo,
372 else => |e| return e,
373 };
374 errdefer coff_file.close();
375 var section_handle: windows.HANDLE = undefined;
376 const create_section_rc = windows.ntdll.NtCreateSection(
377 &section_handle,
378 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
379 null,
380 null,
381 windows.PAGE_READONLY,
382 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
383 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
384 windows.SEC_COMMIT,
385 coff_file.handle,
386 );
387 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
388 errdefer windows.CloseHandle(section_handle);
389 var coff_len: usize = 0;
390 var section_view_ptr: [*]const u8 = undefined;
391 const map_section_rc = windows.ntdll.NtMapViewOfSection(
392 section_handle,
393 process_handle,
394 @ptrCast(&section_view_ptr),
395 null,
396 0,
397 null,
398 &coff_len,
399 .ViewUnmap,
400 0,
401 windows.PAGE_READONLY,
402 );
403 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
404 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS);
405 const section_view = section_view_ptr[0..coff_len];
406 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
407 di.mapped_file = .{
408 .file = coff_file,
409 .section_handle = section_handle,
410 .section_view = section_view,
411 };
412 }
413 di.coff_image_base = coff_obj.getImageBase();
414
415 if (coff_obj.getSectionByName(".debug_info")) |_| {
416 di.dwarf = .{};
417
418 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
419 di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
420 break :blk .{
421 .data = try coff_obj.getSectionDataAlloc(section_header, gpa),
422 .virtual_address = section_header.virtual_address,
423 .owned = true,
424 };
425 } else null;
426 }
427
428 try di.dwarf.?.open(gpa, native_endian);
429 }
430
431 if (try coff_obj.getPdbPath()) |raw_path| pdb: {
432 const path = blk: {
433 if (fs.path.isAbsolute(raw_path)) {
434 break :blk raw_path;
435 } else {
436 const self_dir = try fs.selfExeDirPathAlloc(gpa);
437 defer gpa.free(self_dir);
438 break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
439 }
440 };
441 defer if (path.ptr != raw_path.ptr) gpa.free(path);
442
443 di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
444 error.FileNotFound, error.IsDir => break :pdb,
445 else => return err,
446 };
447 try di.pdb.?.parseInfoStream();
448 try di.pdb.?.parseDbiStream();
449
450 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
451 return error.InvalidDebugInfo;
452
453 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
454 }
455}
456
457const Module = switch (native_os) {130const Module = switch (native_os) {
458 else => "MLUGG TODO", // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm...131 else => {}, // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm...
459 .macos, .ios, .watchos, .tvos, .visionos => struct {132 .macos, .ios, .watchos, .tvos, .visionos => struct {
460 /// The runtime address where __TEXT is loaded.133 /// The runtime address where __TEXT is loaded.
461 text_base: usize,134 text_base: usize,
...@@ -466,6 +139,63 @@ const Module = switch (native_os) {...@@ -466,6 +139,63 @@ const Module = switch (native_os) {
466 fn key(m: *const Module) usize {139 fn key(m: *const Module) usize {
467 return m.text_base;140 return m.text_base;
468 }141 }
142 fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
143 _ = cache;
144 _ = gpa;
145 const image_count = std.c._dyld_image_count();
146 for (0..image_count) |image_idx| {
147 const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;
148 const text_base = @intFromPtr(header);
149 if (address < text_base) continue;
150 const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx));
151
152 // Find the __TEXT segment
153 var it: macho.LoadCommandIterator = .{
154 .ncmds = header.ncmds,
155 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
156 };
157 const text_segment_cmd, const text_sections = while (it.next()) |load_cmd| {
158 if (load_cmd.cmd() != .SEGMENT_64) continue;
159 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
160 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
161 break .{ segment_cmd, load_cmd.getSections() };
162 } else continue;
163
164 const seg_start = load_offset + text_segment_cmd.vmaddr;
165 assert(seg_start == text_base);
166 const seg_end = seg_start + text_segment_cmd.vmsize;
167 if (address < seg_start or address >= seg_end) continue;
168
169 // We've found the matching __TEXT segment. This is the image we need, but we must look
170 // for unwind info in it before returning.
171
172 var result: Module = .{
173 .text_base = text_base,
174 .load_offset = load_offset,
175 .name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))),
176 .unwind_info = null,
177 .eh_frame = null,
178 };
179 for (text_sections) |sect| {
180 if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
181 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr)));
182 result.unwind_info = sect_ptr[0..@intCast(sect.size)];
183 } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
184 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr)));
185 result.eh_frame = sect_ptr[0..@intCast(sect.size)];
186 }
187 }
188 return result;
189 }
190 return error.MissingDebugInfo;
191 }
192 fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
193 return loadMachODebugInfo(gpa, module, di);
194 }
195 fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
196 // MLUGG TODO HACKHACK
197 try loadMachODebugInfo(gpa, module, di);
198 }
469 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {199 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
470 const vaddr = address - module.load_offset;200 const vaddr = address - module.load_offset;
471 const symbol = MachoSymbol.find(di.symbols, vaddr) orelse return .{}; // MLUGG TODO null?201 const symbol = MachoSymbol.find(di.symbols, vaddr) orelse return .{}; // MLUGG TODO null?
...@@ -524,8 +254,8 @@ const Module = switch (native_os) {...@@ -524,8 +254,8 @@ const Module = switch (native_os) {
524 },254 },
525 };255 };
526 }256 }
257 const LookupCache = void;
527 const DebugInfo = struct {258 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,259 mapped_memory: []align(std.heap.page_size_min) const u8,
530 symbols: []const MachoSymbol,260 symbols: []const MachoSymbol,
531 strings: [:0]const u8,261 strings: [:0]const u8,
...@@ -533,6 +263,7 @@ const Module = switch (native_os) {...@@ -533,6 +263,7 @@ const Module = switch (native_os) {
533 ofiles: std.StringArrayHashMapUnmanaged(OFile),263 ofiles: std.StringArrayHashMapUnmanaged(OFile),
534264
535 // Backed by the in-memory sections mapped by the loader265 // Backed by the in-memory sections mapped by the loader
266 // MLUGG TODO: these are duplicated state. i actually reckon they should be removed from Module, and loadMachODebugInfo should be the one discovering them!
536 unwind_info: ?[]const u8,267 unwind_info: ?[]const u8,
537 eh_frame: ?[]const u8,268 eh_frame: ?[]const u8,
538269
...@@ -642,28 +373,137 @@ const Module = switch (native_os) {...@@ -642,28 +373,137 @@ const Module = switch (native_os) {
642 };373 };
643 },374 },
644 .wasi, .emscripten => struct {375 .wasi, .emscripten => struct {
376 const LookupCache = void;
645 const DebugInfo = struct {377 const DebugInfo = struct {
646 const init: DebugInfo = .{};378 const init: DebugInfo = .{};
647 fn getSymbolAtAddress(di: *DebugInfo, gpa: Allocator, base_address: usize, address: usize) !std.debug.Symbol {
648 _ = di;
649 _ = gpa;
650 _ = base_address;
651 _ = address;
652 unreachable;
653 }
654 };379 };
380 fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
381 _ = cache;
382 _ = gpa;
383 _ = address;
384 @panic("TODO implement lookup module for Wasm");
385 }
386 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
387 _ = module;
388 _ = gpa;
389 _ = di;
390 _ = address;
391 unreachable;
392 }
393 fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
394 _ = module;
395 _ = gpa;
396 _ = di;
397 unreachable;
398 }
399 fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
400 _ = module;
401 _ = gpa;
402 _ = di;
403 unreachable;
404 }
655 },405 },
656 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {406 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
657 load_offset: usize,407 load_offset: usize,
658 name: []const u8,408 name: []const u8,
659 build_id: ?[]const u8,409 build_id: ?[]const u8,
660 gnu_eh_frame: ?[]const u8,410 gnu_eh_frame: ?[]const u8,
411 const LookupCache = void;
412 const DebugInfo = Dwarf.ElfModule;
661 fn key(m: Module) usize {413 fn key(m: Module) usize {
662 return m.load_offset; // MLUGG TODO: is this technically valid? idk414 return m.load_offset; // MLUGG TODO: is this technically valid? idk
663 }415 }
664 const DebugInfo = Dwarf.ElfModule;416 fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
665 fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {417 _ = cache;
666 return di.getSymbolAtAddress(gpa, native_endian, mod.load_offset, address);418 _ = gpa;
419 if (native_os == .haiku) @panic("TODO implement lookup module for Haiku");
420 const DlIterContext = struct {
421 /// input
422 address: usize,
423 /// output
424 module: Module,
425
426 fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void {
427 _ = size;
428 // The base address is too high
429 if (context.address < info.addr)
430 return;
431
432 const phdrs = info.phdr[0..info.phnum];
433 for (phdrs) |*phdr| {
434 if (phdr.p_type != elf.PT_LOAD) continue;
435
436 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
437 const seg_start = info.addr +% phdr.p_vaddr;
438 const seg_end = seg_start + phdr.p_memsz;
439 if (context.address >= seg_start and context.address < seg_end) {
440 context.module = .{
441 .load_offset = info.addr,
442 // Android libc uses NULL instead of "" to mark the main program
443 .name = mem.sliceTo(info.name, 0) orelse "",
444 .build_id = null,
445 .gnu_eh_frame = null,
446 };
447 break;
448 }
449 } else return;
450
451 for (info.phdr[0..info.phnum]) |phdr| {
452 switch (phdr.p_type) {
453 elf.PT_NOTE => {
454 // Look for .note.gnu.build-id
455 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
456 var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
457 const name_size = r.takeInt(u32, native_endian) catch continue;
458 const desc_size = r.takeInt(u32, native_endian) catch continue;
459 const note_type = r.takeInt(u32, native_endian) catch continue;
460 const name = r.take(name_size) catch continue;
461 if (note_type != elf.NT_GNU_BUILD_ID) continue;
462 if (!mem.eql(u8, name, "GNU\x00")) continue;
463 const desc = r.take(desc_size) catch continue;
464 context.module.build_id = desc;
465 },
466 elf.PT_GNU_EH_FRAME => {
467 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
468 context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
469 },
470 else => {},
471 }
472 }
473
474 // Stop the iteration
475 return error.Found;
476 }
477 };
478 var ctx: DlIterContext = .{
479 .address = address,
480 .module = undefined,
481 };
482 posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
483 error.Found => return ctx.module,
484 };
485 return error.MissingDebugInfo;
486 }
487 fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
488 const filename: ?[]const u8 = if (module.name.len > 0) module.name else null;
489 const mapped_mem = mapFileOrSelfExe(filename) catch |err| switch (err) {
490 error.FileNotFound => return error.MissingDebugInfo,
491 error.FileTooBig => return error.InvalidDebugInfo,
492 else => |e| return e,
493 };
494 errdefer posix.munmap(mapped_mem);
495 try di.load(gpa, mapped_mem, module.build_id, null, null, null, filename);
496 assert(di.mapped_memory != null);
497 }
498 fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
499 const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file
500 const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
501 const header: Dwarf.Unwind.EhFrameHeader = try .parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian);
502 try di.unwind.loadFromEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
503 try di.unwind.prepareLookup(gpa, @sizeOf(usize), native_endian);
504 }
505 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
506 return di.getSymbolAtAddress(gpa, native_endian, module.load_offset, address);
667 }507 }
668 },508 },
669 .uefi, .windows => struct {509 .uefi, .windows => struct {
...@@ -674,6 +514,152 @@ const Module = switch (native_os) {...@@ -674,6 +514,152 @@ const Module = switch (native_os) {
674 fn key(m: Module) usize {514 fn key(m: Module) usize {
675 return m.base_address;515 return m.base_address;
676 }516 }
517 fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
518 if (lookupInCache(cache, address)) |m| return m;
519 {
520 // Check a new module hasn't been loaded
521 cache.modules.clearRetainingCapacity();
522
523 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
524 if (handle == windows.INVALID_HANDLE_VALUE) {
525 return windows.unexpectedError(windows.GetLastError());
526 }
527 defer windows.CloseHandle(handle);
528
529 var entry: windows.MODULEENTRY32 = undefined;
530 entry.dwSize = @sizeOf(windows.MODULEENTRY32);
531 if (windows.kernel32.Module32First(handle, &entry) != 0) {
532 try cache.modules.append(gpa, entry);
533 while (windows.kernel32.Module32Next(handle, &entry) != 0) {
534 try cache.modules.append(gpa, entry);
535 }
536 }
537 }
538 if (lookupInCache(cache, address)) |m| return m;
539 return error.MissingDebugInfo;
540 }
541 fn lookupInCache(cache: *const LookupCache, address: usize) ?Module {
542 for (cache.modules.items) |*entry| {
543 const base_address = @intFromPtr(entry.modBaseAddr);
544 if (address >= base_address and address < base_address + entry.modBaseSize) {
545 return .{
546 .base_address = base_address,
547 .size = entry.modBaseSize,
548 .name = std.mem.sliceTo(&entry.szModule, 0),
549 .handle = entry.hModule,
550 };
551 }
552 }
553 return null;
554 }
555 fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
556 const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
557 const mapped = mapped_ptr[0..module.size];
558 var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
559 // The string table is not mapped into memory by the loader, so if a section name is in the
560 // string table then we have to map the full image file from disk. This can happen when
561 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
562 if (coff_obj.strtabRequired()) {
563 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
564 name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
565 const process_handle = windows.GetCurrentProcess();
566 const len = windows.kernel32.GetModuleFileNameExW(
567 process_handle,
568 module.handle,
569 name_buffer[4..],
570 windows.PATH_MAX_WIDE,
571 );
572 if (len == 0) return error.MissingDebugInfo;
573 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
574 error.FileNotFound => return error.MissingDebugInfo,
575 else => |e| return e,
576 };
577 errdefer coff_file.close();
578 var section_handle: windows.HANDLE = undefined;
579 const create_section_rc = windows.ntdll.NtCreateSection(
580 &section_handle,
581 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
582 null,
583 null,
584 windows.PAGE_READONLY,
585 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
586 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
587 windows.SEC_COMMIT,
588 coff_file.handle,
589 );
590 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
591 errdefer windows.CloseHandle(section_handle);
592 var coff_len: usize = 0;
593 var section_view_ptr: [*]const u8 = undefined;
594 const map_section_rc = windows.ntdll.NtMapViewOfSection(
595 section_handle,
596 process_handle,
597 @ptrCast(&section_view_ptr),
598 null,
599 0,
600 null,
601 &coff_len,
602 .ViewUnmap,
603 0,
604 windows.PAGE_READONLY,
605 );
606 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
607 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS);
608 const section_view = section_view_ptr[0..coff_len];
609 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
610 di.mapped_file = .{
611 .file = coff_file,
612 .section_handle = section_handle,
613 .section_view = section_view,
614 };
615 }
616 di.coff_image_base = coff_obj.getImageBase();
617
618 if (coff_obj.getSectionByName(".debug_info")) |_| {
619 di.dwarf = .{};
620
621 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
622 di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
623 break :blk .{
624 .data = try coff_obj.getSectionDataAlloc(section_header, gpa),
625 .virtual_address = section_header.virtual_address,
626 .owned = true,
627 };
628 } else null;
629 }
630
631 try di.dwarf.?.open(gpa, native_endian);
632 }
633
634 if (try coff_obj.getPdbPath()) |raw_path| pdb: {
635 const path = blk: {
636 if (fs.path.isAbsolute(raw_path)) {
637 break :blk raw_path;
638 } else {
639 const self_dir = try fs.selfExeDirPathAlloc(gpa);
640 defer gpa.free(self_dir);
641 break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
642 }
643 };
644 defer if (path.ptr != raw_path.ptr) gpa.free(path);
645
646 di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
647 error.FileNotFound, error.IsDir => break :pdb,
648 else => return err,
649 };
650 try di.pdb.?.parseInfoStream();
651 try di.pdb.?.parseDbiStream();
652
653 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
654 return error.InvalidDebugInfo;
655
656 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
657 }
658 }
659 const LookupCache = struct {
660 modules: std.ArrayListUnmanaged(windows.MODULEENTRY32),
661 const init: LookupCache = .{ .modules = .empty };
662 };
677 const DebugInfo = struct {663 const DebugInfo = struct {
678 coff_image_base: u64,664 coff_image_base: u64,
679 mapped_file: ?struct {665 mapped_file: ?struct {
...@@ -747,9 +733,9 @@ const Module = switch (native_os) {...@@ -747,9 +733,9 @@ const Module = switch (native_os) {
747 }733 }
748 };734 };
749735
750 fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {736 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
751 // Translate the runtime address into a virtual address into the module737 // Translate the runtime address into a virtual address into the module
752 const vaddr = address - mod.base_address;738 const vaddr = address - module.base_address;
753739
754 if (di.pdb != null) {740 if (di.pdb != null) {
755 if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol;741 if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol;
...@@ -1091,12 +1077,12 @@ fn unwindFrameDwarf(...@@ -1091,12 +1077,12 @@ fn unwindFrameDwarf(
10911077
1092 const pc_vaddr = context.pc - load_offset;1078 const pc_vaddr = context.pc - load_offset;
10931079
1094 const fde_offset = explicit_fde_offset orelse try unwind.findFdeOffset(1080 const fde_offset = explicit_fde_offset orelse try unwind.lookupPc(
1095 pc_vaddr,1081 pc_vaddr,
1096 @sizeOf(usize),1082 @sizeOf(usize),
1097 native_endian,1083 native_endian,
1098 ) orelse return error.MissingDebugInfo;1084 ) orelse return error.MissingDebugInfo;
1099 const format, const cie, const fde = try unwind.loadFde(fde_offset, @sizeOf(usize), native_endian);1085 const format, const cie, const fde = try unwind.getFde(fde_offset, @sizeOf(usize), native_endian);
11001086
1101 // Check if this FDE *actually* includes the address.1087 // Check if this FDE *actually* includes the address.
1102 if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo;1088 if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo;
...@@ -1294,7 +1280,7 @@ fn unwindFrameMachO(...@@ -1294,7 +1280,7 @@ fn unwindFrameMachO(
1294 load_offset: usize,1280 load_offset: usize,
1295 context: *UnwindContext,1281 context: *UnwindContext,
1296 unwind_info: []const u8,1282 unwind_info: []const u8,
1297 eh_frame: ?[]const u8,1283 opt_eh_frame: ?[]const u8,
1298) !usize {1284) !usize {
1299 if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;1285 if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
1300 const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);1286 const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
...@@ -1304,20 +1290,6 @@ fn unwindFrameMachO(...@@ -1304,20 +1290,6 @@ fn unwindFrameMachO(
1304 const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);1290 const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
1305 if (indices.len == 0) return error.MissingUnwindInfo;1291 if (indices.len == 0) return error.MissingUnwindInfo;
13061292
1307 // MLUGG TODO HACKHACK -- Unwind needs a slight refactor to make this work well
1308 const opt_dwarf_unwind: ?Dwarf.Unwind = if (eh_frame) |eh_frame_data| .{
1309 .debug_frame = null,
1310 .eh_frame = .{
1311 .header = .{
1312 .vaddr = undefined,
1313 .eh_frame_vaddr = @intFromPtr(eh_frame_data.ptr) - load_offset,
1314 .search_table = null,
1315 },
1316 .eh_frame_data = eh_frame_data,
1317 .sorted_fdes = null,
1318 },
1319 } else null;
1320
1321 // offset of the PC into the `__TEXT` segment1293 // offset of the PC into the `__TEXT` segment
1322 const pc_text_offset = context.pc - text_base;1294 const pc_text_offset = context.pc - text_base;
13231295
...@@ -1533,8 +1505,11 @@ fn unwindFrameMachO(...@@ -1533,8 +1505,11 @@ fn unwindFrameMachO(
1533 break :ip new_ip;1505 break :ip new_ip;
1534 },1506 },
1535 .DWARF => {1507 .DWARF => {
1536 const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame);1508 const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
1537 return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf));1509 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
1510 var dwarf_unwind: Dwarf.Unwind = .init;
1511 dwarf_unwind.loadFromSection(.eh_frame, eh_frame_vaddr, eh_frame);
1512 return unwindFrameDwarf(&dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf));
1538 },1513 },
1539 },1514 },
1540 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {1515 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
...@@ -1547,7 +1522,10 @@ fn unwindFrameMachO(...@@ -1547,7 +1522,10 @@ fn unwindFrameMachO(
1547 break :ip new_ip;1522 break :ip new_ip;
1548 },1523 },
1549 .DWARF => {1524 .DWARF => {
1550 const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame);1525 const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
1526 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
1527 var dwarf_unwind: Dwarf.Unwind = .init;
1528 dwarf_unwind.loadFromSection(.eh_frame, eh_frame_vaddr, eh_frame);
1551 return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf));1529 return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf));
1552 },1530 },
1553 .FRAME => ip: {1531 .FRAME => ip: {