| ... | ... | @@ -684,12 +684,16 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz |
| 684 | 684 | pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { |
| 685 | 685 | const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { |
| 686 | 686 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), |
| 687 | | else => return err, |
| 687 | else => { |
| 688 | return err; |
| 689 | }, |
| 688 | 690 | }; |
| 689 | 691 | |
| 690 | 692 | const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) { |
| 691 | 693 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), |
| 692 | | else => return err, |
| 694 | else => { |
| 695 | return err; |
| 696 | }, |
| 693 | 697 | }; |
| 694 | 698 | defer symbol_info.deinit(debug_info.allocator); |
| 695 | 699 | |
| ... | ... | @@ -877,13 +881,29 @@ fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 |
| 877 | 881 | return ptr[start..end]; |
| 878 | 882 | } |
| 879 | 883 | |
| 880 | | /// This takes ownership of elf_file: users of this function should not close |
| 881 | | /// it themselves, even on error. |
| 882 | | /// TODO it's weird to take ownership even on error, rework this code. |
| 883 | | pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugInfo { |
| 884 | pub fn readElfDebugInfo( |
| 885 | allocator: mem.Allocator, |
| 886 | elf_filename: ?[]const u8, |
| 887 | build_id: ?[]const u8, |
| 888 | expected_crc: ?u32, |
| 889 | ) !ModuleDebugInfo { |
| 884 | 890 | nosuspend { |
| 891 | |
| 892 | // TODO https://github.com/ziglang/zig/issues/5525 |
| 893 | const elf_file = (if (elf_filename) |filename| blk: { |
| 894 | break :blk if (fs.path.isAbsolute(filename)) |
| 895 | fs.openFileAbsolute(filename, .{ .intended_io_mode = .blocking }) |
| 896 | else |
| 897 | fs.cwd().openFile(filename, .{ .intended_io_mode = .blocking }); |
| 898 | } else fs.openSelfExe(.{ .intended_io_mode = .blocking })) catch |err| switch (err) { |
| 899 | error.FileNotFound => return error.MissingDebugInfo, |
| 900 | else => return err, |
| 901 | }; |
| 902 | |
| 885 | 903 | const mapped_mem = try mapWholeFile(elf_file); |
| 886 | | const hdr = @as(*const elf.Ehdr, @ptrCast(&mapped_mem[0])); |
| 904 | if (expected_crc) |crc| if (crc != std.hash.crc.Crc32SmallWithPoly(.IEEE).hash(mapped_mem)) return error.MissingDebugInfo; |
| 905 | |
| 906 | const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); |
| 887 | 907 | if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; |
| 888 | 908 | if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; |
| 889 | 909 | |
| ... | ... | @@ -918,44 +938,147 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn |
| 918 | 938 | var opt_debug_names: ?[]const u8 = null; |
| 919 | 939 | var opt_debug_frame: ?[]const u8 = null; |
| 920 | 940 | |
| 941 | var owned_sections: [ModuleDebugInfo.num_sections][]const u8 = [_][]const u8{&.{}} ** ModuleDebugInfo.num_sections; |
| 942 | errdefer for (owned_sections) |section| allocator.free(section); |
| 943 | |
| 944 | var separate_debug_filename: ?[]const u8 = null; |
| 945 | var separate_debug_crc: ?u32 = null; |
| 946 | |
| 921 | 947 | for (shdrs) |*shdr| { |
| 922 | 948 | if (shdr.sh_type == elf.SHT_NULL) continue; |
| 923 | | |
| 924 | 949 | const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); |
| 925 | | if (mem.eql(u8, name, ".debug_info")) { |
| 926 | | opt_debug_info = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 927 | | } else if (mem.eql(u8, name, ".debug_abbrev")) { |
| 928 | | opt_debug_abbrev = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 929 | | } else if (mem.eql(u8, name, ".debug_str")) { |
| 930 | | opt_debug_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 931 | | } else if (mem.eql(u8, name, ".debug_str_offsets")) { |
| 932 | | opt_debug_str_offsets = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 933 | | } else if (mem.eql(u8, name, ".debug_line")) { |
| 934 | | opt_debug_line = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 935 | | } else if (mem.eql(u8, name, ".debug_line_str")) { |
| 936 | | opt_debug_line_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 937 | | } else if (mem.eql(u8, name, ".debug_ranges")) { |
| 938 | | opt_debug_ranges = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 939 | | } else if (mem.eql(u8, name, ".debug_loclists")) { |
| 940 | | opt_debug_loclists = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 941 | | } else if (mem.eql(u8, name, ".debug_rnglists")) { |
| 942 | | opt_debug_rnglists = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 943 | | } else if (mem.eql(u8, name, ".debug_addr")) { |
| 944 | | opt_debug_addr = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 945 | | } else if (mem.eql(u8, name, ".debug_names")) { |
| 946 | | opt_debug_names = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 947 | | } else if (mem.eql(u8, name, ".debug_frame")) { |
| 948 | | opt_debug_frame = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 950 | |
| 951 | if (mem.eql(u8, name, ".gnu_debuglink")) { |
| 952 | const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 953 | const debug_filename = mem.sliceTo(@ptrCast([*:0]const u8, gnu_debuglink.ptr), 0); |
| 954 | const crc_offset = mem.alignForward(@ptrToInt(&debug_filename[debug_filename.len]) + 1, 4) - @ptrToInt(gnu_debuglink.ptr); |
| 955 | const crc_bytes = gnu_debuglink[crc_offset .. crc_offset + 4]; |
| 956 | separate_debug_crc = mem.readIntSliceNative(u32, crc_bytes); |
| 957 | separate_debug_filename = debug_filename; |
| 958 | continue; |
| 959 | } |
| 960 | |
| 961 | const sections = [_]struct { name: []const u8, out: *?[]const u8 }{ |
| 962 | .{ .name = ".debug_info", .out = &opt_debug_info }, |
| 963 | .{ .name = ".debug_abbrev", .out = &opt_debug_abbrev }, |
| 964 | .{ .name = ".debug_str", .out = &opt_debug_str }, |
| 965 | .{ .name = ".debug_str_offsets", .out = &opt_debug_str_offsets }, |
| 966 | .{ .name = ".debug_line", .out = &opt_debug_line }, |
| 967 | .{ .name = ".debug_line_str", .out = &opt_debug_line_str }, |
| 968 | .{ .name = ".debug_ranges", .out = &opt_debug_ranges }, |
| 969 | .{ .name = ".debug_loclists", .out = &opt_debug_loclists }, |
| 970 | .{ .name = ".debug_rnglists", .out = &opt_debug_rnglists }, |
| 971 | .{ .name = ".debug_addr", .out = &opt_debug_addr }, |
| 972 | .{ .name = ".debug_names", .out = &opt_debug_names }, |
| 973 | .{ .name = ".debug_frame", .out = &opt_debug_frame }, |
| 974 | }; |
| 975 | |
| 976 | var section_index = for (sections, 0..) |section, i| { |
| 977 | if (mem.eql(u8, section.name, name)) { |
| 978 | break i; |
| 979 | } |
| 980 | } else continue; |
| 981 | |
| 982 | const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 983 | if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) { |
| 984 | var section_stream = io.fixedBufferStream(section_bytes); |
| 985 | var section_reader = section_stream.reader(); |
| 986 | const chdr = section_reader.readStruct(elf.Chdr) catch continue; |
| 987 | |
| 988 | // TODO: Support ZSTD |
| 989 | if (chdr.ch_type != .ZLIB) continue; |
| 990 | |
| 991 | var zlib_stream = std.compress.zlib.zlibStream(allocator, section_stream.reader()) catch continue; |
| 992 | defer zlib_stream.deinit(); |
| 993 | |
| 994 | var decompressed_section = try allocator.alloc(u8, chdr.ch_size); |
| 995 | errdefer allocator.free(decompressed_section); |
| 996 | |
| 997 | const read = zlib_stream.reader().readAll(decompressed_section) catch continue; |
| 998 | assert(read == decompressed_section.len); |
| 999 | |
| 1000 | sections[section_index].out.* = decompressed_section; |
| 1001 | owned_sections[section_index] = decompressed_section; |
| 1002 | } else { |
| 1003 | sections[section_index].out.* = section_bytes; |
| 1004 | } |
| 1005 | } |
| 1006 | |
| 1007 | const missing_debug_info = |
| 1008 | opt_debug_info == null or |
| 1009 | opt_debug_abbrev == null or |
| 1010 | opt_debug_str == null or |
| 1011 | opt_debug_line == null; |
| 1012 | |
| 1013 | // Attempt to load debug info from an external file |
| 1014 | // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html |
| 1015 | if (missing_debug_info) { |
| 1016 | const global_debug_directories = [_][]const u8{ |
| 1017 | "/usr/lib/debug", |
| 1018 | // TODO: Determine the set of directories used by most distros for this path (check GDB sources) |
| 1019 | }; |
| 1020 | |
| 1021 | // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug |
| 1022 | if (build_id) |id| blk: { |
| 1023 | if (id.len < 3) break :blk; |
| 1024 | |
| 1025 | // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice |
| 1026 | const extension = ".debug"; |
| 1027 | var id_prefix_buf: [2]u8 = undefined; |
| 1028 | var filename_buf: [38 + extension.len]u8 = undefined; |
| 1029 | |
| 1030 | _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable; |
| 1031 | const filename = std.fmt.bufPrint( |
| 1032 | &filename_buf, |
| 1033 | "{s}" ++ extension, |
| 1034 | .{std.fmt.fmtSliceHexLower(id[1..])}, |
| 1035 | ) catch break :blk; |
| 1036 | |
| 1037 | for (global_debug_directories) |global_directory| { |
| 1038 | // TODO: joinBuf would be ideal (with a fs.MAX_PATH_BYTES buffer) |
| 1039 | const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename }); |
| 1040 | defer allocator.free(path); |
| 1041 | std.debug.print(" Loading external debug info from {s}\n", .{path}); |
| 1042 | return readElfDebugInfo(allocator, path, null, separate_debug_crc) catch continue; |
| 1043 | } |
| 1044 | } |
| 1045 | |
| 1046 | // use the path from .gnu_debuglink, in the search order as gdb |
| 1047 | if (separate_debug_filename) |separate_filename| blk: { |
| 1048 | if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo; |
| 1049 | |
| 1050 | // <cwd>/<gnu_debuglink> |
| 1051 | if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc)) |debug_info| return debug_info else |_| {} |
| 1052 | |
| 1053 | // <cwd>/.debug/<gnu_debuglink> |
| 1054 | { |
| 1055 | const path = try fs.path.join(allocator, &.{ ".debug", separate_filename }); |
| 1056 | defer allocator.free(path); |
| 1057 | |
| 1058 | if (readElfDebugInfo(allocator, path, null, separate_debug_crc)) |debug_info| return debug_info else |_| {} |
| 1059 | } |
| 1060 | |
| 1061 | var cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; |
| 1062 | const cwd_path = fs.cwd().realpath("", &cwd_buf) catch break :blk; |
| 1063 | |
| 1064 | // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink> |
| 1065 | for (global_debug_directories) |global_directory| { |
| 1066 | const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename }); |
| 1067 | defer allocator.free(path); |
| 1068 | if (readElfDebugInfo(allocator, path, null, separate_debug_crc)) |debug_info| return debug_info else |_| {} |
| 1069 | } |
| 949 | 1070 | } |
| 1071 | |
| 1072 | return error.MissingDebugInfo; |
| 950 | 1073 | } |
| 951 | 1074 | |
| 952 | 1075 | var di = DW.DwarfInfo{ |
| 953 | 1076 | .endian = endian, |
| 954 | | .debug_info = opt_debug_info orelse return error.MissingDebugInfo, |
| 955 | | .debug_abbrev = opt_debug_abbrev orelse return error.MissingDebugInfo, |
| 956 | | .debug_str = opt_debug_str orelse return error.MissingDebugInfo, |
| 1077 | .debug_info = opt_debug_info.?, |
| 1078 | .debug_abbrev = opt_debug_abbrev.?, |
| 1079 | .debug_str = opt_debug_str.?, |
| 957 | 1080 | .debug_str_offsets = opt_debug_str_offsets, |
| 958 | | .debug_line = opt_debug_line orelse return error.MissingDebugInfo, |
| 1081 | .debug_line = opt_debug_line.?, |
| 959 | 1082 | .debug_line_str = opt_debug_line_str, |
| 960 | 1083 | .debug_ranges = opt_debug_ranges, |
| 961 | 1084 | .debug_loclists = opt_debug_loclists, |
| ... | ... | @@ -971,6 +1094,7 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn |
| 971 | 1094 | .base_address = undefined, |
| 972 | 1095 | .dwarf = di, |
| 973 | 1096 | .mapped_memory = mapped_mem, |
| 1097 | .owned_sections = owned_sections, |
| 974 | 1098 | }; |
| 975 | 1099 | } |
| 976 | 1100 | } |
| ... | ... | @@ -1359,6 +1483,7 @@ pub const DebugInfo = struct { |
| 1359 | 1483 | // Output |
| 1360 | 1484 | base_address: usize = undefined, |
| 1361 | 1485 | name: []const u8 = undefined, |
| 1486 | build_id: ?[]const u8 = undefined, |
| 1362 | 1487 | } = .{ .address = address }; |
| 1363 | 1488 | const CtxTy = @TypeOf(ctx); |
| 1364 | 1489 | |
| ... | ... | @@ -1375,16 +1500,30 @@ pub const DebugInfo = struct { |
| 1375 | 1500 | |
| 1376 | 1501 | const seg_start = info.dlpi_addr + phdr.p_vaddr; |
| 1377 | 1502 | const seg_end = seg_start + phdr.p_memsz; |
| 1378 | | |
| 1379 | 1503 | if (context.address >= seg_start and context.address < seg_end) { |
| 1380 | 1504 | // Android libc uses NULL instead of an empty string to mark the |
| 1381 | 1505 | // main program |
| 1382 | 1506 | context.name = mem.sliceTo(info.dlpi_name, 0) orelse ""; |
| 1383 | 1507 | context.base_address = info.dlpi_addr; |
| 1384 | | // Stop the iteration |
| 1385 | | return error.Found; |
| 1508 | break; |
| 1386 | 1509 | } |
| 1510 | } else return; |
| 1511 | |
| 1512 | for (info.dlpi_phdr[0..info.dlpi_phnum]) |phdr| { |
| 1513 | if (phdr.p_type != elf.PT_NOTE) continue; |
| 1514 | |
| 1515 | const note_bytes = @intToPtr([*]const u8, info.dlpi_addr + phdr.p_vaddr)[0..phdr.p_memsz]; |
| 1516 | const name_size = mem.readIntSliceNative(u32, note_bytes[0..4]); |
| 1517 | if (name_size != 4) continue; |
| 1518 | const desc_size = mem.readIntSliceNative(u32, note_bytes[4..8]); |
| 1519 | const note_type = mem.readIntSliceNative(u32, note_bytes[8..12]); |
| 1520 | if (note_type != elf.NT_GNU_BUILD_ID) continue; |
| 1521 | if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue; |
| 1522 | context.build_id = note_bytes[16 .. 16 + desc_size]; |
| 1387 | 1523 | } |
| 1524 | |
| 1525 | // Stop the iteration |
| 1526 | return error.Found; |
| 1388 | 1527 | } |
| 1389 | 1528 | }.callback)) { |
| 1390 | 1529 | return error.MissingDebugInfo; |
| ... | ... | @@ -1399,18 +1538,7 @@ pub const DebugInfo = struct { |
| 1399 | 1538 | const obj_di = try self.allocator.create(ModuleDebugInfo); |
| 1400 | 1539 | errdefer self.allocator.destroy(obj_di); |
| 1401 | 1540 | |
| 1402 | | // TODO https://github.com/ziglang/zig/issues/5525 |
| 1403 | | const copy = if (ctx.name.len > 0) |
| 1404 | | fs.cwd().openFile(ctx.name, .{ .intended_io_mode = .blocking }) |
| 1405 | | else |
| 1406 | | fs.openSelfExe(.{ .intended_io_mode = .blocking }); |
| 1407 | | |
| 1408 | | const elf_file = copy catch |err| switch (err) { |
| 1409 | | error.FileNotFound => return error.MissingDebugInfo, |
| 1410 | | else => return err, |
| 1411 | | }; |
| 1412 | | |
| 1413 | | obj_di.* = try readElfDebugInfo(self.allocator, elf_file); |
| 1541 | obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null); |
| 1414 | 1542 | obj_di.base_address = ctx.base_address; |
| 1415 | 1543 | |
| 1416 | 1544 | try self.address_map.putNoClobber(ctx.base_address, obj_di); |
| ... | ... | @@ -1752,9 +1880,13 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 1752 | 1880 | base_address: usize, |
| 1753 | 1881 | dwarf: DW.DwarfInfo, |
| 1754 | 1882 | mapped_memory: []align(mem.page_size) const u8, |
| 1883 | owned_sections: [num_sections][]const u8 = [_][]const u8{&.{}} ** num_sections, |
| 1884 | |
| 1885 | const num_sections = 12; |
| 1755 | 1886 | |
| 1756 | 1887 | fn deinit(self: *@This(), allocator: mem.Allocator) void { |
| 1757 | 1888 | self.dwarf.deinit(allocator); |
| 1889 | for (self.owned_sections) |section| allocator.free(section); |
| 1758 | 1890 | os.munmap(self.mapped_memory); |
| 1759 | 1891 | } |
| 1760 | 1892 | |