| 1 | rwlock: Io.RwLock, |
| 2 | |
| 3 | modules: std.ArrayList(Module), |
| 4 | ranges: std.ArrayList(Module.Range), |
| 5 | |
| 6 | unwind_cache: if (can_unwind) ?[]Dwarf.SelfUnwinder.CacheEntry else ?noreturn, |
| 7 | |
| 8 | pub const init: SelfInfo = .{ |
| 9 | .rwlock = .init, |
| 10 | .modules = .empty, |
| 11 | .ranges = .empty, |
| 12 | .unwind_cache = null, |
| 13 | }; |
| 14 | pub fn deinit(si: *SelfInfo, io: Io) void { |
| 15 | _ = io; |
| 16 | const gpa = std.debug.getDebugInfoAllocator(); |
| 17 | for (si.modules.items) |*mod| { |
| 18 | unwind: { |
| 19 | const u = &(mod.unwind orelse break :unwind catch break :unwind); |
| 20 | for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa); |
| 21 | } |
| 22 | loaded: { |
| 23 | const l = &(mod.loaded_elf orelse break :loaded catch break :loaded); |
| 24 | l.file.deinit(gpa); |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | si.modules.deinit(gpa); |
| 29 | si.ranges.deinit(gpa); |
| 30 | if (si.unwind_cache) |cache| gpa.free(cache); |
| 31 | } |
| 32 | |
| 33 | pub fn getSymbols( |
| 34 | si: *SelfInfo, |
| 35 | io: Io, |
| 36 | symbol_allocator: Allocator, |
| 37 | text_arena: Allocator, |
| 38 | address: usize, |
| 39 | resolve_inline_callers: bool, |
| 40 | symbols: *std.ArrayList(std.debug.Symbol), |
| 41 | ) Error!void { |
| 42 | const gpa = std.debug.getDebugInfoAllocator(); |
| 43 | const module = try si.findModule(gpa, io, address, .exclusive); |
| 44 | defer si.rwlock.unlock(io); |
| 45 | |
| 46 | const vaddr = address - module.load_offset; |
| 47 | |
| 48 | const loaded_elf = try module.getLoadedElf(gpa, io); |
| 49 | const dwarf_err: ?Error = err: { |
| 50 | const dwarf = &(loaded_elf.file.dwarf orelse break :err null); |
| 51 | switch (loaded_elf.dwarf) { |
| 52 | .not_scanned => if (dwarf.open(gpa, native_endian)) { |
| 53 | loaded_elf.dwarf = .ok; |
| 54 | } else |err| switch (err) { |
| 55 | error.InvalidDebugInfo, |
| 56 | error.EndOfStream, |
| 57 | error.Overflow, |
| 58 | error.ReadFailed, |
| 59 | error.StreamTooLong, |
| 60 | => { |
| 61 | loaded_elf.dwarf = .invalid; |
| 62 | break :err error.InvalidDebugInfo; |
| 63 | }, |
| 64 | error.MissingDebugInfo => { |
| 65 | loaded_elf.dwarf = .missing; |
| 66 | break :err error.MissingDebugInfo; |
| 67 | }, |
| 68 | error.OutOfMemory => |e| return e, |
| 69 | }, |
| 70 | .invalid => break :err error.InvalidDebugInfo, |
| 71 | .missing => break :err error.MissingDebugInfo, |
| 72 | .ok => {}, |
| 73 | } |
| 74 | return dwarf.getSymbols( |
| 75 | symbol_allocator, |
| 76 | text_arena, |
| 77 | native_endian, |
| 78 | vaddr, |
| 79 | resolve_inline_callers, |
| 80 | symbols, |
| 81 | ) catch |err| switch (err) { |
| 82 | error.InvalidDebugInfo, |
| 83 | error.MissingDebugInfo, |
| 84 | error.UnsupportedDebugInfo, |
| 85 | => |e| break :err e, |
| 86 | |
| 87 | error.ReadFailed, |
| 88 | error.OutOfMemory, |
| 89 | error.Canceled, |
| 90 | error.Unexpected, |
| 91 | => |e| return e, |
| 92 | }; |
| 93 | }; |
| 94 | // When DWARF is unavailable, fall back to searching the symtab. |
| 95 | try symbols.append(symbol_allocator, loaded_elf.file.searchSymtab(gpa, vaddr) catch |err| switch (err) { |
| 96 | error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo, |
| 97 | error.BadSymtab => return error.InvalidDebugInfo, |
| 98 | error.OutOfMemory => |e| return e, |
| 99 | }); |
| 100 | // After searching the symtab, still report the DWARF error. |
| 101 | if (dwarf_err) |e| return e; |
| 102 | } |
| 103 | pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 { |
| 104 | const gpa = std.debug.getDebugInfoAllocator(); |
| 105 | const module = try si.findModule(gpa, io, address, .shared); |
| 106 | defer si.rwlock.unlockShared(io); |
| 107 | if (module.name.len == 0) return error.MissingDebugInfo; |
| 108 | return module.name; |
| 109 | } |
| 110 | pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) Error!usize { |
| 111 | const gpa = std.debug.getDebugInfoAllocator(); |
| 112 | const module = try si.findModule(gpa, io, address, .shared); |
| 113 | defer si.rwlock.unlockShared(io); |
| 114 | return module.load_offset; |
| 115 | } |
| 116 | |
| 117 | pub const can_unwind: bool = s: { |
| 118 | const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) { |
| 119 | .haiku => &.{ |
| 120 | .aarch64, |
| 121 | .arm, |
| 122 | .riscv64, |
| 123 | .x86, |
| 124 | .x86_64, |
| 125 | }, |
| 126 | .illumos => &.{ |
| 127 | .x86, |
| 128 | .x86_64, |
| 129 | }, |
| 130 | // Not supported yet: hppa, hppa64, microblaze/microblazeel, sh/sheb |
| 131 | .linux => &.{ |
| 132 | .aarch64, |
| 133 | .aarch64_be, |
| 134 | .alpha, |
| 135 | .arc, |
| 136 | .arm, |
| 137 | .armeb, |
| 138 | .csky, |
| 139 | .loongarch32, |
| 140 | .loongarch64, |
| 141 | .m68k, |
| 142 | .mips, |
| 143 | .mipsel, |
| 144 | .mips64, |
| 145 | .mips64el, |
| 146 | .or1k, |
| 147 | .riscv32, |
| 148 | .riscv64, |
| 149 | .s390x, |
| 150 | .thumb, |
| 151 | .thumbeb, |
| 152 | .x86, |
| 153 | .x86_64, |
| 154 | }, |
| 155 | .serenity => &.{ |
| 156 | .aarch64, |
| 157 | .x86_64, |
| 158 | .riscv64, |
| 159 | }, |
| 160 | |
| 161 | .dragonfly => &.{ |
| 162 | .x86_64, |
| 163 | }, |
| 164 | .freebsd => &.{ |
| 165 | .aarch64, |
| 166 | .arm, |
| 167 | .riscv64, |
| 168 | .x86, |
| 169 | .x86_64, |
| 170 | }, |
| 171 | // Not supported yet: hppa, mips64/mips64el, sh/sheb |
| 172 | .netbsd => &.{ |
| 173 | .aarch64, |
| 174 | .aarch64_be, |
| 175 | .alpha, |
| 176 | .arm, |
| 177 | .armeb, |
| 178 | .m68k, |
| 179 | .mips, |
| 180 | .mipsel, |
| 181 | .riscv32, |
| 182 | .riscv64, |
| 183 | .x86, |
| 184 | .x86_64, |
| 185 | }, |
| 186 | // Not supported yet: hppa, sh |
| 187 | .openbsd => &.{ |
| 188 | .aarch64, |
| 189 | .arm, |
| 190 | .m88k, |
| 191 | .mips64, |
| 192 | .mips64el, |
| 193 | .riscv64, |
| 194 | .x86, |
| 195 | .x86_64, |
| 196 | }, |
| 197 | |
| 198 | else => unreachable, |
| 199 | }; |
| 200 | for (archs) |a| { |
| 201 | if (builtin.target.cpu.arch == a) break :s true; |
| 202 | } |
| 203 | break :s false; |
| 204 | }; |
| 205 | comptime { |
| 206 | if (can_unwind) { |
| 207 | std.debug.assert(Dwarf.supportsUnwinding(&builtin.target)); |
| 208 | } |
| 209 | } |
| 210 | pub const UnwindContext = Dwarf.SelfUnwinder; |
| 211 | pub fn unwindFrame(si: *SelfInfo, io: Io, context: *UnwindContext) Error!usize { |
| 212 | comptime assert(can_unwind); |
| 213 | const gpa = std.debug.getDebugInfoAllocator(); |
| 214 | |
| 215 | { |
| 216 | si.rwlock.lockSharedUncancelable(io); |
| 217 | defer si.rwlock.unlockShared(io); |
| 218 | if (si.unwind_cache) |cache| { |
| 219 | if (Dwarf.SelfUnwinder.CacheEntry.find(cache, context.pc)) |entry| { |
| 220 | return context.next(gpa, entry); |
| 221 | } |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | const module = try si.findModule(gpa, io, context.pc, .exclusive); |
| 226 | defer si.rwlock.unlock(io); |
| 227 | |
| 228 | if (si.unwind_cache == null) { |
| 229 | si.unwind_cache = try gpa.alloc(Dwarf.SelfUnwinder.CacheEntry, 2048); |
| 230 | @memset(si.unwind_cache.?, .empty); |
| 231 | } |
| 232 | |
| 233 | const unwind_sections = try module.getUnwindSections(gpa, io); |
| 234 | for (unwind_sections) |*unwind| { |
| 235 | if (context.computeRules(gpa, unwind, module.load_offset, null)) |entry| { |
| 236 | entry.populate(si.unwind_cache.?); |
| 237 | return context.next(gpa, &entry); |
| 238 | } else |err| switch (err) { |
| 239 | error.MissingDebugInfo => continue, |
| 240 | |
| 241 | error.InvalidDebugInfo, |
| 242 | error.UnsupportedDebugInfo, |
| 243 | error.OutOfMemory, |
| 244 | => |e| return e, |
| 245 | |
| 246 | error.EndOfStream, |
| 247 | error.StreamTooLong, |
| 248 | error.ReadFailed, |
| 249 | error.Overflow, |
| 250 | error.InvalidOpcode, |
| 251 | error.InvalidOperation, |
| 252 | error.InvalidOperand, |
| 253 | => return error.InvalidDebugInfo, |
| 254 | |
| 255 | error.UnimplementedUserOpcode, |
| 256 | error.UnsupportedAddrSize, |
| 257 | => return error.UnsupportedDebugInfo, |
| 258 | } |
| 259 | } |
| 260 | return error.MissingDebugInfo; |
| 261 | } |
| 262 | |
| 263 | const Module = struct { |
| 264 | load_offset: usize, |
| 265 | name: []const u8, |
| 266 | build_id: ?[]const u8, |
| 267 | gnu_eh_frame: ?[]const u8, |
| 268 | |
| 269 | /// `null` means unwind information has not yet been loaded. |
| 270 | unwind: ?(Error!UnwindSections), |
| 271 | |
| 272 | /// `null` means the ELF file has not yet been loaded. |
| 273 | loaded_elf: ?(Error!LoadedElf), |
| 274 | |
| 275 | const LoadedElf = struct { |
| 276 | file: std.debug.ElfFile, |
| 277 | dwarf: enum { not_scanned, invalid, missing, ok }, |
| 278 | }; |
| 279 | |
| 280 | const UnwindSections = struct { |
| 281 | buf: [2]Dwarf.Unwind, |
| 282 | len: usize, |
| 283 | }; |
| 284 | |
| 285 | const Range = struct { |
| 286 | start: usize, |
| 287 | len: usize, |
| 288 | /// Index into `modules` |
| 289 | module_index: usize, |
| 290 | }; |
| 291 | |
| 292 | /// Assumes we already hold an exclusive lock. |
| 293 | fn getUnwindSections(mod: *Module, gpa: Allocator, io: Io) Error![]Dwarf.Unwind { |
| 294 | if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa, io); |
| 295 | const us = &(mod.unwind.? catch |err| return err); |
| 296 | return us.buf[0..us.len]; |
| 297 | } |
| 298 | fn loadUnwindSections(mod: *Module, gpa: Allocator, io: Io) Error!UnwindSections { |
| 299 | var us: UnwindSections = .{ |
| 300 | .buf = undefined, |
| 301 | .len = 0, |
| 302 | }; |
| 303 | if (mod.gnu_eh_frame) |section_bytes| { |
| 304 | const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - mod.load_offset; |
| 305 | const header = Dwarf.Unwind.EhFrameHeader.parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian) catch |err| switch (err) { |
| 306 | error.ReadFailed => unreachable, // it's all fixed buffers |
| 307 | error.InvalidDebugInfo => |e| return e, |
| 308 | error.EndOfStream, error.Overflow => return error.InvalidDebugInfo, |
| 309 | error.UnsupportedAddrSize => return error.UnsupportedDebugInfo, |
| 310 | }; |
| 311 | us.buf[us.len] = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(@as(usize, @intCast(mod.load_offset + header.eh_frame_vaddr)))); |
| 312 | us.len += 1; |
| 313 | } else { |
| 314 | // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame` |
| 315 | // section, but we'll have to load the binary to get at it. |
| 316 | const loaded = try mod.getLoadedElf(gpa, io); |
| 317 | // If both are present, we can't just pick one -- the info could be split between them. |
| 318 | // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one. |
| 319 | if (loaded.file.debug_frame) |*debug_frame| { |
| 320 | us.buf[us.len] = .initSection(.debug_frame, debug_frame.vaddr, debug_frame.bytes); |
| 321 | us.len += 1; |
| 322 | } |
| 323 | if (loaded.file.eh_frame) |*eh_frame| { |
| 324 | us.buf[us.len] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes); |
| 325 | us.len += 1; |
| 326 | } |
| 327 | } |
| 328 | errdefer for (us.buf[0..us.len]) |*u| u.deinit(gpa); |
| 329 | for (us.buf[0..us.len]) |*u| u.prepare(gpa, @sizeOf(usize), native_endian, true, false) catch |err| switch (err) { |
| 330 | error.ReadFailed => unreachable, // it's all fixed buffers |
| 331 | error.InvalidDebugInfo, |
| 332 | error.MissingDebugInfo, |
| 333 | error.OutOfMemory, |
| 334 | => |e| return e, |
| 335 | error.EndOfStream, |
| 336 | error.Overflow, |
| 337 | error.StreamTooLong, |
| 338 | error.InvalidOperand, |
| 339 | error.InvalidOpcode, |
| 340 | error.InvalidOperation, |
| 341 | => return error.InvalidDebugInfo, |
| 342 | error.UnsupportedAddrSize, |
| 343 | error.UnsupportedDwarfVersion, |
| 344 | error.UnimplementedUserOpcode, |
| 345 | => return error.UnsupportedDebugInfo, |
| 346 | }; |
| 347 | return us; |
| 348 | } |
| 349 | |
| 350 | /// Assumes we already hold an exclusive lock. |
| 351 | fn getLoadedElf(mod: *Module, gpa: Allocator, io: Io) Error!*LoadedElf { |
| 352 | if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa, io); |
| 353 | return if (mod.loaded_elf.?) |*elf| elf else |err| err; |
| 354 | } |
| 355 | |
| 356 | fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf { |
| 357 | const load_result = if (mod.name.len > 0) res: { |
| 358 | var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo; |
| 359 | defer file.close(io); |
| 360 | break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name)); |
| 361 | } else res: { |
| 362 | const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) { |
| 363 | error.OutOfMemory => |e| return e, |
| 364 | else => return error.ReadFailed, |
| 365 | }; |
| 366 | defer gpa.free(path); |
| 367 | var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo; |
| 368 | defer file.close(io); |
| 369 | break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path)); |
| 370 | }; |
| 371 | |
| 372 | var elf_file = load_result catch |err| switch (err) { |
| 373 | error.OutOfMemory, |
| 374 | error.Unexpected, |
| 375 | error.Canceled, |
| 376 | => |e| return e, |
| 377 | |
| 378 | error.Overflow, |
| 379 | error.TruncatedElfFile, |
| 380 | error.InvalidCompressedSection, |
| 381 | error.InvalidElfMagic, |
| 382 | error.InvalidElfVersion, |
| 383 | error.InvalidElfClass, |
| 384 | error.InvalidElfEndian, |
| 385 | => return error.InvalidDebugInfo, |
| 386 | |
| 387 | error.SystemResources, |
| 388 | error.MemoryMappingNotSupported, |
| 389 | error.AccessDenied, |
| 390 | error.LockedMemoryLimitExceeded, |
| 391 | error.ProcessFdQuotaExceeded, |
| 392 | error.SystemFdQuotaExceeded, |
| 393 | error.Streaming, |
| 394 | => return error.ReadFailed, |
| 395 | }; |
| 396 | errdefer elf_file.deinit(gpa); |
| 397 | |
| 398 | if (elf_file.endian != native_endian) return error.InvalidDebugInfo; |
| 399 | if (elf_file.is_64 != (@sizeOf(usize) == 8)) return error.InvalidDebugInfo; |
| 400 | |
| 401 | return .{ |
| 402 | .file = elf_file, |
| 403 | .dwarf = .not_scanned, |
| 404 | }; |
| 405 | } |
| 406 | }; |
| 407 | |
| 408 | fn findModule(si: *SelfInfo, gpa: Allocator, io: Io, address: usize, lock: enum { shared, exclusive }) Error!*Module { |
| 409 | // With the requested lock, scan the module ranges looking for `address`. |
| 410 | switch (lock) { |
| 411 | .shared => si.rwlock.lockSharedUncancelable(io), |
| 412 | .exclusive => si.rwlock.lockUncancelable(io), |
| 413 | } |
| 414 | for (si.ranges.items) |*range| { |
| 415 | if (address >= range.start and address < range.start + range.len) { |
| 416 | return &si.modules.items[range.module_index]; |
| 417 | } |
| 418 | } |
| 419 | // The address wasn't in a known range. We will rebuild the module/range lists, since it's possible |
| 420 | // a new module was loaded. Upgrade to an exclusive lock if necessary. |
| 421 | switch (lock) { |
| 422 | .shared => { |
| 423 | si.rwlock.unlockShared(io); |
| 424 | si.rwlock.lockUncancelable(io); |
| 425 | }, |
| 426 | .exclusive => {}, |
| 427 | } |
| 428 | // Rebuild module list with the exclusive lock. |
| 429 | { |
| 430 | errdefer si.rwlock.unlock(io); |
| 431 | if (si.unwind_cache) |cache| { |
| 432 | @memset(cache, .empty); |
| 433 | } |
| 434 | for (si.modules.items) |*mod| { |
| 435 | unwind: { |
| 436 | const u = &(mod.unwind orelse break :unwind catch break :unwind); |
| 437 | for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa); |
| 438 | } |
| 439 | loaded: { |
| 440 | const l = &(mod.loaded_elf orelse break :loaded catch break :loaded); |
| 441 | l.file.deinit(gpa); |
| 442 | } |
| 443 | } |
| 444 | si.modules.clearRetainingCapacity(); |
| 445 | si.ranges.clearRetainingCapacity(); |
| 446 | var ctx: DlIterContext = .{ .si = si, .gpa = gpa }; |
| 447 | try std.posix.dl_iterate_phdr(&ctx, error{OutOfMemory}, DlIterContext.callback); |
| 448 | } |
| 449 | // Downgrade the lock back to shared if necessary. |
| 450 | switch (lock) { |
| 451 | .shared => { |
| 452 | si.rwlock.unlock(io); |
| 453 | si.rwlock.lockSharedUncancelable(io); |
| 454 | }, |
| 455 | .exclusive => {}, |
| 456 | } |
| 457 | // Scan the newly rebuilt module ranges. |
| 458 | for (si.ranges.items) |*range| { |
| 459 | if (address >= range.start and address < range.start + range.len) { |
| 460 | return &si.modules.items[range.module_index]; |
| 461 | } |
| 462 | } |
| 463 | // Still nothing; unlock and error. |
| 464 | switch (lock) { |
| 465 | .shared => si.rwlock.unlockShared(io), |
| 466 | .exclusive => si.rwlock.unlock(io), |
| 467 | } |
| 468 | return error.MissingDebugInfo; |
| 469 | } |
| 470 | const DlIterContext = struct { |
| 471 | si: *SelfInfo, |
| 472 | gpa: Allocator, |
| 473 | |
| 474 | fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void { |
| 475 | _ = size; |
| 476 | |
| 477 | var build_id: ?[]const u8 = null; |
| 478 | var gnu_eh_frame: ?[]const u8 = null; |
| 479 | |
| 480 | // Populate `build_id` and `gnu_eh_frame` |
| 481 | for (info.phdr[0..info.phnum]) |phdr| { |
| 482 | switch (phdr.type) { |
| 483 | .NOTE => { |
| 484 | // Look for .note.gnu.build-id |
| 485 | const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.vaddr); |
| 486 | var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.memsz]); |
| 487 | const name_size = r.takeInt(u32, native_endian) catch continue; |
| 488 | const desc_size = r.takeInt(u32, native_endian) catch continue; |
| 489 | const note_type = r.takeInt(u32, native_endian) catch continue; |
| 490 | const name = r.take(name_size) catch continue; |
| 491 | if (note_type != std.elf.NT_GNU_BUILD_ID) continue; |
| 492 | if (!std.mem.eql(u8, name, "GNU\x00")) continue; |
| 493 | const desc = r.take(desc_size) catch continue; |
| 494 | build_id = desc; |
| 495 | }, |
| 496 | std.elf.PT.GNU_EH_FRAME => { |
| 497 | const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.vaddr); |
| 498 | gnu_eh_frame = segment_ptr[0..phdr.memsz]; |
| 499 | }, |
| 500 | else => {}, |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | const gpa = context.gpa; |
| 505 | const si = context.si; |
| 506 | |
| 507 | const module_index = si.modules.items.len; |
| 508 | try si.modules.append(gpa, .{ |
| 509 | .load_offset = info.addr, |
| 510 | // Android libc uses NULL instead of "" to mark the main program |
| 511 | .name = std.mem.sliceTo(info.name, 0) orelse "", |
| 512 | .build_id = build_id, |
| 513 | .gnu_eh_frame = gnu_eh_frame, |
| 514 | .unwind = null, |
| 515 | .loaded_elf = null, |
| 516 | }); |
| 517 | |
| 518 | for (info.phdr[0..info.phnum]) |phdr| { |
| 519 | if (phdr.type != .LOAD) continue; |
| 520 | try context.si.ranges.append(gpa, .{ |
| 521 | // Overflowing addition handles VSDOs having vaddr = 0xffffffffff700000 |
| 522 | .start = info.addr +% phdr.vaddr, |
| 523 | .len = phdr.memsz, |
| 524 | .module_index = module_index, |
| 525 | }); |
| 526 | } |
| 527 | } |
| 528 | }; |
| 529 | |
| 530 | const std = @import("std"); |
| 531 | const Io = std.Io; |
| 532 | const Allocator = std.mem.Allocator; |
| 533 | const Dwarf = std.debug.Dwarf; |
| 534 | const Error = std.debug.SelfInfoError; |
| 535 | const assert = std.debug.assert; |
| 536 | |
| 537 | const builtin = @import("builtin"); |
| 538 | const native_endian = builtin.target.cpu.arch.endian(); |
| 539 | |
| 540 | const SelfInfo = @This(); |