master
1//! Cross-platform abstraction for loading debug information into an in-memory
2//! format that supports queries such as "what is the source location of this
3//! virtual memory address?"
4//!
5//! Unlike `std.debug.SelfInfo`, this API does not assume the debug information
6//! in question happens to match the host CPU architecture, OS, or other target
7//! properties.
8
9const std = @import("../std.zig");
10const Allocator = std.mem.Allocator;
11const Path = std.Build.Cache.Path;
12const assert = std.debug.assert;
13const Coverage = std.debug.Coverage;
14const SourceLocation = std.debug.Coverage.SourceLocation;
15
16const ElfFile = std.debug.ElfFile;
17const MachOFile = std.debug.MachOFile;
18
19const Info = @This();
20
21impl: union(enum) {
22 elf: ElfFile,
23 macho: MachOFile,
24},
25/// Externally managed, outlives this `Info` instance.
26coverage: *Coverage,
27
28pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || MachOFile.Error || std.debug.Dwarf.ScanError || error{ MissingDebugInfo, UnsupportedDebugInfo };
29
30pub fn load(gpa: Allocator, path: Path, coverage: *Coverage, format: std.Target.ObjectFormat, arch: std.Target.Cpu.Arch) LoadError!Info {
31 switch (format) {
32 .elf => {
33 var file = try path.root_dir.handle.openFile(path.sub_path, .{});
34 defer file.close();
35
36 var elf_file: ElfFile = try .load(gpa, file, null, &.none);
37 errdefer elf_file.deinit(gpa);
38
39 if (elf_file.dwarf == null) return error.MissingDebugInfo;
40 try elf_file.dwarf.?.open(gpa, elf_file.endian);
41 try elf_file.dwarf.?.populateRanges(gpa, elf_file.endian);
42
43 return .{
44 .impl = .{ .elf = elf_file },
45 .coverage = coverage,
46 };
47 },
48 .macho => {
49 const path_str = try path.toString(gpa);
50 defer gpa.free(path_str);
51
52 var macho_file: MachOFile = try .load(gpa, path_str, arch);
53 errdefer macho_file.deinit(gpa);
54
55 return .{
56 .impl = .{ .macho = macho_file },
57 .coverage = coverage,
58 };
59 },
60 else => return error.UnsupportedDebugInfo,
61 }
62}
63
64pub fn deinit(info: *Info, gpa: Allocator) void {
65 switch (info.impl) {
66 .elf => |*ef| ef.deinit(gpa),
67 .macho => |*mf| mf.deinit(gpa),
68 }
69 info.* = undefined;
70}
71
72pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError || error{UnsupportedDebugInfo};
73
74/// Given an array of virtual memory addresses, sorted ascending, outputs a
75/// corresponding array of source locations.
76pub fn resolveAddresses(
77 info: *Info,
78 gpa: Allocator,
79 /// Asserts the addresses are in ascending order.
80 sorted_pc_addrs: []const u64,
81 /// Asserts its length equals length of `sorted_pc_addrs`.
82 output: []SourceLocation,
83) ResolveAddressesError!void {
84 assert(sorted_pc_addrs.len == output.len);
85 switch (info.impl) {
86 .elf => |*ef| return info.coverage.resolveAddressesDwarf(gpa, ef.endian, sorted_pc_addrs, output, &ef.dwarf.?),
87 .macho => |*mf| {
88 // Resolving all of the addresses at once unfortunately isn't so easy in Mach-O binaries
89 // due to split debug information. For now, we'll just resolve the addreses one by one.
90 for (sorted_pc_addrs, output) |pc_addr, *src_loc| {
91 const dwarf, const dwarf_pc_addr = mf.getDwarfForAddress(gpa, pc_addr) catch |err| switch (err) {
92 error.InvalidMachO, error.InvalidDwarf => return error.InvalidDebugInfo,
93 else => |e| return e,
94 };
95 if (dwarf.ranges.items.len == 0) {
96 dwarf.populateRanges(gpa, .little) catch |err| switch (err) {
97 error.EndOfStream,
98 error.Overflow,
99 error.StreamTooLong,
100 error.ReadFailed,
101 => return error.InvalidDebugInfo,
102 else => |e| return e,
103 };
104 }
105 try info.coverage.resolveAddressesDwarf(gpa, .little, &.{dwarf_pc_addr}, src_loc[0..1], dwarf);
106 }
107 },
108 }
109}