1rwlock: Io.RwLock,
2
3modules: std.ArrayList(Module),
4ranges: std.ArrayList(Module.Range),
5
6unwind_cache: if (can_unwind) ?[]Dwarf.SelfUnwinder.CacheEntry else ?noreturn,
7
8pub const init: SelfInfo = .{
9 .rwlock = .init,
10 .modules = .empty,
11 .ranges = .empty,
12 .unwind_cache = null,
13};
14pub 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
33pub 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}
103pub 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}
110pub 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
117pub 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};
205comptime {
206 if (can_unwind) {
207 std.debug.assert(Dwarf.supportsUnwinding(&builtin.target));
208 }
209}
210pub const UnwindContext = Dwarf.SelfUnwinder;
211pub 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
263const 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
408fn 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}
470const 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
530const std = @import("std");
531const Io = std.Io;
532const Allocator = std.mem.Allocator;
533const Dwarf = std.debug.Dwarf;
534const Error = std.debug.SelfInfoError;
535const assert = std.debug.assert;
536
537const builtin = @import("builtin");
538const native_endian = builtin.target.cpu.arch.endian();
539
540const SelfInfo = @This();