| author | |
| committer | |
| log | b5398180d6b362346522a6067d54b90b97e23dc2 |
| tree | 227d414866a1052fd4197bb0b32e74e173300ef9 |
| parent | 0b5ea2b902b5802786cac70740e93872d2a0973d |
The implementation assumed that compilation units did not overlap, which
is not the case. The new implementation uses .debug_ranges to iterate
over the requested PCs.
This partially resolves #20990. The dump-cov tool is fixed but the same
fix needs to be applied to `std.Build.Fuzz.WebServer` (sorting the PC
list before passing it to be resolved by debug info).
I am observing LLVM emit multiple 8-bit counters for the same PC
addresses when enabling `-fsanitize-coverage=inline-8bit-counters`. This
seems like a bug in LLVM. I can't fathom why that would be desireable.5 files changed, 91 insertions(+), 76 deletions(-)
lib/std/debug/Coverage.zig+18-21| ... | ... | @@ -151,46 +151,35 @@ pub fn resolveAddressesDwarf( |
| 151 | 151 | d: *Dwarf, |
| 152 | 152 | ) ResolveAddressesDwarfError!void { |
| 153 | 153 | assert(sorted_pc_addrs.len == output.len); |
| 154 | assert(d.compile_units_sorted); | |
| 154 | assert(d.ranges.items.len != 0); // call `populateRanges` first. | |
| 155 | 155 | |
| 156 | var cu_i: usize = 0; | |
| 157 | var line_table_i: usize = 0; | |
| 158 | var cu: *Dwarf.CompileUnit = &d.compile_unit_list.items[0]; | |
| 159 | var range = cu.pc_range.?; | |
| 156 | var range_i: usize = 0; | |
| 157 | var range: *std.debug.Dwarf.Range = &d.ranges.items[0]; | |
| 158 | var line_table_i: usize = undefined; | |
| 159 | var prev_cu: ?*std.debug.Dwarf.CompileUnit = null; | |
| 160 | 160 | // Protects directories and files tables from other threads. |
| 161 | 161 | cov.mutex.lock(); |
| 162 | 162 | defer cov.mutex.unlock(); |
| 163 | 163 | next_pc: for (sorted_pc_addrs, output) |pc, *out| { |
| 164 | 164 | while (pc >= range.end) { |
| 165 | cu_i += 1; | |
| 166 | if (cu_i >= d.compile_unit_list.items.len) { | |
| 165 | range_i += 1; | |
| 166 | if (range_i >= d.ranges.items.len) { | |
| 167 | 167 | out.* = SourceLocation.invalid; |
| 168 | 168 | continue :next_pc; |
| 169 | 169 | } |
| 170 | cu = &d.compile_unit_list.items[cu_i]; | |
| 171 | line_table_i = 0; | |
| 172 | range = cu.pc_range orelse { | |
| 173 | out.* = SourceLocation.invalid; | |
| 174 | continue :next_pc; | |
| 175 | }; | |
| 170 | range = &d.ranges.items[range_i]; | |
| 176 | 171 | } |
| 177 | 172 | if (pc < range.start) { |
| 178 | 173 | out.* = SourceLocation.invalid; |
| 179 | 174 | continue :next_pc; |
| 180 | 175 | } |
| 181 | if (line_table_i == 0) { | |
| 182 | line_table_i = 1; | |
| 176 | const cu = &d.compile_unit_list.items[range.compile_unit_index]; | |
| 177 | if (cu.src_loc_cache == null) { | |
| 183 | 178 | cov.mutex.unlock(); |
| 184 | 179 | defer cov.mutex.lock(); |
| 185 | 180 | d.populateSrcLocCache(gpa, cu) catch |err| switch (err) { |
| 186 | 181 | error.MissingDebugInfo, error.InvalidDebugInfo => { |
| 187 | 182 | out.* = SourceLocation.invalid; |
| 188 | cu_i += 1; | |
| 189 | if (cu_i < d.compile_unit_list.items.len) { | |
| 190 | cu = &d.compile_unit_list.items[cu_i]; | |
| 191 | line_table_i = 0; | |
| 192 | if (cu.pc_range) |r| range = r; | |
| 193 | } | |
| 194 | 183 | continue :next_pc; |
| 195 | 184 | }, |
| 196 | 185 | else => |e| return e, |
| ... | ... | @@ -198,6 +187,14 @@ pub fn resolveAddressesDwarf( |
| 198 | 187 | } |
| 199 | 188 | const slc = &cu.src_loc_cache.?; |
| 200 | 189 | const table_addrs = slc.line_table.keys(); |
| 190 | if (cu != prev_cu) { | |
| 191 | prev_cu = cu; | |
| 192 | line_table_i = std.sort.upperBound(u64, table_addrs, pc, struct { | |
| 193 | fn order(context: u64, item: u64) std.math.Order { | |
| 194 | return std.math.order(item, context); | |
| 195 | } | |
| 196 | }.order); | |
| 197 | } | |
| 201 | 198 | while (line_table_i < table_addrs.len and table_addrs[line_table_i] < pc) line_table_i += 1; |
| 202 | 199 | |
| 203 | 200 | const entry = slc.line_table.values()[line_table_i - 1]; |
lib/std/debug/Dwarf.zig+54-42| ... | ... | @@ -38,19 +38,30 @@ pub const call_frame = @import("Dwarf/call_frame.zig"); |
| 38 | 38 | endian: std.builtin.Endian, |
| 39 | 39 | sections: SectionArray = null_section_array, |
| 40 | 40 | is_macho: bool, |
| 41 | compile_units_sorted: bool, | |
| 42 | 41 | |
| 43 | // Filled later by the initializer | |
| 42 | /// Filled later by the initializer | |
| 44 | 43 | abbrev_table_list: std.ArrayListUnmanaged(Abbrev.Table) = .{}, |
| 44 | /// Filled later by the initializer | |
| 45 | 45 | compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .{}, |
| 46 | /// Filled later by the initializer | |
| 46 | 47 | func_list: std.ArrayListUnmanaged(Func) = .{}, |
| 47 | 48 | |
| 48 | 49 | eh_frame_hdr: ?ExceptionFrameHeader = null, |
| 49 | // These lookup tables are only used if `eh_frame_hdr` is null | |
| 50 | /// These lookup tables are only used if `eh_frame_hdr` is null | |
| 50 | 51 | cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .{}, |
| 51 | // Sorted by start_pc | |
| 52 | /// Sorted by start_pc | |
| 52 | 53 | fde_list: std.ArrayListUnmanaged(FrameDescriptionEntry) = .{}, |
| 53 | 54 | |
| 55 | /// Populated by `populateRanges`. | |
| 56 | ranges: std.ArrayListUnmanaged(Range) = .{}, | |
| 57 | ||
| 58 | pub const Range = struct { | |
| 59 | start: u64, | |
| 60 | end: u64, | |
| 61 | /// Index into `compile_unit_list`. | |
| 62 | compile_unit_index: usize, | |
| 63 | }; | |
| 64 | ||
| 54 | 65 | pub const Section = struct { |
| 55 | 66 | data: []const u8, |
| 56 | 67 | // Module-relative virtual address. |
| ... | ... | @@ -799,6 +810,7 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void { |
| 799 | 810 | di.func_list.deinit(gpa); |
| 800 | 811 | di.cie_map.deinit(gpa); |
| 801 | 812 | di.fde_list.deinit(gpa); |
| 813 | di.ranges.deinit(gpa); | |
| 802 | 814 | di.* = undefined; |
| 803 | 815 | } |
| 804 | 816 | |
| ... | ... | @@ -985,8 +997,8 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 985 | 997 | try di.func_list.append(allocator, .{ |
| 986 | 998 | .name = fn_name, |
| 987 | 999 | .pc_range = .{ |
| 988 | .start = range.start_addr, | |
| 989 | .end = range.end_addr, | |
| 1000 | .start = range.start, | |
| 1001 | .end = range.end, | |
| 990 | 1002 | }, |
| 991 | 1003 | }); |
| 992 | 1004 | } |
| ... | ... | @@ -1096,37 +1108,38 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { |
| 1096 | 1108 | } |
| 1097 | 1109 | } |
| 1098 | 1110 | |
| 1099 | /// Populate missing PC ranges in compilation units, and then sort them by start address. | |
| 1100 | /// Does not guarantee pc_range to be non-null because there could be missing debug info. | |
| 1101 | pub fn sortCompileUnits(d: *Dwarf) ScanError!void { | |
| 1102 | assert(!d.compile_units_sorted); | |
| 1111 | pub fn populateRanges(d: *Dwarf, gpa: Allocator) ScanError!void { | |
| 1112 | assert(d.ranges.items.len == 0); | |
| 1103 | 1113 | |
| 1104 | for (d.compile_unit_list.items) |*cu| { | |
| 1105 | if (cu.pc_range != null) continue; | |
| 1114 | for (d.compile_unit_list.items, 0..) |*cu, cu_index| { | |
| 1115 | if (cu.pc_range) |range| { | |
| 1116 | try d.ranges.append(gpa, .{ | |
| 1117 | .start = range.start, | |
| 1118 | .end = range.end, | |
| 1119 | .compile_unit_index = cu_index, | |
| 1120 | }); | |
| 1121 | continue; | |
| 1122 | } | |
| 1106 | 1123 | const ranges_value = cu.die.getAttr(AT.ranges) orelse continue; |
| 1107 | 1124 | var iter = DebugRangeIterator.init(ranges_value, d, cu) catch continue; |
| 1108 | var start: u64 = maxInt(u64); | |
| 1109 | var end: u64 = 0; | |
| 1110 | 1125 | while (try iter.next()) |range| { |
| 1111 | start = @min(start, range.start_addr); | |
| 1112 | end = @max(end, range.end_addr); | |
| 1126 | // Not sure why LLVM thinks it's OK to emit these... | |
| 1127 | if (range.start == range.end) continue; | |
| 1128 | ||
| 1129 | try d.ranges.append(gpa, .{ | |
| 1130 | .start = range.start, | |
| 1131 | .end = range.end, | |
| 1132 | .compile_unit_index = cu_index, | |
| 1133 | }); | |
| 1113 | 1134 | } |
| 1114 | if (end != 0) cu.pc_range = .{ | |
| 1115 | .start = start, | |
| 1116 | .end = end, | |
| 1117 | }; | |
| 1118 | 1135 | } |
| 1119 | 1136 | |
| 1120 | std.mem.sortUnstable(CompileUnit, d.compile_unit_list.items, {}, struct { | |
| 1121 | pub fn lessThan(ctx: void, a: CompileUnit, b: CompileUnit) bool { | |
| 1137 | std.mem.sortUnstable(Range, d.ranges.items, {}, struct { | |
| 1138 | pub fn lessThan(ctx: void, a: Range, b: Range) bool { | |
| 1122 | 1139 | _ = ctx; |
| 1123 | const a_range = a.pc_range orelse return false; | |
| 1124 | const b_range = b.pc_range orelse return true; | |
| 1125 | return a_range.start < b_range.start; | |
| 1140 | return a.start < b.start; | |
| 1126 | 1141 | } |
| 1127 | 1142 | }.lessThan); |
| 1128 | ||
| 1129 | d.compile_units_sorted = true; | |
| 1130 | 1143 | } |
| 1131 | 1144 | |
| 1132 | 1145 | const DebugRangeIterator = struct { |
| ... | ... | @@ -1184,7 +1197,7 @@ const DebugRangeIterator = struct { |
| 1184 | 1197 | } |
| 1185 | 1198 | |
| 1186 | 1199 | // Returns the next range in the list, or null if the end was reached. |
| 1187 | pub fn next(self: *@This()) !?struct { start_addr: u64, end_addr: u64 } { | |
| 1200 | pub fn next(self: *@This()) !?PcRange { | |
| 1188 | 1201 | switch (self.section_type) { |
| 1189 | 1202 | .debug_rnglists => { |
| 1190 | 1203 | const kind = try self.fbr.readByte(); |
| ... | ... | @@ -1203,8 +1216,8 @@ const DebugRangeIterator = struct { |
| 1203 | 1216 | const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index); |
| 1204 | 1217 | |
| 1205 | 1218 | return .{ |
| 1206 | .start_addr = start_addr, | |
| 1207 | .end_addr = end_addr, | |
| 1219 | .start = start_addr, | |
| 1220 | .end = end_addr, | |
| 1208 | 1221 | }; |
| 1209 | 1222 | }, |
| 1210 | 1223 | RLE.startx_length => { |
| ... | ... | @@ -1215,8 +1228,8 @@ const DebugRangeIterator = struct { |
| 1215 | 1228 | const end_addr = start_addr + len; |
| 1216 | 1229 | |
| 1217 | 1230 | return .{ |
| 1218 | .start_addr = start_addr, | |
| 1219 | .end_addr = end_addr, | |
| 1231 | .start = start_addr, | |
| 1232 | .end = end_addr, | |
| 1220 | 1233 | }; |
| 1221 | 1234 | }, |
| 1222 | 1235 | RLE.offset_pair => { |
| ... | ... | @@ -1225,8 +1238,8 @@ const DebugRangeIterator = struct { |
| 1225 | 1238 | |
| 1226 | 1239 | // This is the only kind that uses the base address |
| 1227 | 1240 | return .{ |
| 1228 | .start_addr = self.base_address + start_addr, | |
| 1229 | .end_addr = self.base_address + end_addr, | |
| 1241 | .start = self.base_address + start_addr, | |
| 1242 | .end = self.base_address + end_addr, | |
| 1230 | 1243 | }; |
| 1231 | 1244 | }, |
| 1232 | 1245 | RLE.base_address => { |
| ... | ... | @@ -1238,8 +1251,8 @@ const DebugRangeIterator = struct { |
| 1238 | 1251 | const end_addr = try self.fbr.readInt(usize); |
| 1239 | 1252 | |
| 1240 | 1253 | return .{ |
| 1241 | .start_addr = start_addr, | |
| 1242 | .end_addr = end_addr, | |
| 1254 | .start = start_addr, | |
| 1255 | .end = end_addr, | |
| 1243 | 1256 | }; |
| 1244 | 1257 | }, |
| 1245 | 1258 | RLE.start_length => { |
| ... | ... | @@ -1248,8 +1261,8 @@ const DebugRangeIterator = struct { |
| 1248 | 1261 | const end_addr = start_addr + len; |
| 1249 | 1262 | |
| 1250 | 1263 | return .{ |
| 1251 | .start_addr = start_addr, | |
| 1252 | .end_addr = end_addr, | |
| 1264 | .start = start_addr, | |
| 1265 | .end = end_addr, | |
| 1253 | 1266 | }; |
| 1254 | 1267 | }, |
| 1255 | 1268 | else => return bad(), |
| ... | ... | @@ -1267,8 +1280,8 @@ const DebugRangeIterator = struct { |
| 1267 | 1280 | } |
| 1268 | 1281 | |
| 1269 | 1282 | return .{ |
| 1270 | .start_addr = self.base_address + start_addr, | |
| 1271 | .end_addr = self.base_address + end_addr, | |
| 1283 | .start = self.base_address + start_addr, | |
| 1284 | .end = self.base_address + end_addr, | |
| 1272 | 1285 | }; |
| 1273 | 1286 | }, |
| 1274 | 1287 | else => unreachable, |
| ... | ... | @@ -1286,7 +1299,7 @@ pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*CompileUnit { |
| 1286 | 1299 | const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue; |
| 1287 | 1300 | var iter = DebugRangeIterator.init(ranges_value, di, compile_unit) catch continue; |
| 1288 | 1301 | while (try iter.next()) |range| { |
| 1289 | if (target_address >= range.start_addr and target_address < range.end_addr) return compile_unit; | |
| 1302 | if (target_address >= range.start and target_address < range.end) return compile_unit; | |
| 1290 | 1303 | } |
| 1291 | 1304 | } |
| 1292 | 1305 | |
| ... | ... | @@ -2345,7 +2358,6 @@ pub const ElfModule = struct { |
| 2345 | 2358 | .endian = endian, |
| 2346 | 2359 | .sections = sections, |
| 2347 | 2360 | .is_macho = false, |
| 2348 | .compile_units_sorted = false, | |
| 2349 | 2361 | }; |
| 2350 | 2362 | |
| 2351 | 2363 | try Dwarf.open(&di, gpa); |
lib/std/debug/Info.zig+1-1| ... | ... | @@ -27,7 +27,7 @@ pub const LoadError = Dwarf.ElfModule.LoadError; |
| 27 | 27 | pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info { |
| 28 | 28 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; |
| 29 | 29 | var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, &sections, null); |
| 30 | try elf_module.dwarf.sortCompileUnits(); | |
| 30 | try elf_module.dwarf.populateRanges(gpa); | |
| 31 | 31 | var info: Info = .{ |
| 32 | 32 | .address_map = .{}, |
| 33 | 33 | .coverage = coverage, |
lib/std/debug/SelfInfo.zig-3| ... | ... | @@ -606,7 +606,6 @@ pub const Module = switch (native_os) { |
| 606 | 606 | .endian = .little, |
| 607 | 607 | .sections = sections, |
| 608 | 608 | .is_macho = true, |
| 609 | .compile_units_sorted = false, | |
| 610 | 609 | }; |
| 611 | 610 | |
| 612 | 611 | try Dwarf.open(&di, allocator); |
| ... | ... | @@ -996,7 +995,6 @@ fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module { |
| 996 | 995 | .endian = native_endian, |
| 997 | 996 | .sections = sections, |
| 998 | 997 | .is_macho = false, |
| 999 | .compile_units_sorted = false, | |
| 1000 | 998 | }; |
| 1001 | 999 | |
| 1002 | 1000 | try Dwarf.open(&dwarf, allocator); |
| ... | ... | @@ -1810,7 +1808,6 @@ fn unwindFrameMachODwarf( |
| 1810 | 1808 | var di: Dwarf = .{ |
| 1811 | 1809 | .endian = native_endian, |
| 1812 | 1810 | .is_macho = true, |
| 1813 | .compile_units_sorted = false, | |
| 1814 | 1811 | }; |
| 1815 | 1812 | defer di.deinit(context.allocator); |
| 1816 | 1813 |
tools/dump-cov.zig+18-9| ... | ... | @@ -54,21 +54,30 @@ pub fn main() !void { |
| 54 | 54 | const header: *SeenPcsHeader = @ptrCast(cov_bytes); |
| 55 | 55 | try stdout.print("{any}\n", .{header.*}); |
| 56 | 56 | const pcs = header.pcAddrs(); |
| 57 | for (0.., pcs[0 .. pcs.len - 1], pcs[1..]) |i, a, b| { | |
| 58 | if (a > b) std.log.err("{d}: 0x{x} > 0x{x}", .{ i, a, b }); | |
| 59 | } | |
| 60 | assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize))); | |
| 61 | 57 | |
| 62 | const seen_pcs = header.seenBits(); | |
| 58 | var indexed_pcs: std.AutoArrayHashMapUnmanaged(usize, void) = .{}; | |
| 59 | try indexed_pcs.entries.resize(arena, pcs.len); | |
| 60 | @memcpy(indexed_pcs.entries.items(.key), pcs); | |
| 61 | try indexed_pcs.reIndex(arena); | |
| 62 | ||
| 63 | const sorted_pcs = try arena.dupe(usize, pcs); | |
| 64 | std.mem.sortUnstable(usize, sorted_pcs, {}, std.sort.asc(usize)); | |
| 63 | 65 | |
| 64 | const source_locations = try arena.alloc(std.debug.Coverage.SourceLocation, pcs.len); | |
| 65 | try debug_info.resolveAddresses(gpa, pcs, source_locations); | |
| 66 | const source_locations = try arena.alloc(std.debug.Coverage.SourceLocation, sorted_pcs.len); | |
| 67 | try debug_info.resolveAddresses(gpa, sorted_pcs, source_locations); | |
| 68 | ||
| 69 | const seen_pcs = header.seenBits(); | |
| 66 | 70 | |
| 67 | for (pcs, source_locations, 0..) |pc, sl, i| { | |
| 71 | for (sorted_pcs, source_locations) |pc, sl| { | |
| 72 | if (sl.file == .invalid) { | |
| 73 | try stdout.print(" {x}: invalid\n", .{pc}); | |
| 74 | continue; | |
| 75 | } | |
| 68 | 76 | const file = debug_info.coverage.fileAt(sl.file); |
| 69 | 77 | const dir_name = debug_info.coverage.directories.keys()[file.directory_index]; |
| 70 | 78 | const dir_name_slice = debug_info.coverage.stringAt(dir_name); |
| 71 | const hit: u1 = @truncate(seen_pcs[i / @bitSizeOf(usize)] >> @intCast(i % @bitSizeOf(usize))); | |
| 79 | const seen_i = indexed_pcs.getIndex(pc).?; | |
| 80 | const hit: u1 = @truncate(seen_pcs[seen_i / @bitSizeOf(usize)] >> @intCast(seen_i % @bitSizeOf(usize))); | |
| 72 | 81 | try stdout.print("{c}{x}: {s}/{s}:{d}:{d}\n", .{ |
| 73 | 82 | "-+"[hit], pc, dir_name_slice, debug_info.coverage.stringAt(file.basename), sl.line, sl.column, |
| 74 | 83 | }); |