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.UnimplementedUserOpcode,
344 => return error.UnsupportedDebugInfo,
345 };
346 return us;
347 }
348
349 /// Assumes we already hold an exclusive lock.
350 fn getLoadedElf(mod: *Module, gpa: Allocator, io: Io) Error!*LoadedElf {
351 if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa, io);
352 return if (mod.loaded_elf.?) |*elf| elf else |err| err;
353 }
354
355 fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf {
356 const load_result = if (mod.name.len > 0) res: {
357 var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo;
358 defer file.close(io);
359 break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name));
360 } else res: {
361 const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) {
362 error.OutOfMemory => |e| return e,
363 else => return error.ReadFailed,
364 };
365 defer gpa.free(path);
366 var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo;
367 defer file.close(io);
368 break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path));
369 };
370
371 var elf_file = load_result catch |err| switch (err) {
372 error.OutOfMemory,
373 error.Unexpected,
374 error.Canceled,
375 => |e| return e,
376
377 error.Overflow,
378 error.TruncatedElfFile,
379 error.InvalidCompressedSection,
380 error.InvalidElfMagic,
381 error.InvalidElfVersion,
382 error.InvalidElfClass,
383 error.InvalidElfEndian,
384 => return error.InvalidDebugInfo,
385
386 error.SystemResources,
387 error.MemoryMappingNotSupported,
388 error.AccessDenied,
389 error.LockedMemoryLimitExceeded,
390 error.ProcessFdQuotaExceeded,
391 error.SystemFdQuotaExceeded,
392 error.Streaming,
393 => return error.ReadFailed,
394 };
395 errdefer elf_file.deinit(gpa);
396
397 if (elf_file.endian != native_endian) return error.InvalidDebugInfo;
398 if (elf_file.is_64 != (@sizeOf(usize) == 8)) return error.InvalidDebugInfo;
399
400 return .{
401 .file = elf_file,
402 .dwarf = .not_scanned,
403 };
404 }
405};
406
407fn findModule(si: *SelfInfo, gpa: Allocator, io: Io, address: usize, lock: enum { shared, exclusive }) Error!*Module {
408 // With the requested lock, scan the module ranges looking for `address`.
409 switch (lock) {
410 .shared => si.rwlock.lockSharedUncancelable(io),
411 .exclusive => si.rwlock.lockUncancelable(io),
412 }
413 for (si.ranges.items) |*range| {
414 if (address >= range.start and address < range.start + range.len) {
415 return &si.modules.items[range.module_index];
416 }
417 }
418 // The address wasn't in a known range. We will rebuild the module/range lists, since it's possible
419 // a new module was loaded. Upgrade to an exclusive lock if necessary.
420 switch (lock) {
421 .shared => {
422 si.rwlock.unlockShared(io);
423 si.rwlock.lockUncancelable(io);
424 },
425 .exclusive => {},
426 }
427 // Rebuild module list with the exclusive lock.
428 {
429 errdefer si.rwlock.unlock(io);
430 if (si.unwind_cache) |cache| {
431 @memset(cache, .empty);
432 }
433 for (si.modules.items) |*mod| {
434 unwind: {
435 const u = &(mod.unwind orelse break :unwind catch break :unwind);
436 for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa);
437 }
438 loaded: {
439 const l = &(mod.loaded_elf orelse break :loaded catch break :loaded);
440 l.file.deinit(gpa);
441 }
442 }
443 si.modules.clearRetainingCapacity();
444 si.ranges.clearRetainingCapacity();
445 var ctx: DlIterContext = .{ .si = si, .gpa = gpa };
446 try std.posix.dl_iterate_phdr(&ctx, error{OutOfMemory}, DlIterContext.callback);
447 }
448 // Downgrade the lock back to shared if necessary.
449 switch (lock) {
450 .shared => {
451 si.rwlock.unlock(io);
452 si.rwlock.lockSharedUncancelable(io);
453 },
454 .exclusive => {},
455 }
456 // Scan the newly rebuilt module ranges.
457 for (si.ranges.items) |*range| {
458 if (address >= range.start and address < range.start + range.len) {
459 return &si.modules.items[range.module_index];
460 }
461 }
462 // Still nothing; unlock and error.
463 switch (lock) {
464 .shared => si.rwlock.unlockShared(io),
465 .exclusive => si.rwlock.unlock(io),
466 }
467 return error.MissingDebugInfo;
468}
469const DlIterContext = struct {
470 si: *SelfInfo,
471 gpa: Allocator,
472
473 fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void {
474 _ = size;
475
476 var build_id: ?[]const u8 = null;
477 var gnu_eh_frame: ?[]const u8 = null;
478
479 // Populate `build_id` and `gnu_eh_frame`
480 for (info.phdr[0..info.phnum]) |phdr| {
481 switch (phdr.type) {
482 .NOTE => {
483 // Look for .note.gnu.build-id
484 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.vaddr);
485 var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.memsz]);
486 const name_size = r.takeInt(u32, native_endian) catch continue;
487 const desc_size = r.takeInt(u32, native_endian) catch continue;
488 const note_type = r.takeInt(u32, native_endian) catch continue;
489 const name = r.take(name_size) catch continue;
490 if (note_type != std.elf.NT_GNU_BUILD_ID) continue;
491 if (!std.mem.eql(u8, name, "GNU\x00")) continue;
492 const desc = r.take(desc_size) catch continue;
493 build_id = desc;
494 },
495 std.elf.PT.GNU_EH_FRAME => {
496 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.vaddr);
497 gnu_eh_frame = segment_ptr[0..phdr.memsz];
498 },
499 else => {},
500 }
501 }
502
503 const gpa = context.gpa;
504 const si = context.si;
505
506 const module_index = si.modules.items.len;
507 try si.modules.append(gpa, .{
508 .load_offset = info.addr,
509 // Android libc uses NULL instead of "" to mark the main program
510 .name = std.mem.sliceTo(info.name, 0) orelse "",
511 .build_id = build_id,
512 .gnu_eh_frame = gnu_eh_frame,
513 .unwind = null,
514 .loaded_elf = null,
515 });
516
517 for (info.phdr[0..info.phnum]) |phdr| {
518 if (phdr.type != .LOAD) continue;
519 try context.si.ranges.append(gpa, .{
520 // Overflowing addition handles VSDOs having vaddr = 0xffffffffff700000
521 .start = info.addr +% phdr.vaddr,
522 .len = phdr.memsz,
523 .module_index = module_index,
524 });
525 }
526 }
527};
528
529const std = @import("std");
530const Io = std.Io;
531const Allocator = std.mem.Allocator;
532const Dwarf = std.debug.Dwarf;
533const Error = std.debug.SelfInfoError;
534const assert = std.debug.assert;
535
536const builtin = @import("builtin");
537const native_endian = builtin.target.cpu.arch.endian();
538
539const SelfInfo = @This();