| author | |
| committer | |
| log | 2e12b45d8b43d69e144887df4b04a2d383ff25d4 |
| tree | ba827c7db23da67284f22b12966915d92a194953 |
| parent | 107b27276602d1935a90ae97c57f640b090088b5 |
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 | 32 | }; |
| 33 | 33 | } |
| 34 | 34 | |
| 35 | pub fn joinString(p: Path, allocator: Allocator, sub_path: []const u8) Allocator.Error![]u8 { | |
| 35 | pub fn joinString(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Error![]u8 { | |
| 36 | 36 | const parts: []const []const u8 = |
| 37 | 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 | } |
| 40 | 40 | |
| 41 | pub fn joinStringZ(p: Path, allocator: Allocator, sub_path: []const u8) Allocator.Error![:0]u8 { | |
| 41 | pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Error![:0]u8 { | |
| 42 | 42 | const parts: []const []const u8 = |
| 43 | 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 | } |
| 46 | 46 | |
| 47 | 47 | pub fn openFile( |
lib/std/debug.zig+20-13| ... | ... | @@ -17,6 +17,7 @@ pub const MemoryAccessor = @import("debug/MemoryAccessor.zig"); |
| 17 | 17 | pub const Dwarf = @import("debug/Dwarf.zig"); |
| 18 | 18 | pub const Pdb = @import("debug/Pdb.zig"); |
| 19 | 19 | pub const SelfInfo = @import("debug/SelfInfo.zig"); |
| 20 | pub const Info = @import("debug/Info.zig"); | |
| 20 | 21 | |
| 21 | 22 | /// Unresolved source locations can be represented with a single `usize` that |
| 22 | 23 | /// corresponds to a virtual memory address of the program counter. Combined |
| ... | ... | @@ -28,6 +29,12 @@ pub const SourceLocation = struct { |
| 28 | 29 | file_name: []const u8, |
| 29 | 30 | }; |
| 30 | 31 | |
| 32 | pub const Symbol = struct { | |
| 33 | name: []const u8 = "???", | |
| 34 | compile_unit_name: []const u8 = "???", | |
| 35 | source_location: ?SourceLocation = null, | |
| 36 | }; | |
| 37 | ||
| 31 | 38 | /// Deprecated because it returns the optimization mode of the standard |
| 32 | 39 | /// library, when the caller probably wants to use the optimization mode of |
| 33 | 40 | /// their own module. |
| ... | ... | @@ -871,13 +878,13 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: |
| 871 | 878 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), |
| 872 | 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); | |
| 875 | 882 | |
| 876 | 883 | return printLineInfo( |
| 877 | 884 | out_stream, |
| 878 | symbol_info.line_info, | |
| 885 | symbol_info.source_location, | |
| 879 | 886 | address, |
| 880 | symbol_info.symbol_name, | |
| 887 | symbol_info.name, | |
| 881 | 888 | symbol_info.compile_unit_name, |
| 882 | 889 | tty_config, |
| 883 | 890 | printLineFromFileAnyOs, |
| ... | ... | @@ -886,7 +893,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: |
| 886 | 893 | |
| 887 | 894 | fn printLineInfo( |
| 888 | 895 | out_stream: anytype, |
| 889 | line_info: ?SourceLocation, | |
| 896 | source_location: ?SourceLocation, | |
| 890 | 897 | address: usize, |
| 891 | 898 | symbol_name: []const u8, |
| 892 | 899 | compile_unit_name: []const u8, |
| ... | ... | @@ -896,8 +903,8 @@ fn printLineInfo( |
| 896 | 903 | nosuspend { |
| 897 | 904 | try tty_config.setColor(out_stream, .bold); |
| 898 | 905 | |
| 899 | if (line_info) |*li| { | |
| 900 | try out_stream.print("{s}:{d}:{d}", .{ li.file_name, li.line, li.column }); | |
| 906 | if (source_location) |*sl| { | |
| 907 | try out_stream.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); | |
| 901 | 908 | } else { |
| 902 | 909 | try out_stream.writeAll("???:?:?"); |
| 903 | 910 | } |
| ... | ... | @@ -910,11 +917,11 @@ fn printLineInfo( |
| 910 | 917 | try out_stream.writeAll("\n"); |
| 911 | 918 | |
| 912 | 919 | // Show the matching source code line if possible |
| 913 | if (line_info) |li| { | |
| 914 | if (printLineFromFile(out_stream, li)) { | |
| 915 | if (li.column > 0) { | |
| 920 | if (source_location) |sl| { | |
| 921 | if (printLineFromFile(out_stream, sl)) { | |
| 922 | if (sl.column > 0) { | |
| 916 | 923 | // 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)); | |
| 918 | 925 | |
| 919 | 926 | try out_stream.writeByteNTimes(' ', space_needed); |
| 920 | 927 | try tty_config.setColor(out_stream, .green); |
| ... | ... | @@ -932,10 +939,10 @@ fn printLineInfo( |
| 932 | 939 | } |
| 933 | 940 | } |
| 934 | 941 | |
| 935 | fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void { | |
| 942 | fn printLineFromFileAnyOs(out_stream: anytype, source_location: SourceLocation) !void { | |
| 936 | 943 | // Need this to always block even in async I/O mode, because this could potentially |
| 937 | 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 | 946 | defer f.close(); |
| 940 | 947 | // TODO fstat and make sure that the file has the correct size |
| 941 | 948 | |
| ... | ... | @@ -944,7 +951,7 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void |
| 944 | 951 | const line_start = seek: { |
| 945 | 952 | var current_line_start: usize = 0; |
| 946 | 953 | var next_line: usize = 1; |
| 947 | while (next_line != line_info.line) { | |
| 954 | while (next_line != source_location.line) { | |
| 948 | 955 | const slice = buf[current_line_start..amt_read]; |
| 949 | 956 | if (mem.indexOfScalar(u8, slice, '\n')) |pos| { |
| 950 | 957 | next_line += 1; |
lib/std/debug/Dwarf.zig+368-26| ... | ... | @@ -12,6 +12,8 @@ const native_endian = builtin.cpu.arch.endian(); |
| 12 | 12 | |
| 13 | 13 | const std = @import("../std.zig"); |
| 14 | 14 | const Allocator = std.mem.Allocator; |
| 15 | const elf = std.elf; | |
| 16 | const mem = std.mem; | |
| 15 | 17 | const DW = std.dwarf; |
| 16 | 18 | const AT = DW.AT; |
| 17 | 19 | const EH = DW.EH; |
| ... | ... | @@ -22,8 +24,8 @@ const UT = DW.UT; |
| 22 | 24 | const assert = std.debug.assert; |
| 23 | 25 | const cast = std.math.cast; |
| 24 | 26 | const maxInt = std.math.maxInt; |
| 25 | const readInt = std.mem.readInt; | |
| 26 | 27 | const MemoryAccessor = std.debug.MemoryAccessor; |
| 28 | const Path = std.Build.Cache.Path; | |
| 27 | 29 | |
| 28 | 30 | /// Did I mention this is deprecated? |
| 29 | 31 | const DeprecatedFixedBufferReader = std.debug.DeprecatedFixedBufferReader; |
| ... | ... | @@ -252,13 +254,13 @@ pub const Die = struct { |
| 252 | 254 | .@"32" => { |
| 253 | 255 | const byte_offset = compile_unit.str_offsets_base + 4 * index; |
| 254 | 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 | 258 | return getStringGeneric(opt_str, offset); |
| 257 | 259 | }, |
| 258 | 260 | .@"64" => { |
| 259 | 261 | const byte_offset = compile_unit.str_offsets_base + 8 * index; |
| 260 | 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 | 264 | return getStringGeneric(opt_str, offset); |
| 263 | 265 | }, |
| 264 | 266 | } |
| ... | ... | @@ -721,12 +723,14 @@ const num_sections = std.enums.directEnumArrayLen(Section.Id, 0); |
| 721 | 723 | pub const SectionArray = [num_sections]?Section; |
| 722 | 724 | pub const null_section_array = [_]?Section{null} ** num_sections; |
| 723 | 725 | |
| 726 | pub const OpenError = ScanError; | |
| 727 | ||
| 724 | 728 | /// Initialize DWARF info. The caller has the responsibility to initialize most |
| 725 | 729 | /// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the |
| 726 | 730 | /// main binary file (not the secondary debug info file). |
| 727 | pub fn open(di: *Dwarf, allocator: Allocator) !void { | |
| 728 | try di.scanAllFunctions(allocator); | |
| 729 | try di.scanAllCompileUnits(allocator); | |
| 731 | pub fn open(di: *Dwarf, gpa: Allocator) OpenError!void { | |
| 732 | try di.scanAllFunctions(gpa); | |
| 733 | try di.scanAllCompileUnits(gpa); | |
| 730 | 734 | } |
| 731 | 735 | |
| 732 | 736 | const PcRange = struct { |
| ... | ... | @@ -747,21 +751,21 @@ pub fn sectionVirtualOffset(di: Dwarf, dwarf_section: Section.Id, base_address: |
| 747 | 751 | return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null; |
| 748 | 752 | } |
| 749 | 753 | |
| 750 | pub fn deinit(di: *Dwarf, allocator: Allocator) void { | |
| 754 | pub fn deinit(di: *Dwarf, gpa: Allocator) void { | |
| 751 | 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 | 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 | 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); | |
| 762 | di.func_list.deinit(allocator); | |
| 763 | di.cie_map.deinit(allocator); | |
| 764 | di.fde_list.deinit(allocator); | |
| 765 | di.compile_unit_list.deinit(gpa); | |
| 766 | di.func_list.deinit(gpa); | |
| 767 | di.cie_map.deinit(gpa); | |
| 768 | di.fde_list.deinit(gpa); | |
| 765 | 769 | di.* = undefined; |
| 766 | 770 | } |
| 767 | 771 | |
| ... | ... | @@ -777,7 +781,12 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { |
| 777 | 781 | return null; |
| 778 | 782 | } |
| 779 | 783 | |
| 780 | fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { | |
| 784 | const ScanError = error{ | |
| 785 | InvalidDebugInfo, | |
| 786 | MissingDebugInfo, | |
| 787 | } || Allocator.Error || std.debug.DeprecatedFixedBufferReader.Error; | |
| 788 | ||
| 789 | fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { | |
| 781 | 790 | var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; |
| 782 | 791 | var this_unit_offset: u64 = 0; |
| 783 | 792 | |
| ... | ... | @@ -964,7 +973,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { |
| 964 | 973 | } |
| 965 | 974 | } |
| 966 | 975 | |
| 967 | fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { | |
| 976 | fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { | |
| 968 | 977 | var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; |
| 969 | 978 | var this_unit_offset: u64 = 0; |
| 970 | 979 | |
| ... | ... | @@ -1070,13 +1079,13 @@ const DebugRangeIterator = struct { |
| 1070 | 1079 | .@"32" => { |
| 1071 | 1080 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx)); |
| 1072 | 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 | 1083 | break :off compile_unit.rnglists_base + offset; |
| 1075 | 1084 | }, |
| 1076 | 1085 | .@"64" => { |
| 1077 | 1086 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx)); |
| 1078 | 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 | 1089 | break :off compile_unit.rnglists_base + offset; |
| 1081 | 1090 | }, |
| 1082 | 1091 | } |
| ... | ... | @@ -1287,7 +1296,7 @@ fn parseDie( |
| 1287 | 1296 | attrs_buf: []Die.Attr, |
| 1288 | 1297 | abbrev_table: *const Abbrev.Table, |
| 1289 | 1298 | format: Format, |
| 1290 | ) !?Die { | |
| 1299 | ) ScanError!?Die { | |
| 1291 | 1300 | const abbrev_code = try fbr.readUleb128(u64); |
| 1292 | 1301 | if (abbrev_code == 0) return null; |
| 1293 | 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 | 1597 | // The header is 8 or 12 bytes depending on is_64. |
| 1589 | 1598 | if (compile_unit.addr_base < 8) return bad(); |
| 1590 | 1599 | |
| 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 | 1601 | if (version != 5) return bad(); |
| 1593 | 1602 | |
| 1594 | 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 | 1607 | if (byte_offset + addr_size > debug_addr.len) return bad(); |
| 1599 | 1608 | return switch (addr_size) { |
| 1600 | 1609 | 1 => debug_addr[byte_offset], |
| 1601 | 2 => readInt(u16, debug_addr[byte_offset..][0..2], di.endian), | |
| 1602 | 4 => readInt(u32, debug_addr[byte_offset..][0..4], di.endian), | |
| 1603 | 8 => readInt(u64, debug_addr[byte_offset..][0..8], di.endian), | |
| 1610 | 2 => mem.readInt(u16, debug_addr[byte_offset..][0..2], di.endian), | |
| 1611 | 4 => mem.readInt(u32, debug_addr[byte_offset..][0..4], di.endian), | |
| 1612 | 8 => mem.readInt(u64, debug_addr[byte_offset..][0..8], di.endian), | |
| 1604 | 1613 | else => bad(), |
| 1605 | 1614 | }; |
| 1606 | 1615 | } |
| ... | ... | @@ -1699,7 +1708,7 @@ fn parseFormValue( |
| 1699 | 1708 | form_id: u64, |
| 1700 | 1709 | format: Format, |
| 1701 | 1710 | implicit_const: ?i64, |
| 1702 | ) anyerror!FormValue { | |
| 1711 | ) ScanError!FormValue { | |
| 1703 | 1712 | return switch (form_id) { |
| 1704 | 1713 | FORM.addr => .{ .addr = try fbr.readAddress(switch (@bitSizeOf(usize)) { |
| 1705 | 1714 | 32 => .@"32", |
| ... | ... | @@ -1892,7 +1901,8 @@ const UnitHeader = struct { |
| 1892 | 1901 | header_length: u4, |
| 1893 | 1902 | unit_length: u64, |
| 1894 | 1903 | }; |
| 1895 | fn readUnitHeader(fbr: *DeprecatedFixedBufferReader, opt_ma: ?*MemoryAccessor) !UnitHeader { | |
| 1904 | ||
| 1905 | fn readUnitHeader(fbr: *DeprecatedFixedBufferReader, opt_ma: ?*MemoryAccessor) ScanError!UnitHeader { | |
| 1896 | 1906 | return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) { |
| 1897 | 1907 | 0...0xfffffff0 - 1 => |unit_length| .{ |
| 1898 | 1908 | .format = .@"32", |
| ... | ... | @@ -2023,3 +2033,335 @@ fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { |
| 2023 | 2033 | return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); |
| 2024 | 2034 | } |
| 2025 | 2035 | } |
| 2036 | ||
| 2037 | pub 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. | |
| 2332 | pub 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 | ||
| 2345 | fn 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 | ||
| 2363 | pub 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 | ||
| 9 | const std = @import("../std.zig"); | |
| 10 | const Allocator = std.mem.Allocator; | |
| 11 | const Path = std.Build.Cache.Path; | |
| 12 | const Dwarf = std.debug.Dwarf; | |
| 13 | const page_size = std.mem.page_size; | |
| 14 | const assert = std.debug.assert; | |
| 15 | ||
| 16 | const Info = @This(); | |
| 17 | ||
| 18 | /// Sorted by key, ascending. | |
| 19 | address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule), | |
| 20 | ||
| 21 | pub const LoadError = Dwarf.ElfModule.LoadError; | |
| 22 | ||
| 23 | pub 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 | ||
| 33 | pub 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 | ||
| 41 | pub const ResolveSourceLocationsError = error{ | |
| 42 | MissingDebugInfo, | |
| 43 | InvalidDebugInfo, | |
| 44 | } || Allocator.Error; | |
| 45 | ||
| 46 | pub 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 | 587 | } |
| 588 | 588 | if (section_index == null) continue; |
| 589 | 589 | |
| 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 | 591 | sections[section_index.?] = .{ |
| 592 | 592 | .data = section_bytes, |
| 593 | 593 | .virtual_address = sect.addr, |
| ... | ... | @@ -622,7 +622,7 @@ pub const Module = switch (native_os) { |
| 622 | 622 | return result.value_ptr; |
| 623 | 623 | } |
| 624 | 624 | |
| 625 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { | |
| 625 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !Dwarf.SymbolInfo { | |
| 626 | 626 | nosuspend { |
| 627 | 627 | const result = try self.getOFileInfoForAddress(allocator, address); |
| 628 | 628 | if (result.symbol == null) return .{}; |
| ... | ... | @@ -641,7 +641,7 @@ pub const Module = switch (native_os) { |
| 641 | 641 | const addr_off = result.relocated_address - result.symbol.?.addr; |
| 642 | 642 | const o_file_di = &result.o_file_info.?.di; |
| 643 | 643 | if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { |
| 644 | return SymbolInfo{ | |
| 644 | return .{ | |
| 645 | 645 | .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", |
| 646 | 646 | .compile_unit_name = compile_unit.die.getAttrString( |
| 647 | 647 | o_file_di, |
| ... | ... | @@ -662,7 +662,7 @@ pub const Module = switch (native_os) { |
| 662 | 662 | }; |
| 663 | 663 | } else |err| switch (err) { |
| 664 | 664 | error.MissingDebugInfo, error.InvalidDebugInfo => { |
| 665 | return SymbolInfo{ .symbol_name = stab_symbol }; | |
| 665 | return .{ .symbol_name = stab_symbol }; | |
| 666 | 666 | }, |
| 667 | 667 | else => return err, |
| 668 | 668 | } |
| ... | ... | @@ -729,7 +729,7 @@ pub const Module = switch (native_os) { |
| 729 | 729 | } |
| 730 | 730 | } |
| 731 | 731 | |
| 732 | fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo { | |
| 732 | fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?std.debug.Symbol { | |
| 733 | 733 | var coff_section: *align(1) const coff.SectionHeader = undefined; |
| 734 | 734 | const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| { |
| 735 | 735 | if (sect_contrib.Section > self.coff_section_headers.len) continue; |
| ... | ... | @@ -759,14 +759,14 @@ pub const Module = switch (native_os) { |
| 759 | 759 | relocated_address - coff_section.virtual_address, |
| 760 | 760 | ); |
| 761 | 761 | |
| 762 | return SymbolInfo{ | |
| 762 | return .{ | |
| 763 | 763 | .symbol_name = symbol_name, |
| 764 | 764 | .compile_unit_name = obj_basename, |
| 765 | 765 | .line_info = opt_line_info, |
| 766 | 766 | }; |
| 767 | 767 | } |
| 768 | 768 | |
| 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 | 770 | // Translate the VA into an address into this object |
| 771 | 771 | const relocated_address = address - self.base_address; |
| 772 | 772 | |
| ... | ... | @@ -776,10 +776,10 @@ pub const Module = switch (native_os) { |
| 776 | 776 | |
| 777 | 777 | if (self.dwarf) |*dwarf| { |
| 778 | 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 | } |
| 781 | 781 | |
| 782 | return SymbolInfo{}; | |
| 782 | return .{}; | |
| 783 | 783 | } |
| 784 | 784 | |
| 785 | 785 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { |
| ... | ... | @@ -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 { | |
| 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 | }, | |
| 795 | .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => Dwarf.ElfModule, | |
| 819 | 796 | .wasi, .emscripten => struct { |
| 820 | 797 | pub fn deinit(self: *@This(), allocator: Allocator) void { |
| 821 | 798 | _ = self; |
| 822 | 799 | _ = allocator; |
| 823 | 800 | } |
| 824 | 801 | |
| 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 | 803 | _ = self; |
| 827 | 804 | _ = allocator; |
| 828 | 805 | _ = address; |
| 829 | return SymbolInfo{}; | |
| 806 | return .{}; | |
| 830 | 807 | } |
| 831 | 808 | |
| 832 | 809 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { |
| ... | ... | @@ -1068,7 +1045,7 @@ pub fn readElfDebugInfo( |
| 1068 | 1045 | expected_crc: ?u32, |
| 1069 | 1046 | parent_sections: *Dwarf.SectionArray, |
| 1070 | 1047 | parent_mapped_mem: ?[]align(mem.page_size) const u8, |
| 1071 | ) !Module { | |
| 1048 | ) !Dwarf.ElfModule { | |
| 1072 | 1049 | nosuspend { |
| 1073 | 1050 | const elf_file = (if (elf_filename) |filename| blk: { |
| 1074 | 1051 | break :blk fs.cwd().openFile(filename, .{}); |
| ... | ... | @@ -1078,176 +1055,15 @@ pub fn readElfDebugInfo( |
| 1078 | 1055 | }; |
| 1079 | 1056 | |
| 1080 | 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; | |
| 1082 | ||
| 1083 | const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); | |
| 1084 | if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; | |
| 1085 | if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; | |
| 1086 | ||
| 1087 | const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) { | |
| 1088 | elf.ELFDATA2LSB => .little, | |
| 1089 | elf.ELFDATA2MSB => .big, | |
| 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 | }; | |
| 1058 | return Dwarf.ElfModule.load( | |
| 1059 | allocator, | |
| 1060 | mapped_mem, | |
| 1061 | build_id, | |
| 1062 | expected_crc, | |
| 1063 | parent_sections, | |
| 1064 | parent_mapped_mem, | |
| 1065 | elf_filename, | |
| 1066 | ); | |
| 1251 | 1067 | } |
| 1252 | 1068 | } |
| 1253 | 1069 | |
| ... | ... | @@ -1289,22 +1105,6 @@ fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 { |
| 1289 | 1105 | } |
| 1290 | 1106 | } |
| 1291 | 1107 | |
| 1292 | fn 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 | ||
| 1298 | pub 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 | ||
| 1308 | 1108 | fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { |
| 1309 | 1109 | var min: usize = 0; |
| 1310 | 1110 | var max: usize = symbols.len - 1; |
| ... | ... | @@ -1350,26 +1150,6 @@ test machoSearchSymbols { |
| 1350 | 1150 | try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?); |
| 1351 | 1151 | } |
| 1352 | 1152 | |
| 1353 | fn 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 | 1153 | /// Unwind a frame using MachO compact unwind info (from __unwind_info). |
| 1374 | 1154 | /// If the compact encoding can't encode a way to unwind a frame, it will |
| 1375 | 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 | ||
| 4 | const std = @import("std"); | |
| 5 | const fatal = std.process.fatal; | |
| 6 | const Path = std.Build.Cache.Path; | |
| 7 | const assert = std.debug.assert; | |
| 8 | ||
| 9 | pub 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 | ||
| 65 | const SeenPcsHeader = extern struct { | |
| 66 | n_runs: usize, | |
| 67 | deduplicated_runs: usize, | |
| 68 | pcs_len: usize, | |
| 69 | lowest_stack: usize, | |
| 70 | }; |