authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-11-19 12:19:22+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-11-20 10:42:20+00:00
log0a330d4f947c1b05ac9f7d624443e2f80db2912f
tree041544172c327f401908594ed300fad56938bba1
parent0caca625ebad92495a758e3121c91ba1f32774dd
signaturelock-open Commit is signed but in an unrecognized format.

std.debug.Info: basic Mach-O support


3 files changed, 117 insertions(+), 38 deletions(-)

lib/std/Build/Fuzz.zig+25-4
......@@ -383,7 +383,14 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO
383383 errdefer gop.value_ptr.coverage.deinit(fuzz.gpa);
384384
385385 const rebuilt_exe_path = run_step.rebuilt_executable.?;
386 var debug_info = std.debug.Info.load(fuzz.gpa, rebuilt_exe_path, &gop.value_ptr.coverage) catch |err| {
386 const target = run_step.producer.?.rootModuleTarget();
387 var debug_info = std.debug.Info.load(
388 fuzz.gpa,
389 rebuilt_exe_path,
390 &gop.value_ptr.coverage,
391 target.ofmt,
392 target.cpu.arch,
393 ) catch |err| {
387394 log.err("step '{s}': failed to load debug information for '{f}': {s}", .{
388395 run_step.step.name, rebuilt_exe_path, @errorName(err),
389396 });
......@@ -479,9 +486,23 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte
479486 if (false) {
480487 const sl = coverage_map.source_locations[index];
481488 const file_name = coverage_map.coverage.stringAt(coverage_map.coverage.fileAt(sl.file).basename);
482 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} between {x} and {x}", .{
483 addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1],
484 });
489 if (pcs.len == 1) {
490 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 (final)", .{
491 addr, file_name, sl.line, sl.column,
492 });
493 } else if (index == 0) {
494 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 before {x}", .{
495 addr, file_name, sl.line, sl.column, pcs[index + 1],
496 });
497 } else if (index == pcs.len - 1) {
498 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} (final) after {x}", .{
499 addr, file_name, sl.line, sl.column, index, pcs[index - 1],
500 });
501 } else {
502 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} between {x} and {x}", .{
503 addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1],
504 });
505 }
485506 }
486507 try coverage_map.entry_points.append(fuzz.gpa, @intCast(index));
487508}
lib/std/debug/Info.zig+65-26
......@@ -9,49 +9,67 @@
99const std = @import("../std.zig");
1010const Allocator = std.mem.Allocator;
1111const Path = std.Build.Cache.Path;
12const ElfFile = std.debug.ElfFile;
1312const assert = std.debug.assert;
1413const Coverage = std.debug.Coverage;
1514const SourceLocation = std.debug.Coverage.SourceLocation;
1615
16const ElfFile = std.debug.ElfFile;
17const MachOFile = std.debug.MachOFile;
18
1719const Info = @This();
1820
19/// Sorted by key, ascending.
20address_map: std.AutoArrayHashMapUnmanaged(u64, ElfFile),
21impl: union(enum) {
22 elf: ElfFile,
23 macho: MachOFile,
24},
2125/// Externally managed, outlives this `Info` instance.
2226coverage: *Coverage,
2327
24pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || std.debug.Dwarf.ScanError || error{MissingDebugInfo};
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();
2535
26pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info {
27 var file = try path.root_dir.handle.openFile(path.sub_path, .{});
28 defer file.close();
36 var elf_file: ElfFile = try .load(gpa, file, null, &.none);
37 errdefer elf_file.deinit(gpa);
2938
30 var elf_file: ElfFile = try .load(gpa, file, null, &.none);
31 errdefer elf_file.deinit(gpa);
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);
3242
33 if (elf_file.dwarf == null) return error.MissingDebugInfo;
34 try elf_file.dwarf.?.open(gpa, elf_file.endian);
35 try elf_file.dwarf.?.populateRanges(gpa, elf_file.endian);
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);
3651
37 var info: Info = .{
38 .address_map = .{},
39 .coverage = coverage,
40 };
41 try info.address_map.put(gpa, 0, elf_file);
42 errdefer comptime unreachable; // elf_file is owned by the map now
43 return info;
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 }
4462}
4563
4664pub fn deinit(info: *Info, gpa: Allocator) void {
47 for (info.address_map.values()) |*elf_file| {
48 elf_file.dwarf.?.deinit(gpa);
65 switch (info.impl) {
66 .elf => |*ef| ef.deinit(gpa),
67 .macho => |*mf| mf.deinit(gpa),
4968 }
50 info.address_map.deinit(gpa);
5169 info.* = undefined;
5270}
5371
54pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError;
72pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError || error{UnsupportedDebugInfo};
5573
5674/// Given an array of virtual memory addresses, sorted ascending, outputs a
5775/// corresponding array of source locations.
......@@ -64,7 +82,28 @@ pub fn resolveAddresses(
6482 output: []SourceLocation,
6583) ResolveAddressesError!void {
6684 assert(sorted_pc_addrs.len == output.len);
67 if (info.address_map.entries.len != 1) @panic("TODO");
68 const elf_file = &info.address_map.values()[0];
69 return info.coverage.resolveAddressesDwarf(gpa, elf_file.endian, sorted_pc_addrs, output, &elf_file.dwarf.?);
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 }
70109}
tools/dump-cov.zig+27-8
......@@ -8,31 +8,50 @@ const assert = std.debug.assert;
88const SeenPcsHeader = std.Build.abi.fuzz.SeenPcsHeader;
99
1010pub fn main() !void {
11 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
12 defer _ = general_purpose_allocator.deinit();
13 const gpa = general_purpose_allocator.allocator();
11 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
12 defer _ = debug_allocator.deinit();
13 const gpa = debug_allocator.allocator();
1414
15 var arena_instance = std.heap.ArenaAllocator.init(gpa);
15 var arena_instance: std.heap.ArenaAllocator = .init(gpa);
1616 defer arena_instance.deinit();
1717 const arena = arena_instance.allocator();
1818
19 var threaded: std.Io.Threaded = .init(gpa);
20 defer threaded.deinit();
21 const io = threaded.io();
22
1923 const args = try std.process.argsAlloc(arena);
24
25 const target_query_str = switch (args.len) {
26 3 => "native",
27 4 => args[3],
28 else => return fatal(
29 \\usage: {0s} path/to/exe path/to/coverage [target]
30 \\ if omitted, 'target' defaults to 'native'
31 \\ example: {0s} zig-out/test .zig-cache/v/xxxxxxxx x86_64-linux
32 , .{if (args.len == 0) "dump-cov" else args[0]}),
33 };
34
35 const target = std.zig.resolveTargetQueryOrFatal(io, try .parse(.{
36 .arch_os_abi = target_query_str,
37 }));
38
2039 const exe_file_name = args[1];
2140 const cov_file_name = args[2];
2241
2342 const exe_path: Path = .{
24 .root_dir = std.Build.Cache.Directory.cwd(),
43 .root_dir = .cwd(),
2544 .sub_path = exe_file_name,
2645 };
2746 const cov_path: Path = .{
28 .root_dir = std.Build.Cache.Directory.cwd(),
47 .root_dir = .cwd(),
2948 .sub_path = cov_file_name,
3049 };
3150
32 var coverage = std.debug.Coverage.init;
51 var coverage: std.debug.Coverage = .init;
3352 defer coverage.deinit(gpa);
3453
35 var debug_info = std.debug.Info.load(gpa, exe_path, &coverage) catch |err| {
54 var debug_info = std.debug.Info.load(gpa, exe_path, &coverage, target.ofmt, target.cpu.arch) catch |err| {
3655 fatal("failed to load debug info for {f}: {s}", .{ exe_path, @errorName(err) });
3756 };
3857 defer debug_info.deinit(gpa);