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}