authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-03 17:42:08-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-07 00:48:32-07:00
log53aa9d75a9b10c9cd277031e604a631452d34e8c
tree0ce4bab5f79ab7293917b5984cd8c59e9f93283e
parentc2ab4614b69a2303d640837df357c2336b0cedf2

std.debug.Info.resolveSourceLocations: O(N) implementation


3 files changed, 157 insertions(+), 59 deletions(-)

lib/std/debug/Dwarf.zig+9-50
......@@ -152,6 +152,7 @@ pub const CompileUnit = struct {
152152 pub const LineEntry = struct {
153153 line: u32,
154154 column: u32,
155 /// Offset by 1 depending on whether Dwarf version is >= 5.
155156 file: u32,
156157 };
157158
......@@ -809,7 +810,7 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 {
809810 return null;
810811}
811812
812const ScanError = error{
813pub const ScanError = error{
813814 InvalidDebugInfo,
814815 MissingDebugInfo,
815816} || Allocator.Error || std.debug.FixedBufferReader.Error;
......@@ -1113,7 +1114,7 @@ pub fn sortCompileUnits(d: *Dwarf) ScanError!void {
11131114 }
11141115
11151116 std.mem.sortUnstable(CompileUnit, d.compile_unit_list.items, {}, struct {
1116 fn lessThan(ctx: void, a: CompileUnit, b: CompileUnit) bool {
1117 pub fn lessThan(ctx: void, a: CompileUnit, b: CompileUnit) bool {
11171118 _ = ctx;
11181119 const a_range = a.pc_range orelse return false;
11191120 const b_range = b.pc_range orelse return true;
......@@ -1641,14 +1642,18 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
16411642 };
16421643}
16431644
1645pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, cu: *CompileUnit) ScanError!void {
1646 if (cu.src_loc_cache != null) return;
1647 cu.src_loc_cache = try runLineNumberProgram(d, gpa, cu);
1648}
1649
16441650pub fn getLineNumberInfo(
16451651 d: *Dwarf,
16461652 gpa: Allocator,
16471653 compile_unit: *CompileUnit,
16481654 target_address: u64,
16491655) !std.debug.SourceLocation {
1650 if (compile_unit.src_loc_cache == null)
1651 compile_unit.src_loc_cache = try runLineNumberProgram(d, gpa, compile_unit);
1656 try populateSrcLocCache(d, gpa, compile_unit);
16521657 const slc = &compile_unit.src_loc_cache.?;
16531658 const entry = try slc.findSource(target_address);
16541659 const file_index = entry.file - @intFromBool(slc.version < 5);
......@@ -2343,52 +2348,6 @@ pub const ElfModule = struct {
23432348 }
23442349};
23452350
2346pub const ResolveSourceLocationsError = Allocator.Error || FixedBufferReader.Error;
2347
2348/// Given an array of virtual memory addresses, sorted ascending, outputs a
2349/// corresponding array of source locations, by appending to the provided
2350/// array list.
2351pub fn resolveSourceLocations(
2352 d: *Dwarf,
2353 gpa: Allocator,
2354 sorted_pc_addrs: []const u64,
2355 /// Asserts its length equals length of `sorted_pc_addrs`.
2356 output: []std.debug.SourceLocation,
2357) ResolveSourceLocationsError!void {
2358 assert(sorted_pc_addrs.len == output.len);
2359 assert(d.compile_units_sorted);
2360
2361 var cu_i: usize = 0;
2362 var cu: *CompileUnit = &d.compile_unit_list.items[0];
2363 var range = cu.pc_range.?;
2364 next_pc: for (sorted_pc_addrs, output) |pc, *out| {
2365 while (pc >= range.end) {
2366 cu_i += 1;
2367 if (cu_i >= d.compile_unit_list.items.len) {
2368 out.* = std.debug.SourceLocation.invalid;
2369 continue :next_pc;
2370 }
2371 cu = &d.compile_unit_list.items[cu_i];
2372 range = cu.pc_range orelse {
2373 out.* = std.debug.SourceLocation.invalid;
2374 continue :next_pc;
2375 };
2376 }
2377 if (pc < range.start) {
2378 out.* = std.debug.SourceLocation.invalid;
2379 continue :next_pc;
2380 }
2381 // TODO: instead of calling this function, break the function up into one that parses the
2382 // information once and prepares a context that can be reused for the entire batch.
2383 if (getLineNumberInfo(d, gpa, cu, pc)) |src_loc| {
2384 out.* = src_loc;
2385 } else |err| switch (err) {
2386 error.MissingDebugInfo, error.InvalidDebugInfo => out.* = std.debug.SourceLocation.invalid,
2387 else => |e| return e,
2388 }
2389 }
2390}
2391
23922351fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol {
23932352 if (di.findCompileUnit(address)) |compile_unit| {
23942353 return .{
lib/std/debug/Info.zig+143-3
......@@ -12,12 +12,66 @@ const Path = std.Build.Cache.Path;
1212const Dwarf = std.debug.Dwarf;
1313const page_size = std.mem.page_size;
1414const assert = std.debug.assert;
15const Hash = std.hash.Wyhash;
1516
1617const Info = @This();
1718
1819/// Sorted by key, ascending.
1920address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule),
2021
22/// Provides a globally-scoped integer index for directories.
23///
24/// As opposed to, for example, a directory index that is compilation-unit
25/// scoped inside a single ELF module.
26///
27/// String memory references the memory-mapped debug information.
28///
29/// Protected by `mutex`.
30directories: std.StringArrayHashMapUnmanaged(void),
31/// Provides a globally-scoped integer index for files.
32///
33/// String memory references the memory-mapped debug information.
34///
35/// Protected by `mutex`.
36files: std.ArrayHashMapUnmanaged(File, void, File.MapContext, false),
37/// Protects `directories` and `files`.
38mutex: std.Thread.Mutex,
39
40pub const SourceLocation = struct {
41 file: File.Index,
42 line: u32,
43 column: u32,
44
45 pub const invalid: SourceLocation = .{
46 .file = .invalid,
47 .line = 0,
48 .column = 0,
49 };
50};
51
52pub const File = struct {
53 directory_index: u32,
54 basename: []const u8,
55
56 pub const Index = enum(u32) {
57 invalid = std.math.maxInt(u32),
58 _,
59 };
60
61 pub const MapContext = struct {
62 pub fn hash(ctx: MapContext, a: File) u32 {
63 _ = ctx;
64 return @truncate(Hash.hash(a.directory_index, a.basename));
65 }
66
67 pub fn eql(ctx: MapContext, a: File, b: File, b_index: usize) bool {
68 _ = ctx;
69 _ = b_index;
70 return a.directory_index == b.directory_index and std.mem.eql(u8, a.basename, b.basename);
71 }
72 };
73};
74
2175pub const LoadError = Dwarf.ElfModule.LoadError;
2276
2377pub fn load(gpa: Allocator, path: Path) LoadError!Info {
......@@ -26,12 +80,17 @@ pub fn load(gpa: Allocator, path: Path) LoadError!Info {
2680 try elf_module.dwarf.sortCompileUnits();
2781 var info: Info = .{
2882 .address_map = .{},
83 .directories = .{},
84 .files = .{},
85 .mutex = .{},
2986 };
3087 try info.address_map.put(gpa, elf_module.base_address, elf_module);
3188 return info;
3289}
3390
3491pub fn deinit(info: *Info, gpa: Allocator) void {
92 info.directories.deinit(gpa);
93 info.files.deinit(gpa);
3594 for (info.address_map.values()) |*elf_module| {
3695 elf_module.dwarf.deinit(gpa);
3796 }
......@@ -39,17 +98,98 @@ pub fn deinit(info: *Info, gpa: Allocator) void {
3998 info.* = undefined;
4099}
41100
42pub const ResolveSourceLocationsError = Dwarf.ResolveSourceLocationsError;
101pub fn fileAt(info: *Info, index: File.Index) *File {
102 return &info.files.keys()[@intFromEnum(index)];
103}
104
105pub const ResolveSourceLocationsError = Dwarf.ScanError;
43106
107/// Given an array of virtual memory addresses, sorted ascending, outputs a
108/// corresponding array of source locations.
44109pub fn resolveSourceLocations(
45110 info: *Info,
46111 gpa: Allocator,
47112 sorted_pc_addrs: []const u64,
48113 /// Asserts its length equals length of `sorted_pc_addrs`.
49 output: []std.debug.SourceLocation,
114 output: []SourceLocation,
50115) ResolveSourceLocationsError!void {
51116 assert(sorted_pc_addrs.len == output.len);
52117 if (info.address_map.entries.len != 1) @panic("TODO");
53118 const elf_module = &info.address_map.values()[0];
54 return elf_module.dwarf.resolveSourceLocations(gpa, sorted_pc_addrs, output);
119 return resolveSourceLocationsDwarf(info, gpa, sorted_pc_addrs, output, &elf_module.dwarf);
120}
121
122pub fn resolveSourceLocationsDwarf(
123 info: *Info,
124 gpa: Allocator,
125 sorted_pc_addrs: []const u64,
126 /// Asserts its length equals length of `sorted_pc_addrs`.
127 output: []SourceLocation,
128 d: *Dwarf,
129) ResolveSourceLocationsError!void {
130 assert(sorted_pc_addrs.len == output.len);
131 assert(d.compile_units_sorted);
132
133 var cu_i: usize = 0;
134 var line_table_i: usize = 0;
135 var cu: *Dwarf.CompileUnit = &d.compile_unit_list.items[0];
136 var range = cu.pc_range.?;
137 // Protects directories and files tables from other threads.
138 info.mutex.lock();
139 defer info.mutex.unlock();
140 next_pc: for (sorted_pc_addrs, output) |pc, *out| {
141 while (pc >= range.end) {
142 cu_i += 1;
143 if (cu_i >= d.compile_unit_list.items.len) {
144 out.* = SourceLocation.invalid;
145 continue :next_pc;
146 }
147 cu = &d.compile_unit_list.items[cu_i];
148 line_table_i = 0;
149 range = cu.pc_range orelse {
150 out.* = SourceLocation.invalid;
151 continue :next_pc;
152 };
153 }
154 if (pc < range.start) {
155 out.* = SourceLocation.invalid;
156 continue :next_pc;
157 }
158 if (line_table_i == 0) {
159 line_table_i = 1;
160 info.mutex.unlock();
161 defer info.mutex.lock();
162 d.populateSrcLocCache(gpa, cu) catch |err| switch (err) {
163 error.MissingDebugInfo, error.InvalidDebugInfo => {
164 out.* = SourceLocation.invalid;
165 cu_i += 1;
166 if (cu_i < d.compile_unit_list.items.len) {
167 cu = &d.compile_unit_list.items[cu_i];
168 line_table_i = 0;
169 if (cu.pc_range) |r| range = r;
170 }
171 continue :next_pc;
172 },
173 else => |e| return e,
174 };
175 }
176 const slc = &cu.src_loc_cache.?;
177 const table_addrs = slc.line_table.keys();
178 while (line_table_i < table_addrs.len and table_addrs[line_table_i] < pc) line_table_i += 1;
179
180 const entry = slc.line_table.values()[line_table_i - 1];
181 const corrected_file_index = entry.file - @intFromBool(slc.version < 5);
182 const file_entry = slc.files[corrected_file_index];
183 const dir_path = slc.directories[file_entry.dir_index].path;
184 const dir_gop = try info.directories.getOrPut(gpa, dir_path);
185 const file_gop = try info.files.getOrPut(gpa, .{
186 .directory_index = @intCast(dir_gop.index),
187 .basename = file_entry.path,
188 });
189 out.* = .{
190 .file = @enumFromInt(file_gop.index),
191 .line = entry.line,
192 .column = entry.column,
193 };
194 }
55195}
tools/dump-cov.zig+5-6
......@@ -50,15 +50,14 @@ pub fn main() !void {
5050 }
5151 assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize)));
5252
53 const source_locations = try arena.alloc(std.debug.SourceLocation, pcs.len);
53 const source_locations = try arena.alloc(std.debug.Info.SourceLocation, pcs.len);
5454 try debug_info.resolveSourceLocations(gpa, pcs, source_locations);
55 defer for (source_locations) |sl| {
56 gpa.free(sl.file_name);
57 };
5855
5956 for (pcs, source_locations) |pc, sl| {
60 try stdout.print("{x}: {s}:{d}:{d}\n", .{
61 pc, sl.file_name, sl.line, sl.column,
57 const file = debug_info.fileAt(sl.file);
58 const dir_name = debug_info.directories.keys()[file.directory_index];
59 try stdout.print("{x}: {s}/{s}:{d}:{d}\n", .{
60 pc, dir_name, file.basename, sl.line, sl.column,
6261 });
6362 }
6463