authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-02 14:44:15+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:49+01:00
log55a7affea41a4a1f4e117d7ee55c1c0e8b869203
tree70b29dd2f3df6b84ad19cbfd1d76380fa81d1c4b
parent25e02bed4cebc7c90f763fae5d57e3d99a08bdfc
signaturelock-open Commit is signed but in an unrecognized format.

me when i did a thing


2 files changed, 187 insertions(+), 196 deletions(-)

lib/std/debug/Dwarf.zig+3-3
......@@ -1780,13 +1780,13 @@ pub const ElfModule = struct {
17801780
17811781pub fn getSymbol(di: *Dwarf, allocator: Allocator, endian: Endian, address: u64) !std.debug.Symbol {
17821782 const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) {
1783 error.MissingDebugInfo, error.InvalidDebugInfo => return .{},
1783 error.MissingDebugInfo, error.InvalidDebugInfo => return .{ .name = null, .compile_unit_name = null, .source_location = null },
17841784 else => return err,
17851785 };
17861786 return .{
1787 .name = di.getSymbolName(address) orelse "???",
1787 .name = di.getSymbolName(address),
17881788 .compile_unit_name = compile_unit.die.getAttrString(di, endian, std.dwarf.AT.name, di.section(.debug_str), compile_unit) catch |err| switch (err) {
1789 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
1789 error.MissingDebugInfo, error.InvalidDebugInfo => null,
17901790 },
17911791 .source_location = di.getLineNumberInfo(allocator, endian, compile_unit, address) catch |err| switch (err) {
17921792 error.MissingDebugInfo, error.InvalidDebugInfo => null,
lib/std/debug/SelfInfo.zig+184-193
......@@ -1,6 +1,8 @@
11//! Cross-platform abstraction for this binary's own debug information, with a
22//! goal of minimal code bloat and compilation speed penalty.
33
4// MLUGG TODO: audit use of errors in this file. ideally, introduce some concrete error sets
5
46const builtin = @import("builtin");
57const native_os = builtin.os.tag;
68const native_endian = native_arch.endian();
......@@ -26,13 +28,7 @@ const regValueNative = Dwarf.abi.regValueNative;
2628
2729const SelfInfo = @This();
2830
29modules: std.AutoHashMapUnmanaged(usize, struct {
30 di: Module.DebugInfo,
31 // MLUGG TODO: okay actually these should definitely go on the impl so it can share state. e.g. loading unwind info might require lodaing debug info in some cases
32 loaded_locations: bool,
33 loaded_unwind: bool,
34 const init: @This() = .{ .di = .init, .loaded_locations = false, .loaded_unwind = false };
35}),
31modules: std.AutoHashMapUnmanaged(usize, Module.DebugInfo),
3632lookup_cache: Module.LookupCache,
3733
3834pub const target_supported: bool = switch (native_os) {
......@@ -77,11 +73,7 @@ pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !us
7773 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
7874 const gop = try self.modules.getOrPut(gpa, module.load_offset);
7975 if (!gop.found_existing) gop.value_ptr.* = .init;
80 if (!gop.value_ptr.loaded_unwind) {
81 try module.loadUnwindInfo(gpa, &gop.value_ptr.di);
82 gop.value_ptr.loaded_unwind = true;
83 }
84 return module.unwindFrame(gpa, &gop.value_ptr.di, context);
76 return module.unwindFrame(gpa, gop.value_ptr, context);
8577}
8678
8779pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {
......@@ -89,11 +81,7 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.
8981 const module: Module = try .lookup(&self.lookup_cache, gpa, address);
9082 const gop = try self.modules.getOrPut(gpa, module.key());
9183 if (!gop.found_existing) gop.value_ptr.* = .init;
92 if (!gop.value_ptr.loaded_locations) {
93 try module.loadLocationInfo(gpa, &gop.value_ptr.di);
94 gop.value_ptr.loaded_locations = true;
95 }
96 return module.getSymbolAtAddress(gpa, &gop.value_ptr.di, address);
84 return module.getSymbolAtAddress(gpa, gop.value_ptr, address);
9785}
9886
9987/// Returns the module name for a given address.
......@@ -168,9 +156,125 @@ const Module = switch (native_os) {
168156 return error.MissingDebugInfo;
169157 }
170158 fn loadLocationInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
171 try loadMachODebugInfo(gpa, module, di); // MLUGG TODO inline
159 const mapped_mem = mapFileOrSelfExe(module.name) catch |err| switch (err) {
160 error.FileNotFound => return error.MissingDebugInfo,
161 error.FileTooBig => return error.InvalidDebugInfo,
162 else => |e| return e,
163 };
164 errdefer posix.munmap(mapped_mem);
165
166 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
167 if (hdr.magic != macho.MH_MAGIC_64)
168 return error.InvalidDebugInfo;
169
170 const symtab: macho.symtab_command = symtab: {
171 var it: macho.LoadCommandIterator = .{
172 .ncmds = hdr.ncmds,
173 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
174 };
175 while (it.next()) |cmd| switch (cmd.cmd()) {
176 .SYMTAB => break :symtab cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
177 else => {},
178 };
179 return error.MissingDebugInfo;
180 };
181
182 const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab.symoff..]);
183 const syms = syms_ptr[0..symtab.nsyms];
184 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
185
186 // MLUGG TODO: does it really make sense to initCapacity here? how many of syms are omitted?
187 var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
188 defer symbols.deinit(gpa);
189
190 var ofile: u32 = undefined;
191 var last_sym: MachoSymbol = undefined;
192 var state: enum {
193 init,
194 oso_open,
195 oso_close,
196 bnsym,
197 fun_strx,
198 fun_size,
199 ensym,
200 } = .init;
201
202 for (syms) |*sym| {
203 if (sym.n_type.bits.is_stab == 0) continue;
204
205 // TODO handle globals N_GSYM, and statics N_STSYM
206 switch (sym.n_type.stab) {
207 .oso => switch (state) {
208 .init, .oso_close => {
209 state = .oso_open;
210 ofile = sym.n_strx;
211 },
212 else => return error.InvalidDebugInfo,
213 },
214 .bnsym => switch (state) {
215 .oso_open, .ensym => {
216 state = .bnsym;
217 last_sym = .{
218 .strx = 0,
219 .addr = sym.n_value,
220 .size = 0,
221 .ofile = ofile,
222 };
223 },
224 else => return error.InvalidDebugInfo,
225 },
226 .fun => switch (state) {
227 .bnsym => {
228 state = .fun_strx;
229 last_sym.strx = sym.n_strx;
230 },
231 .fun_strx => {
232 state = .fun_size;
233 last_sym.size = @intCast(sym.n_value);
234 },
235 else => return error.InvalidDebugInfo,
236 },
237 .ensym => switch (state) {
238 .fun_size => {
239 state = .ensym;
240 symbols.appendAssumeCapacity(last_sym);
241 },
242 else => return error.InvalidDebugInfo,
243 },
244 .so => switch (state) {
245 .init, .oso_close => {},
246 .oso_open, .ensym => {
247 state = .oso_close;
248 },
249 else => return error.InvalidDebugInfo,
250 },
251 else => {},
252 }
253 }
254
255 switch (state) {
256 .init => return error.MissingDebugInfo,
257 .oso_close => {},
258 else => return error.InvalidDebugInfo,
259 }
260
261 const symbols_slice = try symbols.toOwnedSlice(gpa);
262 errdefer gpa.free(symbols_slice);
263
264 // Even though lld emits symbols in ascending order, this debug code
265 // should work for programs linked in any valid way.
266 // This sort is so that we can binary search later.
267 mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
268
269 di.full = .{
270 .mapped_memory = mapped_mem,
271 .symbols = symbols_slice,
272 .strings = strings,
273 .ofiles = .empty,
274 };
172275 }
173276 fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
277 if (di.unwind != null) return;
174278 _ = gpa;
175279 di.unwind = .{
176280 .unwind_info = module.unwind_info,
......@@ -178,27 +282,39 @@ const Module = switch (native_os) {
178282 };
179283 }
180284 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
285 if (di.full == null) try module.loadLocationInfo(gpa, di);
181286 const vaddr = address - module.load_offset;
182 const symbol = MachoSymbol.find(di.full.symbols, vaddr) orelse return .{}; // MLUGG TODO null?
287 const symbol = MachoSymbol.find(di.full.?.symbols, vaddr) orelse return .{
288 .name = null,
289 .compile_unit_name = null,
290 .source_location = null,
291 };
183292
184293 // offset of `address` from start of `symbol`
185294 const address_symbol_offset = vaddr - symbol.addr;
186295
187296 // Take the symbol name from the N_FUN STAB entry, we're going to
188297 // use it if we fail to find the DWARF infos
189 const stab_symbol = mem.sliceTo(di.full.strings[symbol.strx..], 0);
190 const o_file_path = mem.sliceTo(di.full.strings[symbol.ofile..], 0);
298 const stab_symbol = mem.sliceTo(di.full.?.strings[symbol.strx..], 0);
299 const o_file_path = mem.sliceTo(di.full.?.strings[symbol.ofile..], 0);
300
301 // If any information is missing, we can at least return this from now on.
302 const sym_only_result: std.debug.Symbol = .{
303 .name = stab_symbol,
304 .compile_unit_name = null,
305 .source_location = null,
306 };
191307
192308 const o_file: *DebugInfo.OFile = of: {
193 const gop = try di.full.ofiles.getOrPut(gpa, o_file_path);
309 const gop = try di.full.?.ofiles.getOrPut(gpa, o_file_path);
194310 if (!gop.found_existing) {
195311 gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| {
196 defer _ = di.full.ofiles.pop().?;
312 defer _ = di.full.?.ofiles.pop().?;
197313 switch (err) {
198314 error.FileNotFound,
199315 error.MissingDebugInfo,
200316 error.InvalidDebugInfo,
201 => return .{ .name = stab_symbol },
317 => return sym_only_result,
202318 else => |e| return e,
203319 }
204320 };
......@@ -206,10 +322,10 @@ const Module = switch (native_os) {
206322 break :of gop.value_ptr;
207323 };
208324
209 const symbol_ofile_vaddr = o_file.addr_table.get(stab_symbol) orelse return .{ .name = stab_symbol };
325 const symbol_ofile_vaddr = o_file.addr_table.get(stab_symbol) orelse return sym_only_result;
210326
211327 const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) {
212 error.MissingDebugInfo, error.InvalidDebugInfo => return .{ .name = stab_symbol },
328 error.MissingDebugInfo, error.InvalidDebugInfo => return sym_only_result,
213329 else => |e| return e,
214330 };
215331
......@@ -222,7 +338,7 @@ const Module = switch (native_os) {
222338 o_file.dwarf.section(.debug_str),
223339 compile_unit,
224340 ) catch |err| switch (err) {
225 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
341 error.MissingDebugInfo, error.InvalidDebugInfo => null,
226342 },
227343 .source_location = o_file.dwarf.getLineNumberInfo(
228344 gpa,
......@@ -236,25 +352,27 @@ const Module = switch (native_os) {
236352 };
237353 }
238354 fn unwindFrame(module: *const Module, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
239 _ = gpa;
240 const unwind_info = di.unwind.unwind_info orelse return error.MissingUnwindInfo;
241 // MLUGG TODO: inline
355 if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
356 const unwind_info = di.unwind.?.unwind_info orelse return error.MissingUnwindInfo;
357 // MLUGG TODO: inline?
242358 return unwindFrameMachO(
243359 module.text_base,
244360 module.load_offset,
245361 context,
246362 unwind_info,
247 di.unwind.eh_frame,
363 di.unwind.?.eh_frame,
248364 );
249365 }
250366 const LookupCache = void;
251367 const DebugInfo = struct {
252 unwind: struct {
368 unwind: ?struct {
369 // Backed by the in-memory sections mapped by the loader
370 // MLUGG TODO: these are duplicated state. i actually reckon they should be removed from Module, and loadLocationInfo should be the one discovering them!
253371 unwind_info: ?[]const u8,
254372 eh_frame: ?[]const u8,
255373 },
256374 // MLUGG TODO: awful field name
257 full: struct {
375 full: ?struct {
258376 mapped_memory: []align(std.heap.page_size_min) const u8,
259377 symbols: []const MachoSymbol,
260378 strings: [:0]const u8,
......@@ -262,11 +380,10 @@ const Module = switch (native_os) {
262380 ofiles: std.StringArrayHashMapUnmanaged(OFile),
263381 },
264382
265 // 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!
267
268 // MLUGG TODO HACKHACK: this is awful
269 const init: DebugInfo = undefined;
383 const init: DebugInfo = .{
384 .unwind = null,
385 .full = null,
386 };
270387
271388 const OFile = struct {
272389 dwarf: Dwarf,
......@@ -388,18 +505,6 @@ const Module = switch (native_os) {
388505 _ = address;
389506 unreachable;
390507 }
391 fn loadLocationInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
392 _ = module;
393 _ = gpa;
394 _ = di;
395 unreachable;
396 }
397 fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
398 _ = module;
399 _ = gpa;
400 _ = di;
401 unreachable;
402 }
403508 },
404509 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
405510 load_offset: usize,
......@@ -408,9 +513,12 @@ const Module = switch (native_os) {
408513 gnu_eh_frame: ?[]const u8,
409514 const LookupCache = void;
410515 const DebugInfo = struct {
411 const init: DebugInfo = undefined; // MLUGG TODO: this makes me sad
412 em: Dwarf.ElfModule, // MLUGG TODO: bad field name (and, frankly, type)
413 unwind: Dwarf.Unwind,
516 em: ?Dwarf.ElfModule, // MLUGG TODO: bad field name (and, frankly, type)
517 unwind: ?Dwarf.Unwind,
518 const init: DebugInfo = .{
519 .em = null,
520 .unwind = null,
521 };
414522 };
415523 fn key(m: Module) usize {
416524 return m.load_offset; // MLUGG TODO: is this technically valid? idk
......@@ -496,19 +604,20 @@ const Module = switch (native_os) {
496604 errdefer posix.munmap(mapped_mem);
497605 di.em = try .load(gpa, mapped_mem, module.build_id, null, null, null, filename);
498606 }
607 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
608 if (di.em == null) try module.loadLocationInfo(gpa, di);
609 return di.em.?.getSymbolAtAddress(gpa, native_endian, module.load_offset, address);
610 }
499611 fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
500612 const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file
501613 const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
502614 const header: Dwarf.Unwind.EhFrameHeader = try .parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian);
503615 di.unwind = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
504 try di.unwind.prepareLookup(gpa, @sizeOf(usize), native_endian);
505 }
506 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
507 return di.em.getSymbolAtAddress(gpa, native_endian, module.load_offset, address);
616 try di.unwind.?.prepareLookup(gpa, @sizeOf(usize), native_endian);
508617 }
509618 fn unwindFrame(module: *const Module, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
510 _ = gpa;
511 return unwindFrameDwarf(&di.unwind, module.load_offset, context, null);
619 if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
620 return unwindFrameDwarf(&di.unwind.?, module.load_offset, context, null);
512621 }
513622 },
514623 .uefi, .windows => struct {
......@@ -660,12 +769,16 @@ const Module = switch (native_os) {
660769
661770 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
662771 }
772
773 di.loaded = true;
663774 }
664775 const LookupCache = struct {
665776 modules: std.ArrayListUnmanaged(windows.MODULEENTRY32),
666777 const init: LookupCache = .{ .modules = .empty };
667778 };
668779 const DebugInfo = struct {
780 loaded: bool,
781
669782 coff_image_base: u64,
670783 mapped_file: ?struct {
671784 file: File,
......@@ -686,6 +799,7 @@ const Module = switch (native_os) {
686799 coff_section_headers: []coff.SectionHeader,
687800
688801 const init: DebugInfo = .{
802 .loaded = false,
689803 .coff_image_base = undefined,
690804 .mapped_file = null,
691805 .dwarf = null,
......@@ -717,28 +831,24 @@ const Module = switch (native_os) {
717831 return null;
718832 };
719833
720 const module = (try di.pdb.?.getModule(mod_index)) orelse
721 return error.InvalidDebugInfo;
722 const obj_basename = fs.path.basename(module.obj_file_name);
723
724 const symbol_name = di.pdb.?.getSymbolName(
725 module,
726 relocated_address - coff_section.virtual_address,
727 ) orelse "???";
728 const opt_line_info = try di.pdb.?.getLineNumberInfo(
729 module,
730 relocated_address - coff_section.virtual_address,
731 );
834 const module = try di.pdb.?.getModule(mod_index) orelse return error.InvalidDebugInfo;
732835
733836 return .{
734 .name = symbol_name,
735 .compile_unit_name = obj_basename,
736 .source_location = opt_line_info,
837 .name = di.pdb.?.getSymbolName(
838 module,
839 relocated_address - coff_section.virtual_address,
840 ),
841 .compile_unit_name = fs.path.basename(module.obj_file_name),
842 .source_location = try di.pdb.?.getLineNumberInfo(
843 module,
844 relocated_address - coff_section.virtual_address,
845 ),
737846 };
738847 }
739848 };
740849
741850 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
851 if (!di.loaded) try module.loadLocationInfo(gpa, di);
742852 // Translate the runtime address into a virtual address into the module
743853 const vaddr = address - module.base_address;
744854
......@@ -756,125 +866,6 @@ const Module = switch (native_os) {
756866 },
757867};
758868
759fn loadMachODebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
760 const mapped_mem = mapFileOrSelfExe(module.name) catch |err| switch (err) {
761 error.FileNotFound => return error.MissingDebugInfo,
762 error.FileTooBig => return error.InvalidDebugInfo,
763 else => |e| return e,
764 };
765 errdefer posix.munmap(mapped_mem);
766
767 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
768 if (hdr.magic != macho.MH_MAGIC_64)
769 return error.InvalidDebugInfo;
770
771 const symtab: macho.symtab_command = symtab: {
772 var it: macho.LoadCommandIterator = .{
773 .ncmds = hdr.ncmds,
774 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
775 };
776 while (it.next()) |cmd| switch (cmd.cmd()) {
777 .SYMTAB => break :symtab cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
778 else => {},
779 };
780 return error.MissingDebugInfo;
781 };
782
783 const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab.symoff..]);
784 const syms = syms_ptr[0..symtab.nsyms];
785 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
786
787 // MLUGG TODO: does it really make sense to initCapacity here? how many of syms are omitted?
788 var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
789 defer symbols.deinit(gpa);
790
791 var ofile: u32 = undefined;
792 var last_sym: MachoSymbol = undefined;
793 var state: enum {
794 init,
795 oso_open,
796 oso_close,
797 bnsym,
798 fun_strx,
799 fun_size,
800 ensym,
801 } = .init;
802
803 for (syms) |*sym| {
804 if (sym.n_type.bits.is_stab == 0) continue;
805
806 // TODO handle globals N_GSYM, and statics N_STSYM
807 switch (sym.n_type.stab) {
808 .oso => switch (state) {
809 .init, .oso_close => {
810 state = .oso_open;
811 ofile = sym.n_strx;
812 },
813 else => return error.InvalidDebugInfo,
814 },
815 .bnsym => switch (state) {
816 .oso_open, .ensym => {
817 state = .bnsym;
818 last_sym = .{
819 .strx = 0,
820 .addr = sym.n_value,
821 .size = 0,
822 .ofile = ofile,
823 };
824 },
825 else => return error.InvalidDebugInfo,
826 },
827 .fun => switch (state) {
828 .bnsym => {
829 state = .fun_strx;
830 last_sym.strx = sym.n_strx;
831 },
832 .fun_strx => {
833 state = .fun_size;
834 last_sym.size = @intCast(sym.n_value);
835 },
836 else => return error.InvalidDebugInfo,
837 },
838 .ensym => switch (state) {
839 .fun_size => {
840 state = .ensym;
841 symbols.appendAssumeCapacity(last_sym);
842 },
843 else => return error.InvalidDebugInfo,
844 },
845 .so => switch (state) {
846 .init, .oso_close => {},
847 .oso_open, .ensym => {
848 state = .oso_close;
849 },
850 else => return error.InvalidDebugInfo,
851 },
852 else => {},
853 }
854 }
855
856 switch (state) {
857 .init => return error.MissingDebugInfo,
858 .oso_close => {},
859 else => return error.InvalidDebugInfo,
860 }
861
862 const symbols_slice = try symbols.toOwnedSlice(gpa);
863 errdefer gpa.free(symbols_slice);
864
865 // Even though lld emits symbols in ascending order, this debug code
866 // should work for programs linked in any valid way.
867 // This sort is so that we can binary search later.
868 mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
869
870 di.full = .{
871 .mapped_memory = mapped_mem,
872 .symbols = symbols_slice,
873 .strings = strings,
874 .ofiles = .empty,
875 };
876}
877
878869const MachoSymbol = struct {
879870 strx: u32,
880871 addr: u64,