authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-02 17:45:31-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-07 00:48:32-07:00
logde47acd732dca8b4d2f2b3559307f488ccac940d
tree06bba0e41326343b49bee8f164b03f791c8c7f21
parent2e12b45d8b43d69e144887df4b04a2d383ff25d4

code coverage dumping tool basic implementation

* std.debug.Dwarf: add `sortCompileUnits` along with a field to track the state for the purpose of assertions and correct API usage. This makes batch lookups faster. - in the future, findCompileUnit should be enhanced to rely on sorted compile units as well. * implement `std.debug.Dwarf.resolveSourceLocations` as well as `std.debug.Info.resolveSourceLocations`. It's still pretty slow, since it calls getLineNumberInfo for each array element, repeating a lot of work unnecessarily. * integrate these APIs with `std.Progress` to understand what is taking so long. The output I'm seeing from this tool shows a lot of missing source locations. In particular, the main area of interest is missing for my tokenizer fuzzing example.

4 files changed, 102 insertions(+), 16 deletions(-)

lib/std/debug.zig+6
...@@ -27,6 +27,12 @@ pub const SourceLocation = struct {...@@ -27,6 +27,12 @@ pub const SourceLocation = struct {
27 line: u64,27 line: u64,
28 column: u64,28 column: u64,
29 file_name: []const u8,29 file_name: []const u8,
30
31 pub const invalid: SourceLocation = .{
32 .line = 0,
33 .column = 0,
34 .file_name = &.{},
35 };
30};36};
3137
32pub const Symbol = struct {38pub const Symbol = struct {
lib/std/debug/Dwarf.zig+78-7
...@@ -39,6 +39,7 @@ pub const call_frame = @import("Dwarf/call_frame.zig");...@@ -39,6 +39,7 @@ pub const call_frame = @import("Dwarf/call_frame.zig");
39endian: std.builtin.Endian,39endian: std.builtin.Endian,
40sections: SectionArray = null_section_array,40sections: SectionArray = null_section_array,
41is_macho: bool,41is_macho: bool,
42compile_units_sorted: bool,
4243
43// Filled later by the initializer44// Filled later by the initializer
44abbrev_table_list: std.ArrayListUnmanaged(Abbrev.Table) = .{},45abbrev_table_list: std.ArrayListUnmanaged(Abbrev.Table) = .{},
...@@ -728,9 +729,9 @@ pub const OpenError = ScanError;...@@ -728,9 +729,9 @@ pub const OpenError = ScanError;
728/// Initialize DWARF info. The caller has the responsibility to initialize most729/// Initialize DWARF info. The caller has the responsibility to initialize most
729/// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the730/// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the
730/// main binary file (not the secondary debug info file).731/// main binary file (not the secondary debug info file).
731pub fn open(di: *Dwarf, gpa: Allocator) OpenError!void {732pub fn open(d: *Dwarf, gpa: Allocator) OpenError!void {
732 try di.scanAllFunctions(gpa);733 try d.scanAllFunctions(gpa);
733 try di.scanAllCompileUnits(gpa);734 try d.scanAllCompileUnits(gpa);
734}735}
735736
736const PcRange = struct {737const PcRange = struct {
...@@ -1061,6 +1062,39 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {...@@ -1061,6 +1062,39 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
1061 }1062 }
1062}1063}
10631064
1065/// Populate missing PC ranges in compilation units, and then sort them by start address.
1066/// Does not guarantee pc_range to be non-null because there could be missing debug info.
1067pub fn sortCompileUnits(d: *Dwarf) ScanError!void {
1068 assert(!d.compile_units_sorted);
1069
1070 for (d.compile_unit_list.items) |*cu| {
1071 if (cu.pc_range != null) continue;
1072 const ranges_value = cu.die.getAttr(AT.ranges) orelse continue;
1073 var iter = DebugRangeIterator.init(ranges_value, d, cu) catch continue;
1074 var start: u64 = maxInt(u64);
1075 var end: u64 = 0;
1076 while (try iter.next()) |range| {
1077 start = @min(start, range.start_addr);
1078 end = @max(end, range.end_addr);
1079 }
1080 if (end != 0) cu.pc_range = .{
1081 .start = start,
1082 .end = end,
1083 };
1084 }
1085
1086 std.mem.sortUnstable(CompileUnit, d.compile_unit_list.items, {}, struct {
1087 fn lessThan(ctx: void, a: CompileUnit, b: CompileUnit) bool {
1088 _ = ctx;
1089 const a_range = a.pc_range orelse return false;
1090 const b_range = b.pc_range orelse return true;
1091 return a_range.start < b_range.start;
1092 }
1093 }.lessThan);
1094
1095 d.compile_units_sorted = true;
1096}
1097
1064const DebugRangeIterator = struct {1098const DebugRangeIterator = struct {
1065 base_address: u64,1099 base_address: u64,
1066 section_type: Section.Id,1100 section_type: Section.Id,
...@@ -1208,6 +1242,7 @@ const DebugRangeIterator = struct {...@@ -1208,6 +1242,7 @@ const DebugRangeIterator = struct {
1208 }1242 }
1209};1243};
12101244
1245/// TODO: change this to binary searching the sorted compile unit list
1211pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*const CompileUnit {1246pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*const CompileUnit {
1212 for (di.compile_unit_list.items) |*compile_unit| {1247 for (di.compile_unit_list.items) |*compile_unit| {
1213 if (compile_unit.pc_range) |range| {1248 if (compile_unit.pc_range) |range| {
...@@ -2275,6 +2310,7 @@ pub const ElfModule = struct {...@@ -2275,6 +2310,7 @@ pub const ElfModule = struct {
2275 .endian = endian,2310 .endian = endian,
2276 .sections = sections,2311 .sections = sections,
2277 .is_macho = false,2312 .is_macho = false,
2313 .compile_units_sorted = false,
2278 };2314 };
22792315
2280 try Dwarf.open(&di, gpa);2316 try Dwarf.open(&di, gpa);
...@@ -2326,6 +2362,8 @@ pub const ElfModule = struct {...@@ -2326,6 +2362,8 @@ pub const ElfModule = struct {
2326 }2362 }
2327};2363};
23282364
2365pub const ResolveSourceLocationsError = Allocator.Error || DeprecatedFixedBufferReader.Error;
2366
2329/// Given an array of virtual memory addresses, sorted ascending, outputs a2367/// Given an array of virtual memory addresses, sorted ascending, outputs a
2330/// corresponding array of source locations, by appending to the provided2368/// corresponding array of source locations, by appending to the provided
2331/// array list.2369/// array list.
...@@ -2335,11 +2373,44 @@ pub fn resolveSourceLocations(...@@ -2335,11 +2373,44 @@ pub fn resolveSourceLocations(
2335 sorted_pc_addrs: []const u64,2373 sorted_pc_addrs: []const u64,
2336 /// Asserts its length equals length of `sorted_pc_addrs`.2374 /// Asserts its length equals length of `sorted_pc_addrs`.
2337 output: []std.debug.SourceLocation,2375 output: []std.debug.SourceLocation,
2338) error{ MissingDebugInfo, InvalidDebugInfo }!void {2376 parent_prog_node: std.Progress.Node,
2377) ResolveSourceLocationsError!void {
2339 assert(sorted_pc_addrs.len == output.len);2378 assert(sorted_pc_addrs.len == output.len);
2340 _ = d;2379 assert(d.compile_units_sorted);
2341 _ = gpa;2380
2342 @panic("TODO");2381 const prog_node = parent_prog_node.start("Resolve Source Locations", sorted_pc_addrs.len);
2382 defer prog_node.end();
2383
2384 var cu_i: usize = 0;
2385 var cu: *const CompileUnit = &d.compile_unit_list.items[0];
2386 var range = cu.pc_range.?;
2387 next_pc: for (sorted_pc_addrs, output) |pc, *out| {
2388 defer prog_node.completeOne();
2389 while (pc >= range.end) {
2390 cu_i += 1;
2391 if (cu_i >= d.compile_unit_list.items.len) {
2392 out.* = std.debug.SourceLocation.invalid;
2393 continue :next_pc;
2394 }
2395 cu = &d.compile_unit_list.items[cu_i];
2396 range = cu.pc_range orelse {
2397 out.* = std.debug.SourceLocation.invalid;
2398 continue :next_pc;
2399 };
2400 }
2401 if (pc < range.start) {
2402 out.* = std.debug.SourceLocation.invalid;
2403 continue :next_pc;
2404 }
2405 // TODO: instead of calling this function, break the function up into one that parses the
2406 // information once and prepares a context that can be reused for the entire batch.
2407 if (getLineNumberInfo(d, gpa, cu.*, pc)) |src_loc| {
2408 out.* = src_loc;
2409 } else |err| switch (err) {
2410 error.MissingDebugInfo, error.InvalidDebugInfo => out.* = std.debug.SourceLocation.invalid,
2411 else => |e| return e,
2412 }
2413 }
2343}2414}
23442415
2345fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol {2416fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol {
lib/std/debug/Info.zig+10-7
...@@ -20,9 +20,14 @@ address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule),...@@ -20,9 +20,14 @@ address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule),
2020
21pub const LoadError = Dwarf.ElfModule.LoadError;21pub const LoadError = Dwarf.ElfModule.LoadError;
2222
23pub fn load(gpa: Allocator, path: Path) LoadError!Info {23pub fn load(gpa: Allocator, path: Path, parent_prog_node: std.Progress.Node) LoadError!Info {
24 var sections: Dwarf.SectionArray = Dwarf.null_section_array;24 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
25 const elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, &sections, null);25 var prog_node = parent_prog_node.start("Loading Debug Info", 0);
26 defer prog_node.end();
27 var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, &sections, null);
28 prog_node.end();
29 prog_node = parent_prog_node.start("Sort Compile Units", 0);
30 try elf_module.dwarf.sortCompileUnits();
26 var info: Info = .{31 var info: Info = .{
27 .address_map = .{},32 .address_map = .{},
28 };33 };
...@@ -38,10 +43,7 @@ pub fn deinit(info: *Info, gpa: Allocator) void {...@@ -38,10 +43,7 @@ pub fn deinit(info: *Info, gpa: Allocator) void {
38 info.* = undefined;43 info.* = undefined;
39}44}
4045
41pub const ResolveSourceLocationsError = error{46pub const ResolveSourceLocationsError = Dwarf.ResolveSourceLocationsError;
42 MissingDebugInfo,
43 InvalidDebugInfo,
44} || Allocator.Error;
4547
46pub fn resolveSourceLocations(48pub fn resolveSourceLocations(
47 info: *Info,49 info: *Info,
...@@ -49,9 +51,10 @@ pub fn resolveSourceLocations(...@@ -49,9 +51,10 @@ pub fn resolveSourceLocations(
49 sorted_pc_addrs: []const u64,51 sorted_pc_addrs: []const u64,
50 /// Asserts its length equals length of `sorted_pc_addrs`.52 /// Asserts its length equals length of `sorted_pc_addrs`.
51 output: []std.debug.SourceLocation,53 output: []std.debug.SourceLocation,
54 parent_prog_node: std.Progress.Node,
52) ResolveSourceLocationsError!void {55) ResolveSourceLocationsError!void {
53 assert(sorted_pc_addrs.len == output.len);56 assert(sorted_pc_addrs.len == output.len);
54 if (info.address_map.entries.len != 1) @panic("TODO");57 if (info.address_map.entries.len != 1) @panic("TODO");
55 const elf_module = &info.address_map.values()[0];58 const elf_module = &info.address_map.values()[0];
56 return elf_module.dwarf.resolveSourceLocations(gpa, sorted_pc_addrs, output);59 return elf_module.dwarf.resolveSourceLocations(gpa, sorted_pc_addrs, output, parent_prog_node);
57}60}
tools/dump-cov.zig+8-2
...@@ -28,7 +28,10 @@ pub fn main() !void {...@@ -28,7 +28,10 @@ pub fn main() !void {
28 .sub_path = cov_file_name,28 .sub_path = cov_file_name,
29 };29 };
3030
31 var debug_info = std.debug.Info.load(gpa, exe_path) catch |err| {31 const prog_node = std.Progress.start(.{});
32 defer prog_node.end();
33
34 var debug_info = std.debug.Info.load(gpa, exe_path, prog_node) catch |err| {
32 fatal("failed to load debug info for {}: {s}", .{ exe_path, @errorName(err) });35 fatal("failed to load debug info for {}: {s}", .{ exe_path, @errorName(err) });
33 };36 };
34 defer debug_info.deinit(gpa);37 defer debug_info.deinit(gpa);
...@@ -51,7 +54,10 @@ pub fn main() !void {...@@ -51,7 +54,10 @@ pub fn main() !void {
51 assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize)));54 assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize)));
5255
53 const source_locations = try arena.alloc(std.debug.SourceLocation, pcs.len);56 const source_locations = try arena.alloc(std.debug.SourceLocation, pcs.len);
54 try debug_info.resolveSourceLocations(gpa, pcs, source_locations);57 try debug_info.resolveSourceLocations(gpa, pcs, source_locations, prog_node);
58 defer for (source_locations) |sl| {
59 gpa.free(sl.file_name);
60 };
5561
56 for (pcs, source_locations) |pc, sl| {62 for (pcs, source_locations) |pc, sl| {
57 try stdout.print("{x}: {s}:{d}:{d}\n", .{63 try stdout.print("{x}: {s}:{d}:{d}\n", .{