aboutsummaryrefslogtreecommitdiff
path: root/lib/std/debug.zig
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std/debug.zig')
-rw-r--r--lib/std/debug.zig234
1 files changed, 183 insertions, 51 deletions
diff --git a/lib/std/debug.zig b/lib/std/debug.zig
index 44f6ce1367..083b2afb6c 100644
--- a/lib/std/debug.zig
+++ b/lib/std/debug.zig
@@ -684,12 +684,16 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz
pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config),
- else => return err,
+ else => {
+ return err;
+ },
};
const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config),
- else => return err,
+ else => {
+ return err;
+ },
};
defer symbol_info.deinit(debug_info.allocator);
@@ -877,13 +881,29 @@ fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8
return ptr[start..end];
}
-/// This takes ownership of elf_file: users of this function should not close
-/// it themselves, even on error.
-/// TODO it's weird to take ownership even on error, rework this code.
-pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugInfo {
+pub fn readElfDebugInfo(
+ allocator: mem.Allocator,
+ elf_filename: ?[]const u8,
+ build_id: ?[]const u8,
+ expected_crc: ?u32,
+) !ModuleDebugInfo {
nosuspend {
+
+ // TODO https://github.com/ziglang/zig/issues/5525
+ const elf_file = (if (elf_filename) |filename| blk: {
+ break :blk if (fs.path.isAbsolute(filename))
+ fs.openFileAbsolute(filename, .{ .intended_io_mode = .blocking })
+ else
+ fs.cwd().openFile(filename, .{ .intended_io_mode = .blocking });
+ } else fs.openSelfExe(.{ .intended_io_mode = .blocking })) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
+ else => return err,
+ };
+
const mapped_mem = try mapWholeFile(elf_file);
- const hdr = @as(*const elf.Ehdr, @ptrCast(&mapped_mem[0]));
+ if (expected_crc) |crc| if (crc != std.hash.crc.Crc32SmallWithPoly(.IEEE).hash(mapped_mem)) return error.MissingDebugInfo;
+
+ const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
@@ -918,44 +938,147 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn
var opt_debug_names: ?[]const u8 = null;
var opt_debug_frame: ?[]const u8 = null;
+ var owned_sections: [ModuleDebugInfo.num_sections][]const u8 = [_][]const u8{&.{}} ** ModuleDebugInfo.num_sections;
+ errdefer for (owned_sections) |section| allocator.free(section);
+
+ var separate_debug_filename: ?[]const u8 = null;
+ var separate_debug_crc: ?u32 = null;
+
for (shdrs) |*shdr| {
if (shdr.sh_type == elf.SHT_NULL) continue;
-
const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
- if (mem.eql(u8, name, ".debug_info")) {
- opt_debug_info = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
- } else if (mem.eql(u8, name, ".debug_abbrev")) {
- opt_debug_abbrev = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
- } else if (mem.eql(u8, name, ".debug_str")) {
- opt_debug_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
- } else if (mem.eql(u8, name, ".debug_str_offsets")) {
- opt_debug_str_offsets = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
- } else if (mem.eql(u8, name, ".debug_line")) {
- opt_debug_line = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
- } else if (mem.eql(u8, name, ".debug_line_str")) {
- opt_debug_line_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
- } else if (mem.eql(u8, name, ".debug_ranges")) {
- opt_debug_ranges = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
- } else if (mem.eql(u8, name, ".debug_loclists")) {
- opt_debug_loclists = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
- } else if (mem.eql(u8, name, ".debug_rnglists")) {
- opt_debug_rnglists = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
- } else if (mem.eql(u8, name, ".debug_addr")) {
- opt_debug_addr = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
- } else if (mem.eql(u8, name, ".debug_names")) {
- opt_debug_names = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
- } else if (mem.eql(u8, name, ".debug_frame")) {
- opt_debug_frame = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
+
+ if (mem.eql(u8, name, ".gnu_debuglink")) {
+ const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
+ const debug_filename = mem.sliceTo(@ptrCast([*:0]const u8, gnu_debuglink.ptr), 0);
+ const crc_offset = mem.alignForward(@ptrToInt(&debug_filename[debug_filename.len]) + 1, 4) - @ptrToInt(gnu_debuglink.ptr);
+ const crc_bytes = gnu_debuglink[crc_offset .. crc_offset + 4];
+ separate_debug_crc = mem.readIntSliceNative(u32, crc_bytes);
+ separate_debug_filename = debug_filename;
+ continue;
+ }
+
+ const sections = [_]struct { name: []const u8, out: *?[]const u8 }{
+ .{ .name = ".debug_info", .out = &opt_debug_info },
+ .{ .name = ".debug_abbrev", .out = &opt_debug_abbrev },
+ .{ .name = ".debug_str", .out = &opt_debug_str },
+ .{ .name = ".debug_str_offsets", .out = &opt_debug_str_offsets },
+ .{ .name = ".debug_line", .out = &opt_debug_line },
+ .{ .name = ".debug_line_str", .out = &opt_debug_line_str },
+ .{ .name = ".debug_ranges", .out = &opt_debug_ranges },
+ .{ .name = ".debug_loclists", .out = &opt_debug_loclists },
+ .{ .name = ".debug_rnglists", .out = &opt_debug_rnglists },
+ .{ .name = ".debug_addr", .out = &opt_debug_addr },
+ .{ .name = ".debug_names", .out = &opt_debug_names },
+ .{ .name = ".debug_frame", .out = &opt_debug_frame },
+ };
+
+ var section_index = for (sections, 0..) |section, i| {
+ if (mem.eql(u8, section.name, name)) {
+ break i;
+ }
+ } else continue;
+
+ const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
+ if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) {
+ var section_stream = io.fixedBufferStream(section_bytes);
+ var section_reader = section_stream.reader();
+ const chdr = section_reader.readStruct(elf.Chdr) catch continue;
+
+ // TODO: Support ZSTD
+ if (chdr.ch_type != .ZLIB) continue;
+
+ var zlib_stream = std.compress.zlib.zlibStream(allocator, section_stream.reader()) catch continue;
+ defer zlib_stream.deinit();
+
+ var decompressed_section = try allocator.alloc(u8, chdr.ch_size);
+ errdefer allocator.free(decompressed_section);
+
+ const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
+ assert(read == decompressed_section.len);
+
+ sections[section_index].out.* = decompressed_section;
+ owned_sections[section_index] = decompressed_section;
+ } else {
+ sections[section_index].out.* = section_bytes;
+ }
+ }
+
+ const missing_debug_info =
+ opt_debug_info == null or
+ opt_debug_abbrev == null or
+ opt_debug_str == null or
+ opt_debug_line == null;
+
+ // Attempt to load debug info from an external file
+ // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
+ if (missing_debug_info) {
+ const global_debug_directories = [_][]const u8{
+ "/usr/lib/debug",
+ // TODO: Determine the set of directories used by most distros for this path (check GDB sources)
+ };
+
+ // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
+ if (build_id) |id| blk: {
+ if (id.len < 3) break :blk;
+
+ // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
+ const extension = ".debug";
+ var id_prefix_buf: [2]u8 = undefined;
+ var filename_buf: [38 + extension.len]u8 = undefined;
+
+ _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable;
+ const filename = std.fmt.bufPrint(
+ &filename_buf,
+ "{s}" ++ extension,
+ .{std.fmt.fmtSliceHexLower(id[1..])},
+ ) catch break :blk;
+
+ for (global_debug_directories) |global_directory| {
+ // TODO: joinBuf would be ideal (with a fs.MAX_PATH_BYTES buffer)
+ const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename });
+ defer allocator.free(path);
+ std.debug.print(" Loading external debug info from {s}\n", .{path});
+ return readElfDebugInfo(allocator, path, null, separate_debug_crc) catch continue;
+ }
+ }
+
+ // use the path from .gnu_debuglink, in the search order as gdb
+ if (separate_debug_filename) |separate_filename| blk: {
+ if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo;
+
+ // <cwd>/<gnu_debuglink>
+ if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc)) |debug_info| return debug_info else |_| {}
+
+ // <cwd>/.debug/<gnu_debuglink>
+ {
+ const path = try fs.path.join(allocator, &.{ ".debug", separate_filename });
+ defer allocator.free(path);
+
+ if (readElfDebugInfo(allocator, path, null, separate_debug_crc)) |debug_info| return debug_info else |_| {}
+ }
+
+ var cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+ const cwd_path = fs.cwd().realpath("", &cwd_buf) catch break :blk;
+
+ // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
+ for (global_debug_directories) |global_directory| {
+ const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename });
+ defer allocator.free(path);
+ if (readElfDebugInfo(allocator, path, null, separate_debug_crc)) |debug_info| return debug_info else |_| {}
+ }
}
+
+ return error.MissingDebugInfo;
}
var di = DW.DwarfInfo{
.endian = endian,
- .debug_info = opt_debug_info orelse return error.MissingDebugInfo,
- .debug_abbrev = opt_debug_abbrev orelse return error.MissingDebugInfo,
- .debug_str = opt_debug_str orelse return error.MissingDebugInfo,
+ .debug_info = opt_debug_info.?,
+ .debug_abbrev = opt_debug_abbrev.?,
+ .debug_str = opt_debug_str.?,
.debug_str_offsets = opt_debug_str_offsets,
- .debug_line = opt_debug_line orelse return error.MissingDebugInfo,
+ .debug_line = opt_debug_line.?,
.debug_line_str = opt_debug_line_str,
.debug_ranges = opt_debug_ranges,
.debug_loclists = opt_debug_loclists,
@@ -971,6 +1094,7 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn
.base_address = undefined,
.dwarf = di,
.mapped_memory = mapped_mem,
+ .owned_sections = owned_sections,
};
}
}
@@ -1359,6 +1483,7 @@ pub const DebugInfo = struct {
// Output
base_address: usize = undefined,
name: []const u8 = undefined,
+ build_id: ?[]const u8 = undefined,
} = .{ .address = address };
const CtxTy = @TypeOf(ctx);
@@ -1375,16 +1500,30 @@ pub const DebugInfo = struct {
const seg_start = info.dlpi_addr + phdr.p_vaddr;
const seg_end = seg_start + phdr.p_memsz;
-
if (context.address >= seg_start and context.address < seg_end) {
// Android libc uses NULL instead of an empty string to mark the
// main program
context.name = mem.sliceTo(info.dlpi_name, 0) orelse "";
context.base_address = info.dlpi_addr;
- // Stop the iteration
- return error.Found;
+ break;
}
+ } else return;
+
+ for (info.dlpi_phdr[0..info.dlpi_phnum]) |phdr| {
+ if (phdr.p_type != elf.PT_NOTE) continue;
+
+ const note_bytes = @intToPtr([*]const u8, info.dlpi_addr + phdr.p_vaddr)[0..phdr.p_memsz];
+ const name_size = mem.readIntSliceNative(u32, note_bytes[0..4]);
+ if (name_size != 4) continue;
+ const desc_size = mem.readIntSliceNative(u32, note_bytes[4..8]);
+ const note_type = mem.readIntSliceNative(u32, note_bytes[8..12]);
+ if (note_type != elf.NT_GNU_BUILD_ID) continue;
+ if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
+ context.build_id = note_bytes[16 .. 16 + desc_size];
}
+
+ // Stop the iteration
+ return error.Found;
}
}.callback)) {
return error.MissingDebugInfo;
@@ -1399,18 +1538,7 @@ pub const DebugInfo = struct {
const obj_di = try self.allocator.create(ModuleDebugInfo);
errdefer self.allocator.destroy(obj_di);
- // TODO https://github.com/ziglang/zig/issues/5525
- const copy = if (ctx.name.len > 0)
- fs.cwd().openFile(ctx.name, .{ .intended_io_mode = .blocking })
- else
- fs.openSelfExe(.{ .intended_io_mode = .blocking });
-
- const elf_file = copy catch |err| switch (err) {
- error.FileNotFound => return error.MissingDebugInfo,
- else => return err,
- };
-
- obj_di.* = try readElfDebugInfo(self.allocator, elf_file);
+ obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null);
obj_di.base_address = ctx.base_address;
try self.address_map.putNoClobber(ctx.base_address, obj_di);
@@ -1752,9 +1880,13 @@ pub const ModuleDebugInfo = switch (native_os) {
base_address: usize,
dwarf: DW.DwarfInfo,
mapped_memory: []align(mem.page_size) const u8,
+ owned_sections: [num_sections][]const u8 = [_][]const u8{&.{}} ** num_sections,
+
+ const num_sections = 12;
fn deinit(self: *@This(), allocator: mem.Allocator) void {
self.dwarf.deinit(allocator);
+ for (self.owned_sections) |section| allocator.free(section);
os.munmap(self.mapped_memory);
}