authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-02 16:31:49-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-07 00:48:32-07:00
log2e12b45d8b43d69e144887df4b04a2d383ff25d4
treeba827c7db23da67284f22b12966915d92a194953
parent107b27276602d1935a90ae97c57f640b090088b5

introduce tool for dumping coverage file

with debug info resolved. begin efforts of providing `std.debug.Info`, a cross-platform abstraction for loading debug information into an in-memory format that supports queries such as "what is the source location of this virtual memory address?" Unlike `std.debug.SelfInfo`, this API does not assume the debug information in question happens to match the host CPU architecture, OS, or other target properties.

6 files changed, 541 insertions(+), 285 deletions(-)

lib/std/Build/Cache/Path.zig+4-4
...@@ -32,16 +32,16 @@ pub fn resolvePosix(p: Path, arena: Allocator, sub_path: []const u8) Allocator.E...@@ -32,16 +32,16 @@ pub fn resolvePosix(p: Path, arena: Allocator, sub_path: []const u8) Allocator.E
32 };32 };
33}33}
3434
35pub fn joinString(p: Path, allocator: Allocator, sub_path: []const u8) Allocator.Error![]u8 {35pub fn joinString(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Error![]u8 {
36 const parts: []const []const u8 =36 const parts: []const []const u8 =
37 if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path };37 if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path };
38 return p.root_dir.join(allocator, parts);38 return p.root_dir.join(gpa, parts);
39}39}
4040
41pub fn joinStringZ(p: Path, allocator: Allocator, sub_path: []const u8) Allocator.Error![:0]u8 {41pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Error![:0]u8 {
42 const parts: []const []const u8 =42 const parts: []const []const u8 =
43 if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path };43 if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path };
44 return p.root_dir.joinZ(allocator, parts);44 return p.root_dir.joinZ(gpa, parts);
45}45}
4646
47pub fn openFile(47pub fn openFile(
lib/std/debug.zig+20-13
...@@ -17,6 +17,7 @@ pub const MemoryAccessor = @import("debug/MemoryAccessor.zig");...@@ -17,6 +17,7 @@ pub const MemoryAccessor = @import("debug/MemoryAccessor.zig");
17pub const Dwarf = @import("debug/Dwarf.zig");17pub const Dwarf = @import("debug/Dwarf.zig");
18pub const Pdb = @import("debug/Pdb.zig");18pub const Pdb = @import("debug/Pdb.zig");
19pub const SelfInfo = @import("debug/SelfInfo.zig");19pub const SelfInfo = @import("debug/SelfInfo.zig");
20pub const Info = @import("debug/Info.zig");
2021
21/// Unresolved source locations can be represented with a single `usize` that22/// Unresolved source locations can be represented with a single `usize` that
22/// corresponds to a virtual memory address of the program counter. Combined23/// corresponds to a virtual memory address of the program counter. Combined
...@@ -28,6 +29,12 @@ pub const SourceLocation = struct {...@@ -28,6 +29,12 @@ pub const SourceLocation = struct {
28 file_name: []const u8,29 file_name: []const u8,
29};30};
3031
32pub const Symbol = struct {
33 name: []const u8 = "???",
34 compile_unit_name: []const u8 = "???",
35 source_location: ?SourceLocation = null,
36};
37
31/// Deprecated because it returns the optimization mode of the standard38/// Deprecated because it returns the optimization mode of the standard
32/// library, when the caller probably wants to use the optimization mode of39/// library, when the caller probably wants to use the optimization mode of
33/// their own module.40/// their own module.
...@@ -871,13 +878,13 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address:...@@ -871,13 +878,13 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address:
871 error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config),878 error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config),
872 else => return err,879 else => return err,
873 };880 };
874 defer symbol_info.deinit(debug_info.allocator);881 defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name);
875882
876 return printLineInfo(883 return printLineInfo(
877 out_stream,884 out_stream,
878 symbol_info.line_info,885 symbol_info.source_location,
879 address,886 address,
880 symbol_info.symbol_name,887 symbol_info.name,
881 symbol_info.compile_unit_name,888 symbol_info.compile_unit_name,
882 tty_config,889 tty_config,
883 printLineFromFileAnyOs,890 printLineFromFileAnyOs,
...@@ -886,7 +893,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address:...@@ -886,7 +893,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address:
886893
887fn printLineInfo(894fn printLineInfo(
888 out_stream: anytype,895 out_stream: anytype,
889 line_info: ?SourceLocation,896 source_location: ?SourceLocation,
890 address: usize,897 address: usize,
891 symbol_name: []const u8,898 symbol_name: []const u8,
892 compile_unit_name: []const u8,899 compile_unit_name: []const u8,
...@@ -896,8 +903,8 @@ fn printLineInfo(...@@ -896,8 +903,8 @@ fn printLineInfo(
896 nosuspend {903 nosuspend {
897 try tty_config.setColor(out_stream, .bold);904 try tty_config.setColor(out_stream, .bold);
898905
899 if (line_info) |*li| {906 if (source_location) |*sl| {
900 try out_stream.print("{s}:{d}:{d}", .{ li.file_name, li.line, li.column });907 try out_stream.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
901 } else {908 } else {
902 try out_stream.writeAll("???:?:?");909 try out_stream.writeAll("???:?:?");
903 }910 }
...@@ -910,11 +917,11 @@ fn printLineInfo(...@@ -910,11 +917,11 @@ fn printLineInfo(
910 try out_stream.writeAll("\n");917 try out_stream.writeAll("\n");
911918
912 // Show the matching source code line if possible919 // Show the matching source code line if possible
913 if (line_info) |li| {920 if (source_location) |sl| {
914 if (printLineFromFile(out_stream, li)) {921 if (printLineFromFile(out_stream, sl)) {
915 if (li.column > 0) {922 if (sl.column > 0) {
916 // The caret already takes one char923 // The caret already takes one char
917 const space_needed = @as(usize, @intCast(li.column - 1));924 const space_needed = @as(usize, @intCast(sl.column - 1));
918925
919 try out_stream.writeByteNTimes(' ', space_needed);926 try out_stream.writeByteNTimes(' ', space_needed);
920 try tty_config.setColor(out_stream, .green);927 try tty_config.setColor(out_stream, .green);
...@@ -932,10 +939,10 @@ fn printLineInfo(...@@ -932,10 +939,10 @@ fn printLineInfo(
932 }939 }
933}940}
934941
935fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void {942fn printLineFromFileAnyOs(out_stream: anytype, source_location: SourceLocation) !void {
936 // Need this to always block even in async I/O mode, because this could potentially943 // Need this to always block even in async I/O mode, because this could potentially
937 // be called from e.g. the event loop code crashing.944 // be called from e.g. the event loop code crashing.
938 var f = try fs.cwd().openFile(line_info.file_name, .{});945 var f = try fs.cwd().openFile(source_location.file_name, .{});
939 defer f.close();946 defer f.close();
940 // TODO fstat and make sure that the file has the correct size947 // TODO fstat and make sure that the file has the correct size
941948
...@@ -944,7 +951,7 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void...@@ -944,7 +951,7 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void
944 const line_start = seek: {951 const line_start = seek: {
945 var current_line_start: usize = 0;952 var current_line_start: usize = 0;
946 var next_line: usize = 1;953 var next_line: usize = 1;
947 while (next_line != line_info.line) {954 while (next_line != source_location.line) {
948 const slice = buf[current_line_start..amt_read];955 const slice = buf[current_line_start..amt_read];
949 if (mem.indexOfScalar(u8, slice, '\n')) |pos| {956 if (mem.indexOfScalar(u8, slice, '\n')) |pos| {
950 next_line += 1;957 next_line += 1;
lib/std/debug/Dwarf.zig+368-26
...@@ -12,6 +12,8 @@ const native_endian = builtin.cpu.arch.endian();...@@ -12,6 +12,8 @@ const native_endian = builtin.cpu.arch.endian();
1212
13const std = @import("../std.zig");13const std = @import("../std.zig");
14const Allocator = std.mem.Allocator;14const Allocator = std.mem.Allocator;
15const elf = std.elf;
16const mem = std.mem;
15const DW = std.dwarf;17const DW = std.dwarf;
16const AT = DW.AT;18const AT = DW.AT;
17const EH = DW.EH;19const EH = DW.EH;
...@@ -22,8 +24,8 @@ const UT = DW.UT;...@@ -22,8 +24,8 @@ const UT = DW.UT;
22const assert = std.debug.assert;24const assert = std.debug.assert;
23const cast = std.math.cast;25const cast = std.math.cast;
24const maxInt = std.math.maxInt;26const maxInt = std.math.maxInt;
25const readInt = std.mem.readInt;
26const MemoryAccessor = std.debug.MemoryAccessor;27const MemoryAccessor = std.debug.MemoryAccessor;
28const Path = std.Build.Cache.Path;
2729
28/// Did I mention this is deprecated?30/// Did I mention this is deprecated?
29const DeprecatedFixedBufferReader = std.debug.DeprecatedFixedBufferReader;31const DeprecatedFixedBufferReader = std.debug.DeprecatedFixedBufferReader;
...@@ -252,13 +254,13 @@ pub const Die = struct {...@@ -252,13 +254,13 @@ pub const Die = struct {
252 .@"32" => {254 .@"32" => {
253 const byte_offset = compile_unit.str_offsets_base + 4 * index;255 const byte_offset = compile_unit.str_offsets_base + 4 * index;
254 if (byte_offset + 4 > debug_str_offsets.len) return bad();256 if (byte_offset + 4 > debug_str_offsets.len) return bad();
255 const offset = readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian);257 const offset = mem.readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian);
256 return getStringGeneric(opt_str, offset);258 return getStringGeneric(opt_str, offset);
257 },259 },
258 .@"64" => {260 .@"64" => {
259 const byte_offset = compile_unit.str_offsets_base + 8 * index;261 const byte_offset = compile_unit.str_offsets_base + 8 * index;
260 if (byte_offset + 8 > debug_str_offsets.len) return bad();262 if (byte_offset + 8 > debug_str_offsets.len) return bad();
261 const offset = readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian);263 const offset = mem.readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian);
262 return getStringGeneric(opt_str, offset);264 return getStringGeneric(opt_str, offset);
263 },265 },
264 }266 }
...@@ -721,12 +723,14 @@ const num_sections = std.enums.directEnumArrayLen(Section.Id, 0);...@@ -721,12 +723,14 @@ const num_sections = std.enums.directEnumArrayLen(Section.Id, 0);
721pub const SectionArray = [num_sections]?Section;723pub const SectionArray = [num_sections]?Section;
722pub const null_section_array = [_]?Section{null} ** num_sections;724pub const null_section_array = [_]?Section{null} ** num_sections;
723725
726pub const OpenError = ScanError;
727
724/// Initialize DWARF info. The caller has the responsibility to initialize most728/// Initialize DWARF info. The caller has the responsibility to initialize most
725/// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the729/// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the
726/// main binary file (not the secondary debug info file).730/// main binary file (not the secondary debug info file).
727pub fn open(di: *Dwarf, allocator: Allocator) !void {731pub fn open(di: *Dwarf, gpa: Allocator) OpenError!void {
728 try di.scanAllFunctions(allocator);732 try di.scanAllFunctions(gpa);
729 try di.scanAllCompileUnits(allocator);733 try di.scanAllCompileUnits(gpa);
730}734}
731735
732const PcRange = struct {736const PcRange = struct {
...@@ -747,21 +751,21 @@ pub fn sectionVirtualOffset(di: Dwarf, dwarf_section: Section.Id, base_address:...@@ -747,21 +751,21 @@ pub fn sectionVirtualOffset(di: Dwarf, dwarf_section: Section.Id, base_address:
747 return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null;751 return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null;
748}752}
749753
750pub fn deinit(di: *Dwarf, allocator: Allocator) void {754pub fn deinit(di: *Dwarf, gpa: Allocator) void {
751 for (di.sections) |opt_section| {755 for (di.sections) |opt_section| {
752 if (opt_section) |s| if (s.owned) allocator.free(s.data);756 if (opt_section) |s| if (s.owned) gpa.free(s.data);
753 }757 }
754 for (di.abbrev_table_list.items) |*abbrev| {758 for (di.abbrev_table_list.items) |*abbrev| {
755 abbrev.deinit(allocator);759 abbrev.deinit(gpa);
756 }760 }
757 di.abbrev_table_list.deinit(allocator);761 di.abbrev_table_list.deinit(gpa);
758 for (di.compile_unit_list.items) |*cu| {762 for (di.compile_unit_list.items) |*cu| {
759 cu.die.deinit(allocator);763 cu.die.deinit(gpa);
760 }764 }
761 di.compile_unit_list.deinit(allocator);765 di.compile_unit_list.deinit(gpa);
762 di.func_list.deinit(allocator);766 di.func_list.deinit(gpa);
763 di.cie_map.deinit(allocator);767 di.cie_map.deinit(gpa);
764 di.fde_list.deinit(allocator);768 di.fde_list.deinit(gpa);
765 di.* = undefined;769 di.* = undefined;
766}770}
767771
...@@ -777,7 +781,12 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 {...@@ -777,7 +781,12 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 {
777 return null;781 return null;
778}782}
779783
780fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void {784const ScanError = error{
785 InvalidDebugInfo,
786 MissingDebugInfo,
787} || Allocator.Error || std.debug.DeprecatedFixedBufferReader.Error;
788
789fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
781 var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian };790 var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian };
782 var this_unit_offset: u64 = 0;791 var this_unit_offset: u64 = 0;
783792
...@@ -964,7 +973,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void {...@@ -964,7 +973,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void {
964 }973 }
965}974}
966975
967fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void {976fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
968 var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian };977 var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian };
969 var this_unit_offset: u64 = 0;978 var this_unit_offset: u64 = 0;
970979
...@@ -1070,13 +1079,13 @@ const DebugRangeIterator = struct {...@@ -1070,13 +1079,13 @@ const DebugRangeIterator = struct {
1070 .@"32" => {1079 .@"32" => {
1071 const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx));1080 const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx));
1072 if (offset_loc + 4 > debug_ranges.len) return bad();1081 if (offset_loc + 4 > debug_ranges.len) return bad();
1073 const offset = readInt(u32, debug_ranges[offset_loc..][0..4], di.endian);1082 const offset = mem.readInt(u32, debug_ranges[offset_loc..][0..4], di.endian);
1074 break :off compile_unit.rnglists_base + offset;1083 break :off compile_unit.rnglists_base + offset;
1075 },1084 },
1076 .@"64" => {1085 .@"64" => {
1077 const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx));1086 const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx));
1078 if (offset_loc + 8 > debug_ranges.len) return bad();1087 if (offset_loc + 8 > debug_ranges.len) return bad();
1079 const offset = readInt(u64, debug_ranges[offset_loc..][0..8], di.endian);1088 const offset = mem.readInt(u64, debug_ranges[offset_loc..][0..8], di.endian);
1080 break :off compile_unit.rnglists_base + offset;1089 break :off compile_unit.rnglists_base + offset;
1081 },1090 },
1082 }1091 }
...@@ -1287,7 +1296,7 @@ fn parseDie(...@@ -1287,7 +1296,7 @@ fn parseDie(
1287 attrs_buf: []Die.Attr,1296 attrs_buf: []Die.Attr,
1288 abbrev_table: *const Abbrev.Table,1297 abbrev_table: *const Abbrev.Table,
1289 format: Format,1298 format: Format,
1290) !?Die {1299) ScanError!?Die {
1291 const abbrev_code = try fbr.readUleb128(u64);1300 const abbrev_code = try fbr.readUleb128(u64);
1292 if (abbrev_code == 0) return null;1301 if (abbrev_code == 0) return null;
1293 const table_entry = abbrev_table.get(abbrev_code) orelse return bad();1302 const table_entry = abbrev_table.get(abbrev_code) orelse return bad();
...@@ -1588,7 +1597,7 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {...@@ -1588,7 +1597,7 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {
1588 // The header is 8 or 12 bytes depending on is_64.1597 // The header is 8 or 12 bytes depending on is_64.
1589 if (compile_unit.addr_base < 8) return bad();1598 if (compile_unit.addr_base < 8) return bad();
15901599
1591 const version = readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian);1600 const version = mem.readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian);
1592 if (version != 5) return bad();1601 if (version != 5) return bad();
15931602
1594 const addr_size = debug_addr[compile_unit.addr_base - 2];1603 const addr_size = debug_addr[compile_unit.addr_base - 2];
...@@ -1598,9 +1607,9 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {...@@ -1598,9 +1607,9 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {
1598 if (byte_offset + addr_size > debug_addr.len) return bad();1607 if (byte_offset + addr_size > debug_addr.len) return bad();
1599 return switch (addr_size) {1608 return switch (addr_size) {
1600 1 => debug_addr[byte_offset],1609 1 => debug_addr[byte_offset],
1601 2 => readInt(u16, debug_addr[byte_offset..][0..2], di.endian),1610 2 => mem.readInt(u16, debug_addr[byte_offset..][0..2], di.endian),
1602 4 => readInt(u32, debug_addr[byte_offset..][0..4], di.endian),1611 4 => mem.readInt(u32, debug_addr[byte_offset..][0..4], di.endian),
1603 8 => readInt(u64, debug_addr[byte_offset..][0..8], di.endian),1612 8 => mem.readInt(u64, debug_addr[byte_offset..][0..8], di.endian),
1604 else => bad(),1613 else => bad(),
1605 };1614 };
1606}1615}
...@@ -1699,7 +1708,7 @@ fn parseFormValue(...@@ -1699,7 +1708,7 @@ fn parseFormValue(
1699 form_id: u64,1708 form_id: u64,
1700 format: Format,1709 format: Format,
1701 implicit_const: ?i64,1710 implicit_const: ?i64,
1702) anyerror!FormValue {1711) ScanError!FormValue {
1703 return switch (form_id) {1712 return switch (form_id) {
1704 FORM.addr => .{ .addr = try fbr.readAddress(switch (@bitSizeOf(usize)) {1713 FORM.addr => .{ .addr = try fbr.readAddress(switch (@bitSizeOf(usize)) {
1705 32 => .@"32",1714 32 => .@"32",
...@@ -1892,7 +1901,8 @@ const UnitHeader = struct {...@@ -1892,7 +1901,8 @@ const UnitHeader = struct {
1892 header_length: u4,1901 header_length: u4,
1893 unit_length: u64,1902 unit_length: u64,
1894};1903};
1895fn readUnitHeader(fbr: *DeprecatedFixedBufferReader, opt_ma: ?*MemoryAccessor) !UnitHeader {1904
1905fn readUnitHeader(fbr: *DeprecatedFixedBufferReader, opt_ma: ?*MemoryAccessor) ScanError!UnitHeader {
1896 return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) {1906 return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) {
1897 0...0xfffffff0 - 1 => |unit_length| .{1907 0...0xfffffff0 - 1 => |unit_length| .{
1898 .format = .@"32",1908 .format = .@"32",
...@@ -2023,3 +2033,335 @@ fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize {...@@ -2023,3 +2033,335 @@ fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize {
2023 return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset)));2033 return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset)));
2024 }2034 }
2025}2035}
2036
2037pub const ElfModule = struct {
2038 base_address: usize,
2039 dwarf: Dwarf,
2040 mapped_memory: []align(std.mem.page_size) const u8,
2041 external_mapped_memory: ?[]align(std.mem.page_size) const u8,
2042
2043 pub fn deinit(self: *@This(), allocator: Allocator) void {
2044 self.dwarf.deinit(allocator);
2045 std.posix.munmap(self.mapped_memory);
2046 if (self.external_mapped_memory) |m| std.posix.munmap(m);
2047 }
2048
2049 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
2050 // Translate the VA into an address into this object
2051 const relocated_address = address - self.base_address;
2052 return self.dwarf.getSymbol(allocator, relocated_address);
2053 }
2054
2055 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
2056 _ = allocator;
2057 _ = address;
2058 return &self.dwarf;
2059 }
2060
2061 pub const LoadError = error{
2062 InvalidDebugInfo,
2063 MissingDebugInfo,
2064 InvalidElfMagic,
2065 InvalidElfVersion,
2066 InvalidElfEndian,
2067 /// TODO: implement this and then remove this error code
2068 UnimplementedDwarfForeignEndian,
2069 /// The debug info may be valid but this implementation uses memory
2070 /// mapping which limits things to usize. If the target debug info is
2071 /// 64-bit and host is 32-bit, there may be debug info that is not
2072 /// supportable using this method.
2073 Overflow,
2074
2075 PermissionDenied,
2076 LockedMemoryLimitExceeded,
2077 MemoryMappingNotSupported,
2078 } || Allocator.Error || std.fs.File.OpenError || OpenError;
2079
2080 /// Reads debug info from an already mapped ELF file.
2081 ///
2082 /// If the required sections aren't present but a reference to external debug
2083 /// info is, then this this function will recurse to attempt to load the debug
2084 /// sections from an external file.
2085 pub fn load(
2086 gpa: Allocator,
2087 mapped_mem: []align(std.mem.page_size) const u8,
2088 build_id: ?[]const u8,
2089 expected_crc: ?u32,
2090 parent_sections: *Dwarf.SectionArray,
2091 parent_mapped_mem: ?[]align(std.mem.page_size) const u8,
2092 elf_filename: ?[]const u8,
2093 ) LoadError!Dwarf.ElfModule {
2094 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
2095
2096 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
2097 if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
2098 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
2099
2100 const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {
2101 elf.ELFDATA2LSB => .little,
2102 elf.ELFDATA2MSB => .big,
2103 else => return error.InvalidElfEndian,
2104 };
2105 if (endian != native_endian) return error.UnimplementedDwarfForeignEndian;
2106
2107 const shoff = hdr.e_shoff;
2108 const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
2109 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[cast(usize, str_section_off) orelse return error.Overflow]));
2110 const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
2111 const shdrs = @as(
2112 [*]const elf.Shdr,
2113 @ptrCast(@alignCast(&mapped_mem[shoff])),
2114 )[0..hdr.e_shnum];
2115
2116 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
2117
2118 // Combine section list. This takes ownership over any owned sections from the parent scope.
2119 for (parent_sections, &sections) |*parent, *section_elem| {
2120 if (parent.*) |*p| {
2121 section_elem.* = p.*;
2122 p.owned = false;
2123 }
2124 }
2125 errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data);
2126
2127 var separate_debug_filename: ?[]const u8 = null;
2128 var separate_debug_crc: ?u32 = null;
2129
2130 for (shdrs) |*shdr| {
2131 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
2132 const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
2133
2134 if (mem.eql(u8, name, ".gnu_debuglink")) {
2135 const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
2136 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
2137 const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr);
2138 const crc_bytes = gnu_debuglink[crc_offset..][0..4];
2139 separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian);
2140 separate_debug_filename = debug_filename;
2141 continue;
2142 }
2143
2144 var section_index: ?usize = null;
2145 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |sect, i| {
2146 if (mem.eql(u8, "." ++ sect.name, name)) section_index = i;
2147 }
2148 if (section_index == null) continue;
2149 if (sections[section_index.?] != null) continue;
2150
2151 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
2152 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
2153 var section_stream = std.io.fixedBufferStream(section_bytes);
2154 const section_reader = section_stream.reader();
2155 const chdr = section_reader.readStruct(elf.Chdr) catch continue;
2156 if (chdr.ch_type != .ZLIB) continue;
2157
2158 var zlib_stream = std.compress.zlib.decompressor(section_reader);
2159
2160 const decompressed_section = try gpa.alloc(u8, chdr.ch_size);
2161 errdefer gpa.free(decompressed_section);
2162
2163 const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
2164 assert(read == decompressed_section.len);
2165
2166 break :blk .{
2167 .data = decompressed_section,
2168 .virtual_address = shdr.sh_addr,
2169 .owned = true,
2170 };
2171 } else .{
2172 .data = section_bytes,
2173 .virtual_address = shdr.sh_addr,
2174 .owned = false,
2175 };
2176 }
2177
2178 const missing_debug_info =
2179 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
2180 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
2181 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
2182 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
2183
2184 // Attempt to load debug info from an external file
2185 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
2186 if (missing_debug_info) {
2187
2188 // Only allow one level of debug info nesting
2189 if (parent_mapped_mem) |_| {
2190 return error.MissingDebugInfo;
2191 }
2192
2193 const global_debug_directories = [_][]const u8{
2194 "/usr/lib/debug",
2195 };
2196
2197 // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
2198 if (build_id) |id| blk: {
2199 if (id.len < 3) break :blk;
2200
2201 // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
2202 const extension = ".debug";
2203 var id_prefix_buf: [2]u8 = undefined;
2204 var filename_buf: [38 + extension.len]u8 = undefined;
2205
2206 _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable;
2207 const filename = std.fmt.bufPrint(
2208 &filename_buf,
2209 "{s}" ++ extension,
2210 .{std.fmt.fmtSliceHexLower(id[1..])},
2211 ) catch break :blk;
2212
2213 for (global_debug_directories) |global_directory| {
2214 const path: Path = .{
2215 .root_dir = std.Build.Cache.Directory.cwd(),
2216 .sub_path = try std.fs.path.join(gpa, &.{
2217 global_directory, ".build-id", &id_prefix_buf, filename,
2218 }),
2219 };
2220 defer gpa.free(path.sub_path);
2221
2222 return loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
2223 }
2224 }
2225
2226 // use the path from .gnu_debuglink, in the same search order as gdb
2227 if (separate_debug_filename) |separate_filename| blk: {
2228 if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename))
2229 return error.MissingDebugInfo;
2230
2231 // <cwd>/<gnu_debuglink>
2232 if (loadPath(
2233 gpa,
2234 .{
2235 .root_dir = std.Build.Cache.Directory.cwd(),
2236 .sub_path = separate_filename,
2237 },
2238 null,
2239 separate_debug_crc,
2240 &sections,
2241 mapped_mem,
2242 )) |debug_info| {
2243 return debug_info;
2244 } else |_| {}
2245
2246 // <cwd>/.debug/<gnu_debuglink>
2247 {
2248 const path: Path = .{
2249 .root_dir = std.Build.Cache.Directory.cwd(),
2250 .sub_path = try std.fs.path.join(gpa, &.{ ".debug", separate_filename }),
2251 };
2252 defer gpa.free(path.sub_path);
2253
2254 if (loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
2255 }
2256
2257 var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
2258 const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :blk;
2259
2260 // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
2261 for (global_debug_directories) |global_directory| {
2262 const path: Path = .{
2263 .root_dir = std.Build.Cache.Directory.cwd(),
2264 .sub_path = try std.fs.path.join(gpa, &.{ global_directory, cwd_path, separate_filename }),
2265 };
2266 defer gpa.free(path.sub_path);
2267 if (loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
2268 }
2269 }
2270
2271 return error.MissingDebugInfo;
2272 }
2273
2274 var di: Dwarf = .{
2275 .endian = endian,
2276 .sections = sections,
2277 .is_macho = false,
2278 };
2279
2280 try Dwarf.open(&di, gpa);
2281
2282 return .{
2283 .base_address = 0,
2284 .dwarf = di,
2285 .mapped_memory = parent_mapped_mem orelse mapped_mem,
2286 .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
2287 };
2288 }
2289
2290 pub fn loadPath(
2291 gpa: Allocator,
2292 elf_file_path: Path,
2293 build_id: ?[]const u8,
2294 expected_crc: ?u32,
2295 parent_sections: *Dwarf.SectionArray,
2296 parent_mapped_mem: ?[]align(std.mem.page_size) const u8,
2297 ) LoadError!Dwarf.ElfModule {
2298 const elf_file = elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{}) catch |err| switch (err) {
2299 error.FileNotFound => return missing(),
2300 else => return err,
2301 };
2302 defer elf_file.close();
2303
2304 const end_pos = elf_file.getEndPos() catch return bad();
2305 const file_len = cast(usize, end_pos) orelse return error.Overflow;
2306
2307 const mapped_mem = try std.posix.mmap(
2308 null,
2309 file_len,
2310 std.posix.PROT.READ,
2311 .{ .TYPE = .SHARED },
2312 elf_file.handle,
2313 0,
2314 );
2315 errdefer std.posix.munmap(mapped_mem);
2316
2317 return load(
2318 gpa,
2319 mapped_mem,
2320 build_id,
2321 expected_crc,
2322 parent_sections,
2323 parent_mapped_mem,
2324 elf_file_path.sub_path,
2325 );
2326 }
2327};
2328
2329/// Given an array of virtual memory addresses, sorted ascending, outputs a
2330/// corresponding array of source locations, by appending to the provided
2331/// array list.
2332pub fn resolveSourceLocations(
2333 d: *Dwarf,
2334 gpa: Allocator,
2335 sorted_pc_addrs: []const u64,
2336 /// Asserts its length equals length of `sorted_pc_addrs`.
2337 output: []std.debug.SourceLocation,
2338) error{ MissingDebugInfo, InvalidDebugInfo }!void {
2339 assert(sorted_pc_addrs.len == output.len);
2340 _ = d;
2341 _ = gpa;
2342 @panic("TODO");
2343}
2344
2345fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol {
2346 if (di.findCompileUnit(address)) |compile_unit| {
2347 return .{
2348 .name = di.getSymbolName(address) orelse "???",
2349 .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) {
2350 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
2351 },
2352 .source_location = di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) {
2353 error.MissingDebugInfo, error.InvalidDebugInfo => null,
2354 else => return err,
2355 },
2356 };
2357 } else |err| switch (err) {
2358 error.MissingDebugInfo, error.InvalidDebugInfo => return .{},
2359 else => return err,
2360 }
2361}
2362
2363pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 {
2364 const start = cast(usize, offset) orelse return error.Overflow;
2365 const end = start + (cast(usize, size) orelse return error.Overflow);
2366 return ptr[start..end];
2367}
lib/std/debug/Info.zig created+57
...@@ -0,0 +1,57 @@
1//! Cross-platform abstraction for loading debug information into an in-memory
2//! format that supports queries such as "what is the source location of this
3//! virtual memory address?"
4//!
5//! Unlike `std.debug.SelfInfo`, this API does not assume the debug information
6//! in question happens to match the host CPU architecture, OS, or other target
7//! properties.
8
9const std = @import("../std.zig");
10const Allocator = std.mem.Allocator;
11const Path = std.Build.Cache.Path;
12const Dwarf = std.debug.Dwarf;
13const page_size = std.mem.page_size;
14const assert = std.debug.assert;
15
16const Info = @This();
17
18/// Sorted by key, ascending.
19address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule),
20
21pub const LoadError = Dwarf.ElfModule.LoadError;
22
23pub fn load(gpa: Allocator, path: Path) LoadError!Info {
24 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
25 const elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, &sections, null);
26 var info: Info = .{
27 .address_map = .{},
28 };
29 try info.address_map.put(gpa, elf_module.base_address, elf_module);
30 return info;
31}
32
33pub fn deinit(info: *Info, gpa: Allocator) void {
34 for (info.address_map.values()) |*elf_module| {
35 elf_module.dwarf.deinit(gpa);
36 }
37 info.address_map.deinit(gpa);
38 info.* = undefined;
39}
40
41pub const ResolveSourceLocationsError = error{
42 MissingDebugInfo,
43 InvalidDebugInfo,
44} || Allocator.Error;
45
46pub fn resolveSourceLocations(
47 info: *Info,
48 gpa: Allocator,
49 sorted_pc_addrs: []const u64,
50 /// Asserts its length equals length of `sorted_pc_addrs`.
51 output: []std.debug.SourceLocation,
52) ResolveSourceLocationsError!void {
53 assert(sorted_pc_addrs.len == output.len);
54 if (info.address_map.entries.len != 1) @panic("TODO");
55 const elf_module = &info.address_map.values()[0];
56 return elf_module.dwarf.resolveSourceLocations(gpa, sorted_pc_addrs, output);
57}
lib/std/debug/SelfInfo.zig+22-242
...@@ -587,7 +587,7 @@ pub const Module = switch (native_os) {...@@ -587,7 +587,7 @@ pub const Module = switch (native_os) {
587 }587 }
588 if (section_index == null) continue;588 if (section_index == null) continue;
589589
590 const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size);590 const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size);
591 sections[section_index.?] = .{591 sections[section_index.?] = .{
592 .data = section_bytes,592 .data = section_bytes,
593 .virtual_address = sect.addr,593 .virtual_address = sect.addr,
...@@ -622,7 +622,7 @@ pub const Module = switch (native_os) {...@@ -622,7 +622,7 @@ pub const Module = switch (native_os) {
622 return result.value_ptr;622 return result.value_ptr;
623 }623 }
624624
625 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {625 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !Dwarf.SymbolInfo {
626 nosuspend {626 nosuspend {
627 const result = try self.getOFileInfoForAddress(allocator, address);627 const result = try self.getOFileInfoForAddress(allocator, address);
628 if (result.symbol == null) return .{};628 if (result.symbol == null) return .{};
...@@ -641,7 +641,7 @@ pub const Module = switch (native_os) {...@@ -641,7 +641,7 @@ pub const Module = switch (native_os) {
641 const addr_off = result.relocated_address - result.symbol.?.addr;641 const addr_off = result.relocated_address - result.symbol.?.addr;
642 const o_file_di = &result.o_file_info.?.di;642 const o_file_di = &result.o_file_info.?.di;
643 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {643 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
644 return SymbolInfo{644 return .{
645 .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",645 .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
646 .compile_unit_name = compile_unit.die.getAttrString(646 .compile_unit_name = compile_unit.die.getAttrString(
647 o_file_di,647 o_file_di,
...@@ -662,7 +662,7 @@ pub const Module = switch (native_os) {...@@ -662,7 +662,7 @@ pub const Module = switch (native_os) {
662 };662 };
663 } else |err| switch (err) {663 } else |err| switch (err) {
664 error.MissingDebugInfo, error.InvalidDebugInfo => {664 error.MissingDebugInfo, error.InvalidDebugInfo => {
665 return SymbolInfo{ .symbol_name = stab_symbol };665 return .{ .symbol_name = stab_symbol };
666 },666 },
667 else => return err,667 else => return err,
668 }668 }
...@@ -729,7 +729,7 @@ pub const Module = switch (native_os) {...@@ -729,7 +729,7 @@ pub const Module = switch (native_os) {
729 }729 }
730 }730 }
731731
732 fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo {732 fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?std.debug.Symbol {
733 var coff_section: *align(1) const coff.SectionHeader = undefined;733 var coff_section: *align(1) const coff.SectionHeader = undefined;
734 const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| {734 const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| {
735 if (sect_contrib.Section > self.coff_section_headers.len) continue;735 if (sect_contrib.Section > self.coff_section_headers.len) continue;
...@@ -759,14 +759,14 @@ pub const Module = switch (native_os) {...@@ -759,14 +759,14 @@ pub const Module = switch (native_os) {
759 relocated_address - coff_section.virtual_address,759 relocated_address - coff_section.virtual_address,
760 );760 );
761761
762 return SymbolInfo{762 return .{
763 .symbol_name = symbol_name,763 .symbol_name = symbol_name,
764 .compile_unit_name = obj_basename,764 .compile_unit_name = obj_basename,
765 .line_info = opt_line_info,765 .line_info = opt_line_info,
766 };766 };
767 }767 }
768768
769 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {769 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
770 // Translate the VA into an address into this object770 // Translate the VA into an address into this object
771 const relocated_address = address - self.base_address;771 const relocated_address = address - self.base_address;
772772
...@@ -776,10 +776,10 @@ pub const Module = switch (native_os) {...@@ -776,10 +776,10 @@ pub const Module = switch (native_os) {
776776
777 if (self.dwarf) |*dwarf| {777 if (self.dwarf) |*dwarf| {
778 const dwarf_address = relocated_address + self.coff_image_base;778 const dwarf_address = relocated_address + self.coff_image_base;
779 return getSymbolFromDwarf(allocator, dwarf_address, dwarf);779 return dwarf.getSymbol(allocator, dwarf_address);
780 }780 }
781781
782 return SymbolInfo{};782 return .{};
783 }783 }
784784
785 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {785 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
...@@ -792,41 +792,18 @@ pub const Module = switch (native_os) {...@@ -792,41 +792,18 @@ pub const Module = switch (native_os) {
792 };792 };
793 }793 }
794 },794 },
795 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {795 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => Dwarf.ElfModule,
796 base_address: usize,
797 dwarf: Dwarf,
798 mapped_memory: []align(mem.page_size) const u8,
799 external_mapped_memory: ?[]align(mem.page_size) const u8,
800
801 pub fn deinit(self: *@This(), allocator: Allocator) void {
802 self.dwarf.deinit(allocator);
803 posix.munmap(self.mapped_memory);
804 if (self.external_mapped_memory) |m| posix.munmap(m);
805 }
806
807 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
808 // Translate the VA into an address into this object
809 const relocated_address = address - self.base_address;
810 return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf);
811 }
812
813 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
814 _ = allocator;
815 _ = address;
816 return &self.dwarf;
817 }
818 },
819 .wasi, .emscripten => struct {796 .wasi, .emscripten => struct {
820 pub fn deinit(self: *@This(), allocator: Allocator) void {797 pub fn deinit(self: *@This(), allocator: Allocator) void {
821 _ = self;798 _ = self;
822 _ = allocator;799 _ = allocator;
823 }800 }
824801
825 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {802 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
826 _ = self;803 _ = self;
827 _ = allocator;804 _ = allocator;
828 _ = address;805 _ = address;
829 return SymbolInfo{};806 return .{};
830 }807 }
831808
832 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {809 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
...@@ -1068,7 +1045,7 @@ pub fn readElfDebugInfo(...@@ -1068,7 +1045,7 @@ pub fn readElfDebugInfo(
1068 expected_crc: ?u32,1045 expected_crc: ?u32,
1069 parent_sections: *Dwarf.SectionArray,1046 parent_sections: *Dwarf.SectionArray,
1070 parent_mapped_mem: ?[]align(mem.page_size) const u8,1047 parent_mapped_mem: ?[]align(mem.page_size) const u8,
1071) !Module {1048) !Dwarf.ElfModule {
1072 nosuspend {1049 nosuspend {
1073 const elf_file = (if (elf_filename) |filename| blk: {1050 const elf_file = (if (elf_filename) |filename| blk: {
1074 break :blk fs.cwd().openFile(filename, .{});1051 break :blk fs.cwd().openFile(filename, .{});
...@@ -1078,176 +1055,15 @@ pub fn readElfDebugInfo(...@@ -1078,176 +1055,15 @@ pub fn readElfDebugInfo(
1078 };1055 };
10791056
1080 const mapped_mem = try mapWholeFile(elf_file);1057 const mapped_mem = try mapWholeFile(elf_file);
1081 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;1058 return Dwarf.ElfModule.load(
10821059 allocator,
1083 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);1060 mapped_mem,
1084 if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;1061 build_id,
1085 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;1062 expected_crc,
10861063 parent_sections,
1087 const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {1064 parent_mapped_mem,
1088 elf.ELFDATA2LSB => .little,1065 elf_filename,
1089 elf.ELFDATA2MSB => .big,1066 );
1090 else => return error.InvalidElfEndian,
1091 };
1092 assert(endian == native_endian); // this is our own debug info
1093
1094 const shoff = hdr.e_shoff;
1095 const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
1096 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow]));
1097 const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
1098 const shdrs = @as(
1099 [*]const elf.Shdr,
1100 @ptrCast(@alignCast(&mapped_mem[shoff])),
1101 )[0..hdr.e_shnum];
1102
1103 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
1104
1105 // Combine section list. This takes ownership over any owned sections from the parent scope.
1106 for (parent_sections, &sections) |*parent, *section| {
1107 if (parent.*) |*p| {
1108 section.* = p.*;
1109 p.owned = false;
1110 }
1111 }
1112 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1113
1114 var separate_debug_filename: ?[]const u8 = null;
1115 var separate_debug_crc: ?u32 = null;
1116
1117 for (shdrs) |*shdr| {
1118 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
1119 const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
1120
1121 if (mem.eql(u8, name, ".gnu_debuglink")) {
1122 const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1123 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
1124 const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr);
1125 const crc_bytes = gnu_debuglink[crc_offset..][0..4];
1126 separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian);
1127 separate_debug_filename = debug_filename;
1128 continue;
1129 }
1130
1131 var section_index: ?usize = null;
1132 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1133 if (mem.eql(u8, "." ++ section.name, name)) section_index = i;
1134 }
1135 if (section_index == null) continue;
1136 if (sections[section_index.?] != null) continue;
1137
1138 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1139 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
1140 var section_stream = std.io.fixedBufferStream(section_bytes);
1141 var section_reader = section_stream.reader();
1142 const chdr = section_reader.readStruct(elf.Chdr) catch continue;
1143 if (chdr.ch_type != .ZLIB) continue;
1144
1145 var zlib_stream = std.compress.zlib.decompressor(section_stream.reader());
1146
1147 const decompressed_section = try allocator.alloc(u8, chdr.ch_size);
1148 errdefer allocator.free(decompressed_section);
1149
1150 const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
1151 assert(read == decompressed_section.len);
1152
1153 break :blk .{
1154 .data = decompressed_section,
1155 .virtual_address = shdr.sh_addr,
1156 .owned = true,
1157 };
1158 } else .{
1159 .data = section_bytes,
1160 .virtual_address = shdr.sh_addr,
1161 .owned = false,
1162 };
1163 }
1164
1165 const missing_debug_info =
1166 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
1167 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
1168 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
1169 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
1170
1171 // Attempt to load debug info from an external file
1172 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
1173 if (missing_debug_info) {
1174
1175 // Only allow one level of debug info nesting
1176 if (parent_mapped_mem) |_| {
1177 return error.MissingDebugInfo;
1178 }
1179
1180 const global_debug_directories = [_][]const u8{
1181 "/usr/lib/debug",
1182 };
1183
1184 // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
1185 if (build_id) |id| blk: {
1186 if (id.len < 3) break :blk;
1187
1188 // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
1189 const extension = ".debug";
1190 var id_prefix_buf: [2]u8 = undefined;
1191 var filename_buf: [38 + extension.len]u8 = undefined;
1192
1193 _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable;
1194 const filename = std.fmt.bufPrint(
1195 &filename_buf,
1196 "{s}" ++ extension,
1197 .{std.fmt.fmtSliceHexLower(id[1..])},
1198 ) catch break :blk;
1199
1200 for (global_debug_directories) |global_directory| {
1201 const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename });
1202 defer allocator.free(path);
1203
1204 return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
1205 }
1206 }
1207
1208 // use the path from .gnu_debuglink, in the same search order as gdb
1209 if (separate_debug_filename) |separate_filename| blk: {
1210 if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo;
1211
1212 // <cwd>/<gnu_debuglink>
1213 if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1214
1215 // <cwd>/.debug/<gnu_debuglink>
1216 {
1217 const path = try fs.path.join(allocator, &.{ ".debug", separate_filename });
1218 defer allocator.free(path);
1219
1220 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1221 }
1222
1223 var cwd_buf: [fs.max_path_bytes]u8 = undefined;
1224 const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk;
1225
1226 // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
1227 for (global_debug_directories) |global_directory| {
1228 const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename });
1229 defer allocator.free(path);
1230 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1231 }
1232 }
1233
1234 return error.MissingDebugInfo;
1235 }
1236
1237 var di = Dwarf{
1238 .endian = endian,
1239 .sections = sections,
1240 .is_macho = false,
1241 };
1242
1243 try Dwarf.open(&di, allocator);
1244
1245 return .{
1246 .base_address = undefined,
1247 .dwarf = di,
1248 .mapped_memory = parent_mapped_mem orelse mapped_mem,
1249 .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
1250 };
1251 }1067 }
1252}1068}
12531069
...@@ -1289,22 +1105,6 @@ fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {...@@ -1289,22 +1105,6 @@ fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {
1289 }1105 }
1290}1106}
12911107
1292fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 {
1293 const start = math.cast(usize, offset) orelse return error.Overflow;
1294 const end = start + (math.cast(usize, size) orelse return error.Overflow);
1295 return ptr[start..end];
1296}
1297
1298pub const SymbolInfo = struct {
1299 symbol_name: []const u8 = "???",
1300 compile_unit_name: []const u8 = "???",
1301 line_info: ?std.debug.SourceLocation = null,
1302
1303 pub fn deinit(self: SymbolInfo, allocator: Allocator) void {
1304 if (self.line_info) |li| allocator.free(li.file_name);
1305 }
1306};
1307
1308fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {1108fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
1309 var min: usize = 0;1109 var min: usize = 0;
1310 var max: usize = symbols.len - 1;1110 var max: usize = symbols.len - 1;
...@@ -1350,26 +1150,6 @@ test machoSearchSymbols {...@@ -1350,26 +1150,6 @@ test machoSearchSymbols {
1350 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?);1150 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?);
1351}1151}
13521152
1353fn getSymbolFromDwarf(allocator: Allocator, address: u64, di: *Dwarf) !SymbolInfo {
1354 if (nosuspend di.findCompileUnit(address)) |compile_unit| {
1355 return SymbolInfo{
1356 .symbol_name = nosuspend di.getSymbolName(address) orelse "???",
1357 .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) {
1358 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
1359 },
1360 .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) {
1361 error.MissingDebugInfo, error.InvalidDebugInfo => null,
1362 else => return err,
1363 },
1364 };
1365 } else |err| switch (err) {
1366 error.MissingDebugInfo, error.InvalidDebugInfo => {
1367 return SymbolInfo{};
1368 },
1369 else => return err,
1370 }
1371}
1372
1373/// Unwind a frame using MachO compact unwind info (from __unwind_info).1153/// Unwind a frame using MachO compact unwind info (from __unwind_info).
1374/// If the compact encoding can't encode a way to unwind a frame, it will1154/// If the compact encoding can't encode a way to unwind a frame, it will
1375/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.1155/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
tools/dump-cov.zig created+70
...@@ -0,0 +1,70 @@
1//! Reads a Zig coverage file and prints human-readable information to stdout,
2//! including file:line:column information for each PC.
3
4const std = @import("std");
5const fatal = std.process.fatal;
6const Path = std.Build.Cache.Path;
7const assert = std.debug.assert;
8
9pub fn main() !void {
10 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{};
11 defer _ = general_purpose_allocator.deinit();
12 const gpa = general_purpose_allocator.allocator();
13
14 var arena_instance = std.heap.ArenaAllocator.init(gpa);
15 defer arena_instance.deinit();
16 const arena = arena_instance.allocator();
17
18 const args = try std.process.argsAlloc(arena);
19 const exe_file_name = args[1];
20 const cov_file_name = args[2];
21
22 const exe_path: Path = .{
23 .root_dir = std.Build.Cache.Directory.cwd(),
24 .sub_path = exe_file_name,
25 };
26 const cov_path: Path = .{
27 .root_dir = std.Build.Cache.Directory.cwd(),
28 .sub_path = cov_file_name,
29 };
30
31 var debug_info = std.debug.Info.load(gpa, exe_path) catch |err| {
32 fatal("failed to load debug info for {}: {s}", .{ exe_path, @errorName(err) });
33 };
34 defer debug_info.deinit(gpa);
35
36 const cov_bytes = cov_path.root_dir.handle.readFileAlloc(arena, cov_path.sub_path, 1 << 30) catch |err| {
37 fatal("failed to load coverage file {}: {s}", .{ cov_path, @errorName(err) });
38 };
39
40 var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
41 const stdout = bw.writer();
42
43 const header: *align(1) SeenPcsHeader = @ptrCast(cov_bytes);
44 try stdout.print("{any}\n", .{header.*});
45 //const n_bitset_elems = (header.pcs_len + 7) / 8;
46 const pcs_bytes = cov_bytes[@sizeOf(SeenPcsHeader)..][0 .. header.pcs_len * @sizeOf(usize)];
47 const pcs = try arena.alloc(usize, header.pcs_len);
48 for (0..pcs_bytes.len / @sizeOf(usize), pcs) |i, *pc| {
49 pc.* = std.mem.readInt(usize, pcs_bytes[i * @sizeOf(usize) ..][0..@sizeOf(usize)], .little);
50 }
51 assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize)));
52
53 const source_locations = try arena.alloc(std.debug.SourceLocation, pcs.len);
54 try debug_info.resolveSourceLocations(gpa, pcs, source_locations);
55
56 for (pcs, source_locations) |pc, sl| {
57 try stdout.print("{x}: {s}:{d}:{d}\n", .{
58 pc, sl.file_name, sl.line, sl.column,
59 });
60 }
61
62 try bw.flush();
63}
64
65const SeenPcsHeader = extern struct {
66 n_runs: usize,
67 deduplicated_runs: usize,
68 pcs_len: usize,
69 lowest_stack: usize,
70};