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...@@ -383,7 +383,14 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO
383 errdefer gop.value_ptr.coverage.deinit(fuzz.gpa);383 errdefer gop.value_ptr.coverage.deinit(fuzz.gpa);
384384
385 const rebuilt_exe_path = run_step.rebuilt_executable.?;385 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| {
387 log.err("step '{s}': failed to load debug information for '{f}': {s}", .{394 log.err("step '{s}': failed to load debug information for '{f}': {s}", .{
388 run_step.step.name, rebuilt_exe_path, @errorName(err),395 run_step.step.name, rebuilt_exe_path, @errorName(err),
389 });396 });
...@@ -479,9 +486,23 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte...@@ -479,9 +486,23 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte
479 if (false) {486 if (false) {
480 const sl = coverage_map.source_locations[index];487 const sl = coverage_map.source_locations[index];
481 const file_name = coverage_map.coverage.stringAt(coverage_map.coverage.fileAt(sl.file).basename);488 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}", .{489 if (pcs.len == 1) {
483 addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1],490 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 (final)", .{
484 });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 }
485 }506 }
486 try coverage_map.entry_points.append(fuzz.gpa, @intCast(index));507 try coverage_map.entry_points.append(fuzz.gpa, @intCast(index));
487}508}
lib/std/debug/Info.zig+65-26
...@@ -9,49 +9,67 @@...@@ -9,49 +9,67 @@
9const std = @import("../std.zig");9const std = @import("../std.zig");
10const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
11const Path = std.Build.Cache.Path;11const Path = std.Build.Cache.Path;
12const ElfFile = std.debug.ElfFile;
13const assert = std.debug.assert;12const assert = std.debug.assert;
14const Coverage = std.debug.Coverage;13const Coverage = std.debug.Coverage;
15const SourceLocation = std.debug.Coverage.SourceLocation;14const SourceLocation = std.debug.Coverage.SourceLocation;
1615
16const ElfFile = std.debug.ElfFile;
17const MachOFile = std.debug.MachOFile;
18
17const Info = @This();19const Info = @This();
1820
19/// Sorted by key, ascending.21impl: union(enum) {
20address_map: std.AutoArrayHashMapUnmanaged(u64, ElfFile),22 elf: ElfFile,
23 macho: MachOFile,
24},
21/// Externally managed, outlives this `Info` instance.25/// Externally managed, outlives this `Info` instance.
22coverage: *Coverage,26coverage: *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 {36 var elf_file: ElfFile = try .load(gpa, file, null, &.none);
27 var file = try path.root_dir.handle.openFile(path.sub_path, .{});37 errdefer elf_file.deinit(gpa);
28 defer file.close();
2938
30 var elf_file: ElfFile = try .load(gpa, file, null, &.none);39 if (elf_file.dwarf == null) return error.MissingDebugInfo;
31 errdefer elf_file.deinit(gpa);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;43 return .{
34 try elf_file.dwarf.?.open(gpa, elf_file.endian);44 .impl = .{ .elf = elf_file },
35 try elf_file.dwarf.?.populateRanges(gpa, elf_file.endian);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 = .{52 var macho_file: MachOFile = try .load(gpa, path_str, arch);
38 .address_map = .{},53 errdefer macho_file.deinit(gpa);
39 .coverage = coverage,54
40 };55 return .{
41 try info.address_map.put(gpa, 0, elf_file);56 .impl = .{ .macho = macho_file },
42 errdefer comptime unreachable; // elf_file is owned by the map now57 .coverage = coverage,
43 return info;58 };
59 },
60 else => return error.UnsupportedDebugInfo,
61 }
44}62}
4563
46pub fn deinit(info: *Info, gpa: Allocator) void {64pub fn deinit(info: *Info, gpa: Allocator) void {
47 for (info.address_map.values()) |*elf_file| {65 switch (info.impl) {
48 elf_file.dwarf.?.deinit(gpa);66 .elf => |*ef| ef.deinit(gpa),
67 .macho => |*mf| mf.deinit(gpa),
49 }68 }
50 info.address_map.deinit(gpa);
51 info.* = undefined;69 info.* = undefined;
52}70}
5371
54pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError;72pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError || error{UnsupportedDebugInfo};
5573
56/// Given an array of virtual memory addresses, sorted ascending, outputs a74/// Given an array of virtual memory addresses, sorted ascending, outputs a
57/// corresponding array of source locations.75/// corresponding array of source locations.
...@@ -64,7 +82,28 @@ pub fn resolveAddresses(...@@ -64,7 +82,28 @@ pub fn resolveAddresses(
64 output: []SourceLocation,82 output: []SourceLocation,
65) ResolveAddressesError!void {83) ResolveAddressesError!void {
66 assert(sorted_pc_addrs.len == output.len);84 assert(sorted_pc_addrs.len == output.len);
67 if (info.address_map.entries.len != 1) @panic("TODO");85 switch (info.impl) {
68 const elf_file = &info.address_map.values()[0];86 .elf => |*ef| return info.coverage.resolveAddressesDwarf(gpa, ef.endian, sorted_pc_addrs, output, &ef.dwarf.?),
69 return info.coverage.resolveAddressesDwarf(gpa, elf_file.endian, sorted_pc_addrs, output, &elf_file.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 }
70}109}
tools/dump-cov.zig+27-8
...@@ -8,31 +8,50 @@ const assert = std.debug.assert;...@@ -8,31 +8,50 @@ const assert = std.debug.assert;
8const SeenPcsHeader = std.Build.abi.fuzz.SeenPcsHeader;8const SeenPcsHeader = std.Build.abi.fuzz.SeenPcsHeader;
99
10pub fn main() !void {10pub fn main() !void {
11 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;11 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
12 defer _ = general_purpose_allocator.deinit();12 defer _ = debug_allocator.deinit();
13 const gpa = general_purpose_allocator.allocator();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);
16 defer arena_instance.deinit();16 defer arena_instance.deinit();
17 const arena = arena_instance.allocator();17 const arena = arena_instance.allocator();
1818
19 var threaded: std.Io.Threaded = .init(gpa);
20 defer threaded.deinit();
21 const io = threaded.io();
22
19 const args = try std.process.argsAlloc(arena);23 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
20 const exe_file_name = args[1];39 const exe_file_name = args[1];
21 const cov_file_name = args[2];40 const cov_file_name = args[2];
2241
23 const exe_path: Path = .{42 const exe_path: Path = .{
24 .root_dir = std.Build.Cache.Directory.cwd(),43 .root_dir = .cwd(),
25 .sub_path = exe_file_name,44 .sub_path = exe_file_name,
26 };45 };
27 const cov_path: Path = .{46 const cov_path: Path = .{
28 .root_dir = std.Build.Cache.Directory.cwd(),47 .root_dir = .cwd(),
29 .sub_path = cov_file_name,48 .sub_path = cov_file_name,
30 };49 };
3150
32 var coverage = std.debug.Coverage.init;51 var coverage: std.debug.Coverage = .init;
33 defer coverage.deinit(gpa);52 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| {
36 fatal("failed to load debug info for {f}: {s}", .{ exe_path, @errorName(err) });55 fatal("failed to load debug info for {f}: {s}", .{ exe_path, @errorName(err) });
37 };56 };
38 defer debug_info.deinit(gpa);57 defer debug_info.deinit(gpa);