commit aa2aad229c96427f8b9130a3665a1f6ad768ec4c (tree)
parent a55e5363917befcb93575c256a3ce8fc150e0666
Author: Andrew Kelley <andrew@ziglang.org>
Date: Wed, 26 Feb 2020 11:10:38 -0500
Merge pull request #4497 from LemonBoy/do-do-do
The great stack-trace race (Part 1 of N)
Diffstat:
5 files changed, 1522 insertions(+), 1926 deletions(-)
diff --git a/lib/std/debug.zig b/lib/std/debug.zig
@@ -38,6 +38,18 @@ const Module = struct {
checksum_offset: ?usize,
};
+pub const LineInfo = struct {
+ line: u64,
+ column: u64,
+ file_name: []const u8,
+ allocator: ?*mem.Allocator,
+
+ fn deinit(self: LineInfo) void {
+ const allocator = self.allocator orelse return;
+ allocator.free(self.file_name);
+ }
+};
+
/// Tries to write to stderr, unbuffered, and ignores any error returned.
/// Does not append a newline.
var stderr_file: File = undefined;
@@ -378,175 +390,6 @@ pub fn writeCurrentStackTraceWindows(
}
}
-/// TODO once https://github.com/ziglang/zig/issues/3157 is fully implemented,
-/// make this `noasync fn` and remove the individual noasync calls.
-pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: usize, tty_config: TTY.Config) !void {
- if (builtin.os == .windows) {
- return noasync printSourceAtAddressWindows(debug_info, out_stream, address, tty_config);
- }
- if (comptime std.Target.current.isDarwin()) {
- return noasync printSourceAtAddressMacOs(debug_info, out_stream, address, tty_config);
- }
- return noasync printSourceAtAddressPosix(debug_info, out_stream, address, tty_config);
-}
-
-/// TODO resources https://github.com/ziglang/zig/issues/4353
-fn printSourceAtAddressWindows(
- di: *DebugInfo,
- out_stream: var,
- relocated_address: usize,
- tty_config: TTY.Config,
-) !void {
- const allocator = getDebugInfoAllocator();
- const base_address = process.getBaseAddress();
- const relative_address = relocated_address - base_address;
-
- var coff_section: *coff.Section = undefined;
- const mod_index = for (di.sect_contribs) |sect_contrib| {
- if (sect_contrib.Section > di.coff.sections.len) continue;
- // Remember that SectionContribEntry.Section is 1-based.
- coff_section = &di.coff.sections.toSlice()[sect_contrib.Section - 1];
-
- const vaddr_start = coff_section.header.virtual_address + sect_contrib.Offset;
- const vaddr_end = vaddr_start + sect_contrib.Size;
- if (relative_address >= vaddr_start and relative_address < vaddr_end) {
- break sect_contrib.ModuleIndex;
- }
- } else {
- // we have no information to add to the address
- return printLineInfo(out_stream, null, relocated_address, "???", "???", tty_config, printLineFromFileAnyOs);
- };
-
- const mod = &di.modules[mod_index];
- try populateModule(di, mod);
- const obj_basename = fs.path.basename(mod.obj_file_name);
-
- var symbol_i: usize = 0;
- const symbol_name = if (!mod.populated) "???" else while (symbol_i != mod.symbols.len) {
- const prefix = @ptrCast(*pdb.RecordPrefix, &mod.symbols[symbol_i]);
- if (prefix.RecordLen < 2)
- return error.InvalidDebugInfo;
- switch (prefix.RecordKind) {
- .S_LPROC32, .S_GPROC32 => {
- const proc_sym = @ptrCast(*pdb.ProcSym, &mod.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)]);
- const vaddr_start = coff_section.header.virtual_address + proc_sym.CodeOffset;
- const vaddr_end = vaddr_start + proc_sym.CodeSize;
- if (relative_address >= vaddr_start and relative_address < vaddr_end) {
- break mem.toSliceConst(u8, @ptrCast([*:0]u8, proc_sym) + @sizeOf(pdb.ProcSym));
- }
- },
- else => {},
- }
- symbol_i += prefix.RecordLen + @sizeOf(u16);
- if (symbol_i > mod.symbols.len)
- return error.InvalidDebugInfo;
- } else "???";
-
- const subsect_info = mod.subsect_info;
-
- var sect_offset: usize = 0;
- var skip_len: usize = undefined;
- const opt_line_info = subsections: {
- const checksum_offset = mod.checksum_offset orelse break :subsections null;
- while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {
- const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &subsect_info[sect_offset]);
- skip_len = subsect_hdr.Length;
- sect_offset += @sizeOf(pdb.DebugSubsectionHeader);
-
- switch (subsect_hdr.Kind) {
- pdb.DebugSubsectionKind.Lines => {
- var line_index = sect_offset;
-
- const line_hdr = @ptrCast(*pdb.LineFragmentHeader, &subsect_info[line_index]);
- if (line_hdr.RelocSegment == 0) return error.MissingDebugInfo;
- line_index += @sizeOf(pdb.LineFragmentHeader);
- const frag_vaddr_start = coff_section.header.virtual_address + line_hdr.RelocOffset;
- const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize;
-
- if (relative_address >= frag_vaddr_start and relative_address < frag_vaddr_end) {
- // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records)
- // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in,
- // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection.
- const subsection_end_index = sect_offset + subsect_hdr.Length;
-
- while (line_index < subsection_end_index) {
- const block_hdr = @ptrCast(*pdb.LineBlockFragmentHeader, &subsect_info[line_index]);
- line_index += @sizeOf(pdb.LineBlockFragmentHeader);
- const start_line_index = line_index;
-
- const has_column = line_hdr.Flags.LF_HaveColumns;
-
- // All line entries are stored inside their line block by ascending start address.
- // Heuristic: we want to find the last line entry that has a vaddr_start <= relative_address.
- // This is done with a simple linear search.
- var line_i: u32 = 0;
- while (line_i < block_hdr.NumLines) : (line_i += 1) {
- const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[line_index]);
- line_index += @sizeOf(pdb.LineNumberEntry);
-
- const vaddr_start = frag_vaddr_start + line_num_entry.Offset;
- if (relative_address < vaddr_start) {
- break;
- }
- }
-
- // line_i == 0 would mean that no matching LineNumberEntry was found.
- if (line_i > 0) {
- const subsect_index = checksum_offset + block_hdr.NameIndex;
- const chksum_hdr = @ptrCast(*pdb.FileChecksumEntryHeader, &mod.subsect_info[subsect_index]);
- const strtab_offset = @sizeOf(pdb.PDBStringTableHeader) + chksum_hdr.FileNameOffset;
- try di.pdb.string_table.seekTo(strtab_offset);
- const source_file_name = try di.pdb.string_table.readNullTermString(allocator);
-
- const line_entry_idx = line_i - 1;
-
- const column = if (has_column) blk: {
- const start_col_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.NumLines;
- const col_index = start_col_index + @sizeOf(pdb.ColumnNumberEntry) * line_entry_idx;
- const col_num_entry = @ptrCast(*pdb.ColumnNumberEntry, &subsect_info[col_index]);
- break :blk col_num_entry.StartColumn;
- } else 0;
-
- const found_line_index = start_line_index + line_entry_idx * @sizeOf(pdb.LineNumberEntry);
- const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[found_line_index]);
- const flags = @ptrCast(*pdb.LineNumberEntry.Flags, &line_num_entry.Flags);
-
- break :subsections LineInfo{
- .allocator = allocator,
- .file_name = source_file_name,
- .line = flags.Start,
- .column = column,
- };
- }
- }
-
- // Checking that we are not reading garbage after the (possibly) multiple block fragments.
- if (line_index != subsection_end_index) {
- return error.InvalidDebugInfo;
- }
- }
- },
- else => {},
- }
-
- if (sect_offset > subsect_info.len)
- return error.InvalidDebugInfo;
- } else {
- break :subsections null;
- }
- };
-
- try printLineInfo(
- out_stream,
- opt_line_info,
- relocated_address,
- symbol_name,
- obj_basename,
- tty_config,
- printLineFromFileAnyOs,
- );
-}
-
pub const TTY = struct {
pub const Color = enum {
Red,
@@ -618,7 +461,7 @@ pub const TTY = struct {
};
/// TODO resources https://github.com/ziglang/zig/issues/4353
-fn populateModule(di: *DebugInfo, mod: *Module) !void {
+fn populateModule(di: *ModuleDebugInfo, mod: *Module) !void {
if (mod.populated)
return;
const allocator = getDebugInfoAllocator();
@@ -650,7 +493,7 @@ fn populateModule(di: *DebugInfo, mod: *Module) !void {
sect_offset += @sizeOf(pdb.DebugSubsectionHeader);
switch (subsect_hdr.Kind) {
- pdb.DebugSubsectionKind.FileChecksums => {
+ .FileChecksums => {
mod.checksum_offset = sect_offset;
break;
},
@@ -682,41 +525,37 @@ fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const Mach
return null;
}
-fn printSourceAtAddressMacOs(di: *DebugInfo, out_stream: var, address: usize, tty_config: TTY.Config) !void {
- const base_addr = process.getBaseAddress();
- const adjusted_addr = 0x100000000 + (address - base_addr);
-
- const symbol = machoSearchSymbols(di.symbols, adjusted_addr) orelse {
- return printLineInfo(out_stream, null, address, "???", "???", tty_config, printLineFromFileAnyOs);
- };
-
- const symbol_name = mem.toSliceConst(u8, @ptrCast([*:0]const u8, di.strings.ptr + symbol.nlist.n_strx));
- const compile_unit_name = if (symbol.ofile) |ofile| blk: {
- const ofile_path = mem.toSliceConst(u8, @ptrCast([*:0]const u8, di.strings.ptr + ofile.n_strx));
- break :blk fs.path.basename(ofile_path);
- } else "???";
-
- const line_info = getLineNumberInfoMacOs(di, symbol.*, adjusted_addr) catch |err| switch (err) {
- error.MissingDebugInfo, error.InvalidDebugInfo => null,
+/// TODO resources https://github.com/ziglang/zig/issues/4353
+pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: usize, tty_config: TTY.Config) !void {
+ const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => {
+ return printLineInfo(
+ out_stream,
+ null,
+ address,
+ "???",
+ "???",
+ tty_config,
+ printLineFromFileAnyOs,
+ );
+ },
else => return err,
};
- defer if (line_info) |li| li.deinit();
- try printLineInfo(
+ const symbol_info = try module.getSymbolAtAddress(address);
+ defer symbol_info.deinit();
+
+ return printLineInfo(
out_stream,
- line_info,
+ symbol_info.line_info,
address,
- symbol_name,
- compile_unit_name,
+ symbol_info.symbol_name,
+ symbol_info.compile_unit_name,
tty_config,
printLineFromFileAnyOs,
);
}
-pub fn printSourceAtAddressPosix(debug_info: *DebugInfo, out_stream: var, address: usize, tty_config: TTY.Config) !void {
- return debug_info.printSourceAtAddress(out_stream, address, tty_config, printLineFromFileAnyOs);
-}
-
fn printLineInfo(
out_stream: var,
line_info: ?LineInfo,
@@ -772,29 +611,32 @@ pub const OpenSelfDebugInfoError = error{
/// TODO resources https://github.com/ziglang/zig/issues/4353
/// TODO once https://github.com/ziglang/zig/issues/3157 is fully implemented,
/// make this `noasync fn` and remove the individual noasync calls.
-pub fn openSelfDebugInfo(allocator: *mem.Allocator) !DebugInfo {
+pub fn openSelfDebugInfo(allocator: *mem.Allocator) anyerror!DebugInfo {
if (builtin.strip_debug_info)
return error.MissingDebugInfo;
if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) {
return noasync root.os.debug.openSelfDebugInfo(allocator);
}
- if (builtin.os == .windows) {
- return noasync openSelfDebugInfoWindows(allocator);
- }
- if (comptime std.Target.current.isDarwin()) {
- return noasync openSelfDebugInfoMacOs(allocator);
+ switch (builtin.os) {
+ .linux,
+ .freebsd,
+ .macosx,
+ .windows,
+ => return DebugInfo.init(allocator),
+ else => @compileError("openSelfDebugInfo unsupported for this platform"),
}
- return noasync openSelfDebugInfoPosix(allocator);
}
-fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo {
- const self_file = try fs.openSelfExe();
- defer self_file.close();
+/// TODO resources https://github.com/ziglang/zig/issues/4353
+fn openCoffDebugInfo(allocator: *mem.Allocator, coff_file_path: [:0]const u16) !ModuleDebugInfo {
+ const coff_file = try std.fs.openFileAbsoluteW(coff_file_path.ptr, .{});
+ errdefer coff_file.close();
const coff_obj = try allocator.create(coff.Coff);
- coff_obj.* = coff.Coff.init(allocator, self_file);
+ coff_obj.* = coff.Coff.init(allocator, coff_file);
- var di = DebugInfo{
+ var di = ModuleDebugInfo{
+ .base_address = undefined,
.coff = coff_obj,
.pdb = undefined,
.sect_contribs = undefined,
@@ -958,36 +800,21 @@ fn readSparseBitVector(stream: var, allocator: *mem.Allocator) ![]usize {
return list.toOwnedSlice();
}
-fn findDwarfSectionFromElf(elf_file: *elf.Elf, name: []const u8) !?DwarfInfo.Section {
- const elf_header = (try elf_file.findSection(name)) orelse return null;
- return DwarfInfo.Section{
- .offset = elf_header.sh_offset,
- .size = elf_header.sh_size,
- };
-}
-
-/// Initialize DWARF info. The caller has the responsibility to initialize most
-/// the DwarfInfo fields before calling. These fields can be left undefined:
-/// * abbrev_table_list
-/// * compile_unit_list
-pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: *mem.Allocator) !void {
- di.abbrev_table_list = ArrayList(AbbrevTableHeader).init(allocator);
- di.compile_unit_list = ArrayList(CompileUnit).init(allocator);
- di.func_list = ArrayList(Func).init(allocator);
- try di.scanAllFunctions();
- try di.scanAllCompileUnits();
+fn chopSlice(ptr: []const u8, offset: u64, size: u64) ![]const u8 {
+ const start = try math.cast(usize, offset);
+ const end = start + try math.cast(usize, size);
+ return ptr[start..end];
}
/// TODO resources https://github.com/ziglang/zig/issues/4353
-pub fn openElfDebugInfo(
- allocator: *mem.Allocator,
- data: []u8,
-) !DwarfInfo {
- var seekable_stream = io.SliceSeekableInStream.init(data);
+pub fn openElfDebugInfo(allocator: *mem.Allocator, elf_file_path: []const u8) !ModuleDebugInfo {
+ const mapped_mem = try mapWholeFile(elf_file_path);
+
+ var seekable_stream = io.SliceSeekableInStream.init(mapped_mem);
var efile = try elf.Elf.openStream(
allocator,
- @ptrCast(*DwarfSeekableStream, &seekable_stream.seekable_stream),
- @ptrCast(*DwarfInStream, &seekable_stream.stream),
+ @ptrCast(*DW.DwarfSeekableStream, &seekable_stream.seekable_stream),
+ @ptrCast(*DW.DwarfInStream, &seekable_stream.stream),
);
defer efile.close();
@@ -1001,66 +828,57 @@ pub fn openElfDebugInfo(
return error.MissingDebugInfo;
const opt_debug_ranges = try efile.findSection(".debug_ranges");
- var di = DwarfInfo{
+ var di = DW.DwarfInfo{
.endian = efile.endian,
- .debug_info = (data[@intCast(usize, debug_info.sh_offset)..@intCast(usize, debug_info.sh_offset + debug_info.sh_size)]),
- .debug_abbrev = (data[@intCast(usize, debug_abbrev.sh_offset)..@intCast(usize, debug_abbrev.sh_offset + debug_abbrev.sh_size)]),
- .debug_str = (data[@intCast(usize, debug_str.sh_offset)..@intCast(usize, debug_str.sh_offset + debug_str.sh_size)]),
- .debug_line = (data[@intCast(usize, debug_line.sh_offset)..@intCast(usize, debug_line.sh_offset + debug_line.sh_size)]),
+ .debug_info = try chopSlice(mapped_mem, debug_info.sh_offset, debug_info.sh_size),
+ .debug_abbrev = try chopSlice(mapped_mem, debug_abbrev.sh_offset, debug_abbrev.sh_size),
+ .debug_str = try chopSlice(mapped_mem, debug_str.sh_offset, debug_str.sh_size),
+ .debug_line = try chopSlice(mapped_mem, debug_line.sh_offset, debug_line.sh_size),
.debug_ranges = if (opt_debug_ranges) |debug_ranges|
- data[@intCast(usize, debug_ranges.sh_offset)..@intCast(usize, debug_ranges.sh_offset + debug_ranges.sh_size)]
+ try chopSlice(mapped_mem, debug_ranges.sh_offset, debug_ranges.sh_size)
else
null,
};
- try openDwarfDebugInfo(&di, allocator);
- return di;
+ try DW.openDwarfDebugInfo(&di, allocator);
+
+ return ModuleDebugInfo{
+ .base_address = undefined,
+ .dwarf = di,
+ .mapped_memory = mapped_mem,
+ };
}
/// TODO resources https://github.com/ziglang/zig/issues/4353
-fn openSelfDebugInfoPosix(allocator: *mem.Allocator) !DwarfInfo {
- var exe_file = try fs.openSelfExe();
- errdefer exe_file.close();
+fn openMachODebugInfo(allocator: *mem.Allocator, macho_file_path: []const u8) !ModuleDebugInfo {
+ const mapped_mem = try mapWholeFile(macho_file_path);
- const exe_len = math.cast(usize, try exe_file.getEndPos()) catch
- return error.DebugInfoTooLarge;
- const exe_mmap = try os.mmap(
- null,
- exe_len,
- os.PROT_READ,
- os.MAP_SHARED,
- exe_file.handle,
- 0,
+ const hdr = @ptrCast(
+ *const macho.mach_header_64,
+ @alignCast(@alignOf(macho.mach_header_64), mapped_mem.ptr),
);
- errdefer os.munmap(exe_mmap);
-
- return openElfDebugInfo(allocator, exe_mmap);
-}
-
-/// TODO resources https://github.com/ziglang/zig/issues/4353
-fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo {
- const hdr = &std.c._mh_execute_header;
- assert(hdr.magic == std.macho.MH_MAGIC_64);
+ if (hdr.magic != macho.MH_MAGIC_64)
+ return error.InvalidDebugInfo;
- const hdr_base = @ptrCast([*]u8, hdr);
+ const hdr_base = @ptrCast([*]const u8, hdr);
var ptr = hdr_base + @sizeOf(macho.mach_header_64);
var ncmd: u32 = hdr.ncmds;
const symtab = while (ncmd != 0) : (ncmd -= 1) {
- const lc = @ptrCast(*std.macho.load_command, ptr);
+ const lc = @ptrCast(*const std.macho.load_command, ptr);
switch (lc.cmd) {
- std.macho.LC_SYMTAB => break @ptrCast(*std.macho.symtab_command, ptr),
+ std.macho.LC_SYMTAB => break @ptrCast(*const std.macho.symtab_command, ptr),
else => {},
}
ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize);
} else {
return error.MissingDebugInfo;
};
- const syms = @ptrCast([*]macho.nlist_64, @alignCast(@alignOf(macho.nlist_64), hdr_base + symtab.symoff))[0..symtab.nsyms];
- const strings = @ptrCast([*]u8, hdr_base + symtab.stroff)[0..symtab.strsize];
+ const syms = @ptrCast([*]const macho.nlist_64, @alignCast(@alignOf(macho.nlist_64), hdr_base + symtab.symoff))[0..symtab.nsyms];
+ const strings = @ptrCast([*]const u8, hdr_base + symtab.stroff)[0..symtab.strsize :0];
const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
- var ofile: ?*macho.nlist_64 = null;
+ var ofile: ?*const macho.nlist_64 = null;
var reloc: u64 = 0;
var symbol_index: usize = 0;
var last_len: u64 = 0;
@@ -1108,8 +926,10 @@ fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo {
// This sort is so that we can binary search later.
std.sort.sort(MachoSymbol, symbols, MachoSymbol.addressLessThan);
- return DebugInfo{
- .ofiles = DebugInfo.OFileTable.init(allocator),
+ return ModuleDebugInfo{
+ .base_address = undefined,
+ .mapped_memory = mapped_mem,
+ .ofiles = ModuleDebugInfo.OFileTable.init(allocator),
.symbols = symbols,
.strings = strings,
};
@@ -1148,8 +968,8 @@ fn printLineFromFileAnyOs(out_stream: var, line_info: LineInfo) !void {
}
const MachoSymbol = struct {
- nlist: *macho.nlist_64,
- ofile: ?*macho.nlist_64,
+ nlist: *const macho.nlist_64,
+ ofile: ?*const macho.nlist_64,
reloc: u64,
/// Returns the address from the macho file
@@ -1162,1057 +982,614 @@ const MachoSymbol = struct {
}
};
-pub const DwarfSeekableStream = io.SeekableStream(anyerror, anyerror);
-pub const DwarfInStream = io.InStream(anyerror);
-
-pub const DwarfInfo = struct {
- endian: builtin.Endian,
- // No memory is owned by the DwarfInfo
- debug_info: []u8,
- debug_abbrev: []u8,
- debug_str: []u8,
- debug_line: []u8,
- debug_ranges: ?[]u8,
- // Filled later by the initializer
- abbrev_table_list: ArrayList(AbbrevTableHeader) = undefined,
- compile_unit_list: ArrayList(CompileUnit) = undefined,
- func_list: ArrayList(Func) = undefined,
-
- pub fn allocator(self: DwarfInfo) *mem.Allocator {
- return self.abbrev_table_list.allocator;
- }
+fn mapWholeFile(path: []const u8) ![]const u8 {
+ const file = try fs.openFileAbsolute(path, .{});
+ defer file.close();
- /// This function works in freestanding mode.
- /// fn printLineFromFile(out_stream: var, line_info: LineInfo) !void
- pub fn printSourceAtAddress(
- self: *DwarfInfo,
- out_stream: var,
- address: usize,
- tty_config: TTY.Config,
- comptime printLineFromFile: var,
- ) !void {
- const compile_unit = self.findCompileUnit(address) catch {
- return printLineInfo(out_stream, null, address, "???", "???", tty_config, printLineFromFile);
- };
+ const file_len = try math.cast(usize, try file.getEndPos());
+ const mapped_mem = try os.mmap(
+ null,
+ file_len,
+ os.PROT_READ,
+ os.MAP_SHARED,
+ file.handle,
+ 0,
+ );
+ errdefer os.munmap(mapped_mem);
- const compile_unit_name = try compile_unit.die.getAttrString(self, DW.AT_name);
- const symbol_name = self.getSymbolName(address) orelse "???";
- const line_info = self.getLineNumberInfo(compile_unit.*, address) catch |err| switch (err) {
- error.MissingDebugInfo, error.InvalidDebugInfo => null,
- else => return err,
- };
- defer if (line_info) |li| li.deinit();
-
- try printLineInfo(
- out_stream,
- line_info,
- address,
- symbol_name,
- compile_unit_name,
- tty_config,
- printLineFromFile,
- );
- }
+ return mapped_mem;
+}
- fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 {
- for (di.func_list.toSliceConst()) |*func| {
- if (func.pc_range) |range| {
- if (address >= range.start and address < range.end) {
- return func.name;
- }
- }
- }
+pub const DebugInfo = struct {
+ allocator: *mem.Allocator,
+ address_map: std.AutoHashMap(usize, *ModuleDebugInfo),
- return null;
+ pub fn init(allocator: *mem.Allocator) DebugInfo {
+ return DebugInfo{
+ .allocator = allocator,
+ .address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator),
+ };
}
- fn scanAllFunctions(di: *DwarfInfo) !void {
- var s = io.SliceSeekableInStream.init(di.debug_info);
- var this_unit_offset: u64 = 0;
-
- while (true) {
- s.seekable_stream.seekTo(this_unit_offset) catch |err| switch (err) {
- error.EndOfStream => return,
- else => return err,
- };
-
- var is_64: bool = undefined;
- const unit_length = try readInitialLength(@TypeOf(s.stream.readFn).ReturnType.ErrorSet, &s.stream, &is_64);
- if (unit_length == 0) return;
- const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4));
-
- const version = try s.stream.readInt(u16, di.endian);
- if (version < 2 or version > 5) return error.InvalidDebugInfo;
-
- const debug_abbrev_offset = if (is_64) try s.stream.readInt(u64, di.endian) else try s.stream.readInt(u32, di.endian);
-
- const address_size = try s.stream.readByte();
- if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo;
-
- const compile_unit_pos = try s.seekable_stream.getPos();
- const abbrev_table = try di.getAbbrevTable(debug_abbrev_offset);
-
- try s.seekable_stream.seekTo(compile_unit_pos);
-
- const next_unit_pos = this_unit_offset + next_offset;
-
- while ((try s.seekable_stream.getPos()) < next_unit_pos) {
- const die_obj = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse continue;
- const after_die_offset = try s.seekable_stream.getPos();
-
- switch (die_obj.tag_id) {
- DW.TAG_subprogram, DW.TAG_inlined_subroutine, DW.TAG_subroutine, DW.TAG_entry_point => {
- const fn_name = x: {
- var depth: i32 = 3;
- var this_die_obj = die_obj;
- // Prenvent endless loops
- while (depth > 0) : (depth -= 1) {
- if (this_die_obj.getAttr(DW.AT_name)) |_| {
- const name = try this_die_obj.getAttrString(di, DW.AT_name);
- break :x name;
- } else if (this_die_obj.getAttr(DW.AT_abstract_origin)) |ref| {
- // Follow the DIE it points to and repeat
- const ref_offset = try this_die_obj.getAttrRef(DW.AT_abstract_origin);
- if (ref_offset > next_offset) return error.InvalidDebugInfo;
- try s.seekable_stream.seekTo(this_unit_offset + ref_offset);
- this_die_obj = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse return error.InvalidDebugInfo;
- } else if (this_die_obj.getAttr(DW.AT_specification)) |ref| {
- // Follow the DIE it points to and repeat
- const ref_offset = try this_die_obj.getAttrRef(DW.AT_specification);
- if (ref_offset > next_offset) return error.InvalidDebugInfo;
- try s.seekable_stream.seekTo(this_unit_offset + ref_offset);
- this_die_obj = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse return error.InvalidDebugInfo;
- } else {
- break :x null;
- }
- }
-
- break :x null;
- };
-
- const pc_range = x: {
- if (die_obj.getAttrAddr(DW.AT_low_pc)) |low_pc| {
- if (die_obj.getAttr(DW.AT_high_pc)) |high_pc_value| {
- const pc_end = switch (high_pc_value.*) {
- FormValue.Address => |value| value,
- FormValue.Const => |value| b: {
- const offset = try value.asUnsignedLe();
- break :b (low_pc + offset);
- },
- else => return error.InvalidDebugInfo,
- };
- break :x PcRange{
- .start = low_pc,
- .end = pc_end,
- };
- } else {
- break :x null;
- }
- } else |err| {
- if (err != error.MissingDebugInfo) return err;
- break :x null;
- }
- };
-
- try di.func_list.append(Func{
- .name = fn_name,
- .pc_range = pc_range,
- });
- },
- else => {},
- }
-
- try s.seekable_stream.seekTo(after_die_offset);
- }
-
- this_unit_offset += next_offset;
- }
+ pub fn deinit(self: *DebugInfo) void {
+ // TODO: resources https://github.com/ziglang/zig/issues/4353
+ self.address_map.deinit();
}
- fn scanAllCompileUnits(di: *DwarfInfo) !void {
- var s = io.SliceSeekableInStream.init(di.debug_info);
- var this_unit_offset: u64 = 0;
-
- while (true) {
- s.seekable_stream.seekTo(this_unit_offset) catch |err| switch (err) {
- error.EndOfStream => return,
- else => return err,
- };
-
- var is_64: bool = undefined;
- const unit_length = try readInitialLength(@TypeOf(s.stream.readFn).ReturnType.ErrorSet, &s.stream, &is_64);
- if (unit_length == 0) return;
- const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4));
+ pub fn getModuleForAddress(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
+ if (comptime std.Target.current.isDarwin())
+ return self.lookupModuleDyld(address)
+ else if (builtin.os == .windows)
+ return self.lookupModuleWin32(address)
+ else
+ return self.lookupModuleDl(address);
+ }
- const version = try s.stream.readInt(u16, di.endian);
- if (version < 2 or version > 5) return error.InvalidDebugInfo;
+ fn lookupModuleDyld(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
+ const image_count = std.c._dyld_image_count();
- const debug_abbrev_offset = if (is_64) try s.stream.readInt(u64, di.endian) else try s.stream.readInt(u32, di.endian);
+ var i: u32 = 0;
+ while (i < image_count) : (i += 1) {
+ const base_address = std.c._dyld_get_image_vmaddr_slide(i);
- const address_size = try s.stream.readByte();
- if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo;
+ if (address < base_address) continue;
- const compile_unit_pos = try s.seekable_stream.getPos();
- const abbrev_table = try di.getAbbrevTable(debug_abbrev_offset);
+ const header = std.c._dyld_get_image_header(i) orelse continue;
+ // The array of load commands is right after the header
+ var cmd_ptr = @intToPtr([*]u8, @ptrToInt(header) + @sizeOf(macho.mach_header_64));
- try s.seekable_stream.seekTo(compile_unit_pos);
+ var cmds = header.ncmds;
+ while (cmds != 0) : (cmds -= 1) {
+ const lc = @ptrCast(
+ *macho.load_command,
+ @alignCast(@alignOf(macho.load_command), cmd_ptr),
+ );
+ cmd_ptr += lc.cmdsize;
+ if (lc.cmd != macho.LC_SEGMENT_64) continue;
- const compile_unit_die = try di.allocator().create(Die);
- compile_unit_die.* = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse return error.InvalidDebugInfo;
+ const segment_cmd = @ptrCast(
+ *const std.macho.segment_command_64,
+ @alignCast(@alignOf(std.macho.segment_command_64), lc),
+ );
- if (compile_unit_die.tag_id != DW.TAG_compile_unit) return error.InvalidDebugInfo;
+ const rebased_address = address - base_address;
+ const seg_start = segment_cmd.vmaddr;
+ const seg_end = seg_start + segment_cmd.vmsize;
- const pc_range = x: {
- if (compile_unit_die.getAttrAddr(DW.AT_low_pc)) |low_pc| {
- if (compile_unit_die.getAttr(DW.AT_high_pc)) |high_pc_value| {
- const pc_end = switch (high_pc_value.*) {
- FormValue.Address => |value| value,
- FormValue.Const => |value| b: {
- const offset = try value.asUnsignedLe();
- break :b (low_pc + offset);
- },
- else => return error.InvalidDebugInfo,
- };
- break :x PcRange{
- .start = low_pc,
- .end = pc_end,
- };
- } else {
- break :x null;
+ if (rebased_address >= seg_start and rebased_address < seg_end) {
+ if (self.address_map.getValue(base_address)) |obj_di| {
+ return obj_di;
}
- } else |err| {
- if (err != error.MissingDebugInfo) return err;
- break :x null;
- }
- };
-
- try di.compile_unit_list.append(CompileUnit{
- .version = version,
- .is_64 = is_64,
- .pc_range = pc_range,
- .die = compile_unit_die,
- });
- this_unit_offset += next_offset;
- }
- }
+ const obj_di = try self.allocator.create(ModuleDebugInfo);
+ errdefer self.allocator.destroy(obj_di);
- fn findCompileUnit(di: *DwarfInfo, target_address: u64) !*const CompileUnit {
- for (di.compile_unit_list.toSlice()) |*compile_unit| {
- if (compile_unit.pc_range) |range| {
- if (target_address >= range.start and target_address < range.end) return compile_unit;
- }
- if (di.debug_ranges) |debug_ranges| {
- if (compile_unit.die.getAttrSecOffset(DW.AT_ranges)) |ranges_offset| {
- var s = io.SliceSeekableInStream.init(debug_ranges);
-
- // All the addresses in the list are relative to the value
- // specified by DW_AT_low_pc or to some other value encoded
- // in the list itself.
- // If no starting value is specified use zero.
- var base_address = compile_unit.die.getAttrAddr(DW.AT_low_pc) catch |err| switch (err) {
- error.MissingDebugInfo => 0,
+ const macho_path = mem.toSliceConst(u8, std.c._dyld_get_image_name(i));
+ obj_di.* = openMachODebugInfo(self.allocator, macho_path) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
else => return err,
};
+ obj_di.base_address = base_address;
- try s.seekable_stream.seekTo(ranges_offset);
+ try self.address_map.putNoClobber(base_address, obj_di);
- while (true) {
- const begin_addr = try s.stream.readIntLittle(usize);
- const end_addr = try s.stream.readIntLittle(usize);
- if (begin_addr == 0 and end_addr == 0) {
- break;
- }
- // This entry selects a new value for the base address
- if (begin_addr == maxInt(usize)) {
- base_address = end_addr;
- continue;
- }
- if (target_address >= base_address + begin_addr and target_address < base_address + end_addr) {
- return compile_unit;
- }
- }
- } else |err| {
- if (err != error.MissingDebugInfo) return err;
- continue;
+ return obj_di;
}
}
}
- return error.MissingDebugInfo;
- }
-
- /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found,
- /// seeks in the stream and parses it.
- fn getAbbrevTable(di: *DwarfInfo, abbrev_offset: u64) !*const AbbrevTable {
- for (di.abbrev_table_list.toSlice()) |*header| {
- if (header.offset == abbrev_offset) {
- return &header.table;
- }
- }
- try di.abbrev_table_list.append(AbbrevTableHeader{
- .offset = abbrev_offset,
- .table = try di.parseAbbrevTable(abbrev_offset),
- });
- return &di.abbrev_table_list.items[di.abbrev_table_list.len - 1].table;
- }
-
- fn parseAbbrevTable(di: *DwarfInfo, offset: u64) !AbbrevTable {
- var s = io.SliceSeekableInStream.init(di.debug_abbrev);
-
- try s.seekable_stream.seekTo(offset);
- var result = AbbrevTable.init(di.allocator());
- errdefer result.deinit();
- while (true) {
- const abbrev_code = try leb.readULEB128(u64, &s.stream);
- if (abbrev_code == 0) return result;
- try result.append(AbbrevTableEntry{
- .abbrev_code = abbrev_code,
- .tag_id = try leb.readULEB128(u64, &s.stream),
- .has_children = (try s.stream.readByte()) == DW.CHILDREN_yes,
- .attrs = ArrayList(AbbrevAttr).init(di.allocator()),
- });
- const attrs = &result.items[result.len - 1].attrs;
-
- while (true) {
- const attr_id = try leb.readULEB128(u64, &s.stream);
- const form_id = try leb.readULEB128(u64, &s.stream);
- if (attr_id == 0 and form_id == 0) break;
- try attrs.append(AbbrevAttr{
- .attr_id = attr_id,
- .form_id = form_id,
- });
- }
- }
- }
-
- fn parseDie(di: *DwarfInfo, in_stream: var, abbrev_table: *const AbbrevTable, is_64: bool) !?Die {
- const abbrev_code = try leb.readULEB128(u64, in_stream);
- if (abbrev_code == 0) return null;
- const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return error.InvalidDebugInfo;
- var result = Die{
- .tag_id = table_entry.tag_id,
- .has_children = table_entry.has_children,
- .attrs = ArrayList(Die.Attr).init(di.allocator()),
- };
- try result.attrs.resize(table_entry.attrs.len);
- for (table_entry.attrs.toSliceConst()) |attr, i| {
- result.attrs.items[i] = Die.Attr{
- .id = attr.attr_id,
- .value = try parseFormValue(di.allocator(), in_stream, attr.form_id, is_64),
- };
- }
- return result;
+ return error.MissingDebugInfo;
}
- fn getLineNumberInfo(di: *DwarfInfo, compile_unit: CompileUnit, target_address: usize) !LineInfo {
- var s = io.SliceSeekableInStream.init(di.debug_line);
-
- const compile_unit_cwd = try compile_unit.die.getAttrString(di, DW.AT_comp_dir);
- const line_info_offset = try compile_unit.die.getAttrSecOffset(DW.AT_stmt_list);
+ fn lookupModuleWin32(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
+ const process_handle = windows.kernel32.GetCurrentProcess();
- try s.seekable_stream.seekTo(line_info_offset);
+ // Find how many modules are actually loaded
+ var dummy: windows.HMODULE = undefined;
+ var bytes_needed: windows.DWORD = undefined;
+ if (windows.kernel32.K32EnumProcessModules(
+ process_handle,
+ @ptrCast([*]windows.HMODULE, &dummy),
+ 0,
+ &bytes_needed,
+ ) == 0)
+ return error.MissingDebugInfo;
- var is_64: bool = undefined;
- const unit_length = try readInitialLength(@TypeOf(s.stream.readFn).ReturnType.ErrorSet, &s.stream, &is_64);
- if (unit_length == 0) {
+ const needed_modules = bytes_needed / @sizeOf(windows.HMODULE);
+
+ // Fetch the complete module list
+ var modules = try self.allocator.alloc(windows.HMODULE, needed_modules);
+ defer self.allocator.free(modules);
+ if (windows.kernel32.K32EnumProcessModules(
+ process_handle,
+ modules.ptr,
+ try math.cast(windows.DWORD, modules.len * @sizeOf(windows.HMODULE)),
+ &bytes_needed,
+ ) == 0)
return error.MissingDebugInfo;
- }
- const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4));
- const version = try s.stream.readInt(u16, di.endian);
- // TODO support 3 and 5
- if (version != 2 and version != 4) return error.InvalidDebugInfo;
+ // There's an unavoidable TOCTOU problem here, the module list may have
+ // changed between the two EnumProcessModules call.
+ // Pick the smallest amount of elements to avoid processing garbage.
+ const needed_modules_after = bytes_needed / @sizeOf(windows.HMODULE);
+ const loaded_modules = math.min(needed_modules, needed_modules_after);
+
+ for (modules[0..loaded_modules]) |module| {
+ var info: windows.MODULEINFO = undefined;
+ if (windows.kernel32.K32GetModuleInformation(
+ process_handle,
+ module,
+ &info,
+ @sizeOf(@TypeOf(info)),
+ ) == 0)
+ return error.MissingDebugInfo;
- const prologue_length = if (is_64) try s.stream.readInt(u64, di.endian) else try s.stream.readInt(u32, di.endian);
- const prog_start_offset = (try s.seekable_stream.getPos()) + prologue_length;
+ const seg_start = @ptrToInt(info.lpBaseOfDll);
+ const seg_end = seg_start + info.SizeOfImage;
- const minimum_instruction_length = try s.stream.readByte();
- if (minimum_instruction_length == 0) return error.InvalidDebugInfo;
+ if (address >= seg_start and address < seg_end) {
+ if (self.address_map.getValue(seg_start)) |obj_di| {
+ return obj_di;
+ }
- if (version >= 4) {
- // maximum_operations_per_instruction
- _ = try s.stream.readByte();
+ var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
+ // openFileAbsoluteW requires the prefix to be present
+ mem.copy(u16, name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' });
+ const len = windows.kernel32.K32GetModuleFileNameExW(
+ process_handle,
+ module,
+ @ptrCast(windows.LPWSTR, &name_buffer[4]),
+ windows.PATH_MAX_WIDE,
+ );
+ assert(len > 0);
+
+ const obj_di = try self.allocator.create(ModuleDebugInfo);
+ errdefer self.allocator.destroy(obj_di);
+
+ obj_di.* = openCoffDebugInfo(self.allocator, name_buffer[0..:0]) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
+ else => return err,
+ };
+ obj_di.base_address = seg_start;
+
+ try self.address_map.putNoClobber(seg_start, obj_di);
+
+ return obj_di;
+ }
}
- const default_is_stmt = (try s.stream.readByte()) != 0;
- const line_base = try s.stream.readByteSigned();
-
- const line_range = try s.stream.readByte();
- if (line_range == 0) return error.InvalidDebugInfo;
-
- const opcode_base = try s.stream.readByte();
+ return error.MissingDebugInfo;
+ }
- const standard_opcode_lengths = try di.allocator().alloc(u8, opcode_base - 1);
+ fn lookupModuleDl(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
+ var ctx: struct {
+ // Input
+ address: usize,
+ // Output
+ base_address: usize = undefined,
+ name: []const u8 = undefined,
+ } = .{ .address = address };
+ const CtxTy = @TypeOf(ctx);
+
+ if (os.dl_iterate_phdr(&ctx, anyerror, struct {
+ fn callback(info: *os.dl_phdr_info, size: usize, context: *CtxTy) !void {
+ // The base address is too high
+ if (context.address < info.dlpi_addr)
+ return;
- {
- var i: usize = 0;
- while (i < opcode_base - 1) : (i += 1) {
- standard_opcode_lengths[i] = try s.stream.readByte();
+ const phdrs = info.dlpi_phdr[0..info.dlpi_phnum];
+ for (phdrs) |*phdr| {
+ if (phdr.p_type != elf.PT_LOAD) continue;
+
+ 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 = if (info.dlpi_name) |dlpi_name|
+ mem.toSliceConst(u8, dlpi_name)
+ else
+ "";
+ context.base_address = info.dlpi_addr;
+ // Stop the iteration
+ return error.Found;
+ }
+ }
}
+ }.callback)) {
+ return error.MissingDebugInfo;
+ } else |err| switch (err) {
+ error.Found => {},
+ else => return error.MissingDebugInfo,
}
- var include_directories = ArrayList([]u8).init(di.allocator());
- try include_directories.append(compile_unit_cwd);
- while (true) {
- const dir = try readStringRaw(di.allocator(), &s.stream);
- if (dir.len == 0) break;
- try include_directories.append(dir);
+ if (self.address_map.getValue(ctx.base_address)) |obj_di| {
+ return obj_di;
}
- var file_entries = ArrayList(FileEntry).init(di.allocator());
- var prog = LineNumberProgram.init(default_is_stmt, include_directories.toSliceConst(), &file_entries, target_address);
-
- while (true) {
- const file_name = try readStringRaw(di.allocator(), &s.stream);
- if (file_name.len == 0) break;
- const dir_index = try leb.readULEB128(usize, &s.stream);
- const mtime = try leb.readULEB128(usize, &s.stream);
- const len_bytes = try leb.readULEB128(usize, &s.stream);
- try file_entries.append(FileEntry{
- .file_name = file_name,
- .dir_index = dir_index,
- .mtime = mtime,
- .len_bytes = len_bytes,
- });
- }
+ const elf_path = if (ctx.name.len > 0)
+ ctx.name
+ else blk: {
+ var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
+ break :blk try fs.selfExePath(&buf);
+ };
- try s.seekable_stream.seekTo(prog_start_offset);
+ const obj_di = try self.allocator.create(ModuleDebugInfo);
+ errdefer self.allocator.destroy(obj_di);
- const next_unit_pos = line_info_offset + next_offset;
+ obj_di.* = openElfDebugInfo(self.allocator, elf_path) catch |err| switch (err) {
+ error.FileNotFound => return error.MissingDebugInfo,
+ else => return err,
+ };
+ obj_di.base_address = ctx.base_address;
- while ((try s.seekable_stream.getPos()) < next_unit_pos) {
- const opcode = try s.stream.readByte();
+ try self.address_map.putNoClobber(ctx.base_address, obj_di);
- if (opcode == DW.LNS_extended_op) {
- const op_size = try leb.readULEB128(u64, &s.stream);
- if (op_size < 1) return error.InvalidDebugInfo;
- var sub_op = try s.stream.readByte();
- switch (sub_op) {
- DW.LNE_end_sequence => {
- prog.end_sequence = true;
- if (try prog.checkLineMatch()) |info| return info;
- prog.reset();
- },
- DW.LNE_set_address => {
- const addr = try s.stream.readInt(usize, di.endian);
- prog.address = addr;
- },
- DW.LNE_define_file => {
- const file_name = try readStringRaw(di.allocator(), &s.stream);
- const dir_index = try leb.readULEB128(usize, &s.stream);
- const mtime = try leb.readULEB128(usize, &s.stream);
- const len_bytes = try leb.readULEB128(usize, &s.stream);
- try file_entries.append(FileEntry{
- .file_name = file_name,
- .dir_index = dir_index,
- .mtime = mtime,
- .len_bytes = len_bytes,
- });
- },
- else => {
- const fwd_amt = math.cast(isize, op_size - 1) catch return error.InvalidDebugInfo;
- try s.seekable_stream.seekBy(fwd_amt);
- },
- }
- } else if (opcode >= opcode_base) {
- // special opcodes
- const adjusted_opcode = opcode - opcode_base;
- const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range);
- const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range);
- prog.line += inc_line;
- prog.address += inc_addr;
- if (try prog.checkLineMatch()) |info| return info;
- prog.basic_block = false;
- } else {
- switch (opcode) {
- DW.LNS_copy => {
- if (try prog.checkLineMatch()) |info| return info;
- prog.basic_block = false;
- },
- DW.LNS_advance_pc => {
- const arg = try leb.readULEB128(usize, &s.stream);
- prog.address += arg * minimum_instruction_length;
- },
- DW.LNS_advance_line => {
- const arg = try leb.readILEB128(i64, &s.stream);
- prog.line += arg;
- },
- DW.LNS_set_file => {
- const arg = try leb.readULEB128(usize, &s.stream);
- prog.file = arg;
- },
- DW.LNS_set_column => {
- const arg = try leb.readULEB128(u64, &s.stream);
- prog.column = arg;
- },
- DW.LNS_negate_stmt => {
- prog.is_stmt = !prog.is_stmt;
- },
- DW.LNS_set_basic_block => {
- prog.basic_block = true;
- },
- DW.LNS_const_add_pc => {
- const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range);
- prog.address += inc_addr;
- },
- DW.LNS_fixed_advance_pc => {
- const arg = try s.stream.readInt(u16, di.endian);
- prog.address += arg;
- },
- DW.LNS_set_prologue_end => {},
- else => {
- if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo;
- const len_bytes = standard_opcode_lengths[opcode - 1];
- try s.seekable_stream.seekBy(len_bytes);
- },
- }
- }
- }
-
- return error.MissingDebugInfo;
+ return obj_di;
}
+};
- fn getString(di: *DwarfInfo, offset: u64) ![]u8 {
- if (offset > di.debug_str.len)
- return error.InvalidDebugInfo;
- const casted_offset = math.cast(usize, offset) catch
- return error.InvalidDebugInfo;
+const SymbolInfo = struct {
+ symbol_name: []const u8 = "???",
+ compile_unit_name: []const u8 = "???",
+ line_info: ?LineInfo = null,
- // Valid strings always have a terminating zero byte
- if (mem.indexOfScalarPos(u8, di.debug_str, casted_offset, 0)) |last| {
- return di.debug_str[casted_offset..last];
+ fn deinit(self: @This()) void {
+ if (self.line_info) |li| {
+ li.deinit();
}
-
- return error.InvalidDebugInfo;
}
};
-pub const DebugInfo = switch (builtin.os) {
+pub const ModuleDebugInfo = switch (builtin.os) {
.macosx, .ios, .watchos, .tvos => struct {
+ base_address: usize,
+ mapped_memory: []const u8,
symbols: []const MachoSymbol,
- strings: []const u8,
+ strings: [:0]const u8,
ofiles: OFileTable,
- const OFileTable = std.HashMap(
- *macho.nlist_64,
- DwarfInfo,
- std.hash_map.getHashPtrAddrFn(*macho.nlist_64),
- std.hash_map.getTrivialEqlFn(*macho.nlist_64),
- );
+ const OFileTable = std.StringHashMap(DW.DwarfInfo);
- pub fn allocator(self: DebugInfo) *mem.Allocator {
+ pub fn allocator(self: @This()) *mem.Allocator {
return self.ofiles.allocator;
}
- },
- .uefi, .windows => struct {
- pdb: pdb.Pdb,
- coff: *coff.Coff,
- sect_contribs: []pdb.SectionContribEntry,
- modules: []Module,
- },
- else => DwarfInfo,
-};
-
-const PcRange = struct {
- start: u64,
- end: u64,
-};
-
-const CompileUnit = struct {
- version: u16,
- is_64: bool,
- die: *Die,
- pc_range: ?PcRange,
-};
-const AbbrevTable = ArrayList(AbbrevTableEntry);
+ fn loadOFile(self: *@This(), o_file_path: []const u8) !DW.DwarfInfo {
+ const mapped_mem = try mapWholeFile(o_file_path);
-const AbbrevTableHeader = struct {
- // offset from .debug_abbrev
- offset: u64,
- table: AbbrevTable,
-};
-
-const AbbrevTableEntry = struct {
- has_children: bool,
- abbrev_code: u64,
- tag_id: u64,
- attrs: ArrayList(AbbrevAttr),
-};
+ const hdr = @ptrCast(
+ *const macho.mach_header_64,
+ @alignCast(@alignOf(macho.mach_header_64), mapped_mem.ptr),
+ );
+ if (hdr.magic != std.macho.MH_MAGIC_64)
+ return error.InvalidDebugInfo;
-const AbbrevAttr = struct {
- attr_id: u64,
- form_id: u64,
-};
+ const hdr_base = @ptrCast([*]const u8, hdr);
+ var ptr = hdr_base + @sizeOf(macho.mach_header_64);
+ var ncmd: u32 = hdr.ncmds;
+ const segcmd = while (ncmd != 0) : (ncmd -= 1) {
+ const lc = @ptrCast(*const std.macho.load_command, ptr);
+ switch (lc.cmd) {
+ std.macho.LC_SEGMENT_64 => {
+ break @ptrCast(
+ *const std.macho.segment_command_64,
+ @alignCast(@alignOf(std.macho.segment_command_64), ptr),
+ );
+ },
+ else => {},
+ }
+ ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize);
+ } else {
+ return error.MissingDebugInfo;
+ };
-const FormValue = union(enum) {
- Address: u64,
- Block: []u8,
- Const: Constant,
- ExprLoc: []u8,
- Flag: bool,
- SecOffset: u64,
- Ref: u64,
- RefAddr: u64,
- String: []u8,
- StrPtr: u64,
-};
+ var opt_debug_line: ?*const macho.section_64 = null;
+ var opt_debug_info: ?*const macho.section_64 = null;
+ var opt_debug_abbrev: ?*const macho.section_64 = null;
+ var opt_debug_str: ?*const macho.section_64 = null;
+ var opt_debug_ranges: ?*const macho.section_64 = null;
+
+ const sections = @ptrCast(
+ [*]const macho.section_64,
+ @alignCast(@alignOf(macho.section_64), ptr + @sizeOf(std.macho.segment_command_64)),
+ )[0..segcmd.nsects];
+ for (sections) |*sect| {
+ // The section name may not exceed 16 chars and a trailing null may
+ // not be present
+ const name = if (mem.indexOfScalar(u8, sect.sectname[0..], 0)) |last|
+ sect.sectname[0..last]
+ else
+ sect.sectname[0..];
+
+ if (mem.eql(u8, name, "__debug_line")) {
+ opt_debug_line = sect;
+ } else if (mem.eql(u8, name, "__debug_info")) {
+ opt_debug_info = sect;
+ } else if (mem.eql(u8, name, "__debug_abbrev")) {
+ opt_debug_abbrev = sect;
+ } else if (mem.eql(u8, name, "__debug_str")) {
+ opt_debug_str = sect;
+ } else if (mem.eql(u8, name, "__debug_ranges")) {
+ opt_debug_ranges = sect;
+ }
+ }
-const Constant = struct {
- payload: u64,
- signed: bool,
+ const debug_line = opt_debug_line orelse
+ return error.MissingDebugInfo;
+ const debug_info = opt_debug_info orelse
+ return error.MissingDebugInfo;
+ const debug_str = opt_debug_str orelse
+ return error.MissingDebugInfo;
+ const debug_abbrev = opt_debug_abbrev orelse
+ return error.MissingDebugInfo;
- fn asUnsignedLe(self: *const Constant) !u64 {
- if (self.signed) return error.InvalidDebugInfo;
- return self.payload;
- }
-};
+ var di = DW.DwarfInfo{
+ .endian = .Little,
+ .debug_info = try chopSlice(mapped_mem, debug_info.offset, debug_info.size),
+ .debug_abbrev = try chopSlice(mapped_mem, debug_abbrev.offset, debug_abbrev.size),
+ .debug_str = try chopSlice(mapped_mem, debug_str.offset, debug_str.size),
+ .debug_line = try chopSlice(mapped_mem, debug_line.offset, debug_line.size),
+ .debug_ranges = if (opt_debug_ranges) |debug_ranges|
+ try chopSlice(mapped_mem, debug_ranges.offset, debug_ranges.size)
+ else
+ null,
+ };
-const Die = struct {
- tag_id: u64,
- has_children: bool,
- attrs: ArrayList(Attr),
+ try DW.openDwarfDebugInfo(&di, self.allocator());
- const Attr = struct {
- id: u64,
- value: FormValue,
- };
+ // Add the debug info to the cache
+ try self.ofiles.putNoClobber(o_file_path, di);
- fn getAttr(self: *const Die, id: u64) ?*const FormValue {
- for (self.attrs.toSliceConst()) |*attr| {
- if (attr.id == id) return &attr.value;
+ return di;
}
- return null;
- }
-
- fn getAttrAddr(self: *const Die, id: u64) !u64 {
- const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
- return switch (form_value.*) {
- FormValue.Address => |value| value,
- else => error.InvalidDebugInfo,
- };
- }
- fn getAttrSecOffset(self: *const Die, id: u64) !u64 {
- const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
- return switch (form_value.*) {
- FormValue.Const => |value| value.asUnsignedLe(),
- FormValue.SecOffset => |value| value,
- else => error.InvalidDebugInfo,
- };
- }
-
- fn getAttrUnsignedLe(self: *const Die, id: u64) !u64 {
- const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
- return switch (form_value.*) {
- FormValue.Const => |value| value.asUnsignedLe(),
- else => error.InvalidDebugInfo,
- };
- }
-
- fn getAttrRef(self: *const Die, id: u64) !u64 {
- const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
- return switch (form_value.*) {
- FormValue.Ref => |value| value,
- else => error.InvalidDebugInfo,
- };
- }
-
- fn getAttrString(self: *const Die, di: *DwarfInfo, id: u64) ![]u8 {
- const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
- return switch (form_value.*) {
- FormValue.String => |value| value,
- FormValue.StrPtr => |offset| di.getString(offset),
- else => error.InvalidDebugInfo,
- };
- }
-};
+ fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {
+ // Translate the VA into an address into this object
+ const relocated_address = address - self.base_address;
+ assert(relocated_address >= 0x100000000);
-const FileEntry = struct {
- file_name: []const u8,
- dir_index: usize,
- mtime: usize,
- len_bytes: usize,
-};
+ // Find the .o file where this symbol is defined
+ const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse
+ return SymbolInfo{};
-pub const LineInfo = struct {
- line: u64,
- column: u64,
- file_name: []const u8,
- allocator: ?*mem.Allocator,
+ // XXX: Return the symbol name
+ if (symbol.ofile == null)
+ return SymbolInfo{};
- fn deinit(self: LineInfo) void {
- const allocator = self.allocator orelse return;
- allocator.free(self.file_name);
- }
-};
+ assert(symbol.ofile.?.n_strx < self.strings.len);
+ const o_file_path = mem.toSliceConst(u8, self.strings.ptr + symbol.ofile.?.n_strx);
-const LineNumberProgram = struct {
- address: usize,
- file: usize,
- line: i64,
- column: u64,
- is_stmt: bool,
- basic_block: bool,
- end_sequence: bool,
-
- default_is_stmt: bool,
- target_address: usize,
- include_dirs: []const []const u8,
- file_entries: *ArrayList(FileEntry),
-
- prev_address: usize,
- prev_file: usize,
- prev_line: i64,
- prev_column: u64,
- prev_is_stmt: bool,
- prev_basic_block: bool,
- prev_end_sequence: bool,
-
- // Reset the state machine following the DWARF specification
- pub fn reset(self: *LineNumberProgram) void {
- self.address = 0;
- self.file = 1;
- self.line = 1;
- self.column = 0;
- self.is_stmt = self.default_is_stmt;
- self.basic_block = false;
- self.end_sequence = false;
- // Invalidate all the remaining fields
- self.prev_address = 0;
- self.prev_file = undefined;
- self.prev_line = undefined;
- self.prev_column = undefined;
- self.prev_is_stmt = undefined;
- self.prev_basic_block = undefined;
- self.prev_end_sequence = undefined;
- }
+ // Check if its debug infos are already in the cache
+ var o_file_di = self.ofiles.getValue(o_file_path) orelse
+ (self.loadOFile(o_file_path) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => {
+ // XXX: Return the symbol name
+ return SymbolInfo{};
+ },
+ else => return err,
+ });
- pub fn init(is_stmt: bool, include_dirs: []const []const u8, file_entries: *ArrayList(FileEntry), target_address: usize) LineNumberProgram {
- return LineNumberProgram{
- .address = 0,
- .file = 1,
- .line = 1,
- .column = 0,
- .is_stmt = is_stmt,
- .basic_block = false,
- .end_sequence = false,
- .include_dirs = include_dirs,
- .file_entries = file_entries,
- .default_is_stmt = is_stmt,
- .target_address = target_address,
- .prev_address = 0,
- .prev_file = undefined,
- .prev_line = undefined,
- .prev_column = undefined,
- .prev_is_stmt = undefined,
- .prev_basic_block = undefined,
- .prev_end_sequence = undefined,
- };
- }
+ // Translate again the address, this time into an address inside the
+ // .o file
+ const relocated_address_o = relocated_address - symbol.reloc;
- pub fn checkLineMatch(self: *LineNumberProgram) !?LineInfo {
- if (self.target_address >= self.prev_address and self.target_address < self.address) {
- const file_entry = if (self.prev_file == 0) {
- return error.MissingDebugInfo;
- } else if (self.prev_file - 1 >= self.file_entries.len) {
- return error.InvalidDebugInfo;
- } else
- &self.file_entries.items[self.prev_file - 1];
+ if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
+ return SymbolInfo{
+ .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
+ .compile_unit_name = compile_unit.die.getAttrString(&o_file_di, DW.AT_name) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => "???",
+ else => return err,
+ },
+ .line_info = o_file_di.getLineNumberInfo(compile_unit.*, relocated_address_o) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => null,
+ else => return err,
+ },
+ };
+ } else |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => {
+ return SymbolInfo{};
+ },
+ else => return err,
+ }
- const dir_name = if (file_entry.dir_index >= self.include_dirs.len) {
- return error.InvalidDebugInfo;
- } else
- self.include_dirs[file_entry.dir_index];
- const file_name = try fs.path.join(self.file_entries.allocator, &[_][]const u8{ dir_name, file_entry.file_name });
- errdefer self.file_entries.allocator.free(file_name);
- return LineInfo{
- .line = if (self.prev_line >= 0) @intCast(u64, self.prev_line) else 0,
- .column = self.prev_column,
- .file_name = file_name,
- .allocator = self.file_entries.allocator,
- };
+ unreachable;
}
+ },
+ .uefi, .windows => struct {
+ base_address: usize,
+ pdb: pdb.Pdb,
+ coff: *coff.Coff,
+ sect_contribs: []pdb.SectionContribEntry,
+ modules: []Module,
- self.prev_address = self.address;
- self.prev_file = self.file;
- self.prev_line = self.line;
- self.prev_column = self.column;
- self.prev_is_stmt = self.is_stmt;
- self.prev_basic_block = self.basic_block;
- self.prev_end_sequence = self.end_sequence;
- return null;
- }
-};
+ pub fn allocator(self: @This()) *mem.Allocator {
+ return self.coff.allocator;
+ }
-// TODO the noasyncs here are workarounds
-fn readStringRaw(allocator: *mem.Allocator, in_stream: var) ![]u8 {
- var buf = ArrayList(u8).init(allocator);
- while (true) {
- const byte = try noasync in_stream.readByte();
- if (byte == 0) break;
- try buf.append(byte);
- }
- return buf.toSlice();
-}
+ fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {
+ // Translate the VA into an address into this object
+ const relocated_address = address - self.base_address;
-// TODO the noasyncs here are workarounds
-fn readAllocBytes(allocator: *mem.Allocator, in_stream: var, size: usize) ![]u8 {
- const buf = try allocator.alloc(u8, size);
- errdefer allocator.free(buf);
- if ((try noasync in_stream.read(buf)) < size) return error.EndOfFile;
- return buf;
-}
+ var coff_section: *coff.Section = undefined;
+ const mod_index = for (self.sect_contribs) |sect_contrib| {
+ if (sect_contrib.Section > self.coff.sections.len) continue;
+ // Remember that SectionContribEntry.Section is 1-based.
+ coff_section = &self.coff.sections.toSlice()[sect_contrib.Section - 1];
-fn parseFormValueBlockLen(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue {
- const buf = try readAllocBytes(allocator, in_stream, size);
- return FormValue{ .Block = buf };
-}
+ const vaddr_start = coff_section.header.virtual_address + sect_contrib.Offset;
+ const vaddr_end = vaddr_start + sect_contrib.Size;
+ if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
+ break sect_contrib.ModuleIndex;
+ }
+ } else {
+ // we have no information to add to the address
+ return SymbolInfo{};
+ };
-// TODO the noasyncs here are workarounds
-fn parseFormValueBlock(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue {
- const block_len = try noasync in_stream.readVarInt(usize, builtin.Endian.Little, size);
- return parseFormValueBlockLen(allocator, in_stream, block_len);
-}
+ const mod = &self.modules[mod_index];
+ try populateModule(self, mod);
+ const obj_basename = fs.path.basename(mod.obj_file_name);
+
+ var symbol_i: usize = 0;
+ const symbol_name = if (!mod.populated) "???" else while (symbol_i != mod.symbols.len) {
+ const prefix = @ptrCast(*pdb.RecordPrefix, &mod.symbols[symbol_i]);
+ if (prefix.RecordLen < 2)
+ return error.InvalidDebugInfo;
+ switch (prefix.RecordKind) {
+ .S_LPROC32, .S_GPROC32 => {
+ const proc_sym = @ptrCast(*pdb.ProcSym, &mod.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)]);
+ const vaddr_start = coff_section.header.virtual_address + proc_sym.CodeOffset;
+ const vaddr_end = vaddr_start + proc_sym.CodeSize;
+ if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
+ break mem.toSliceConst(u8, @ptrCast([*:0]u8, proc_sym) + @sizeOf(pdb.ProcSym));
+ }
+ },
+ else => {},
+ }
+ symbol_i += prefix.RecordLen + @sizeOf(u16);
+ if (symbol_i > mod.symbols.len)
+ return error.InvalidDebugInfo;
+ } else "???";
+
+ const subsect_info = mod.subsect_info;
+
+ var sect_offset: usize = 0;
+ var skip_len: usize = undefined;
+ const opt_line_info = subsections: {
+ const checksum_offset = mod.checksum_offset orelse break :subsections null;
+ while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {
+ const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &subsect_info[sect_offset]);
+ skip_len = subsect_hdr.Length;
+ sect_offset += @sizeOf(pdb.DebugSubsectionHeader);
+
+ switch (subsect_hdr.Kind) {
+ .Lines => {
+ var line_index = sect_offset;
+
+ const line_hdr = @ptrCast(*pdb.LineFragmentHeader, &subsect_info[line_index]);
+ if (line_hdr.RelocSegment == 0)
+ return error.MissingDebugInfo;
+ line_index += @sizeOf(pdb.LineFragmentHeader);
+ const frag_vaddr_start = coff_section.header.virtual_address + line_hdr.RelocOffset;
+ const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize;
+
+ if (relocated_address >= frag_vaddr_start and relocated_address < frag_vaddr_end) {
+ // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records)
+ // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in,
+ // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection.
+ const subsection_end_index = sect_offset + subsect_hdr.Length;
+
+ while (line_index < subsection_end_index) {
+ const block_hdr = @ptrCast(*pdb.LineBlockFragmentHeader, &subsect_info[line_index]);
+ line_index += @sizeOf(pdb.LineBlockFragmentHeader);
+ const start_line_index = line_index;
+
+ const has_column = line_hdr.Flags.LF_HaveColumns;
+
+ // All line entries are stored inside their line block by ascending start address.
+ // Heuristic: we want to find the last line entry
+ // that has a vaddr_start <= relocated_address.
+ // This is done with a simple linear search.
+ var line_i: u32 = 0;
+ while (line_i < block_hdr.NumLines) : (line_i += 1) {
+ const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[line_index]);
+ line_index += @sizeOf(pdb.LineNumberEntry);
+
+ const vaddr_start = frag_vaddr_start + line_num_entry.Offset;
+ if (relocated_address < vaddr_start) {
+ break;
+ }
+ }
+
+ // line_i == 0 would mean that no matching LineNumberEntry was found.
+ if (line_i > 0) {
+ const subsect_index = checksum_offset + block_hdr.NameIndex;
+ const chksum_hdr = @ptrCast(*pdb.FileChecksumEntryHeader, &mod.subsect_info[subsect_index]);
+ const strtab_offset = @sizeOf(pdb.PDBStringTableHeader) + chksum_hdr.FileNameOffset;
+ try self.pdb.string_table.seekTo(strtab_offset);
+ const source_file_name = try self.pdb.string_table.readNullTermString(self.allocator());
+
+ const line_entry_idx = line_i - 1;
+
+ const column = if (has_column) blk: {
+ const start_col_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.NumLines;
+ const col_index = start_col_index + @sizeOf(pdb.ColumnNumberEntry) * line_entry_idx;
+ const col_num_entry = @ptrCast(*pdb.ColumnNumberEntry, &subsect_info[col_index]);
+ break :blk col_num_entry.StartColumn;
+ } else 0;
+
+ const found_line_index = start_line_index + line_entry_idx * @sizeOf(pdb.LineNumberEntry);
+ const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[found_line_index]);
+ const flags = @ptrCast(*pdb.LineNumberEntry.Flags, &line_num_entry.Flags);
+
+ break :subsections LineInfo{
+ .allocator = self.allocator(),
+ .file_name = source_file_name,
+ .line = flags.Start,
+ .column = column,
+ };
+ }
+ }
-fn parseFormValueConstant(allocator: *mem.Allocator, in_stream: var, signed: bool, comptime size: i32) !FormValue {
- // TODO: Please forgive me, I've worked around zig not properly spilling some intermediate values here.
- // `noasync` should be removed from all the function calls once it is fixed.
- return FormValue{
- .Const = Constant{
- .signed = signed,
- .payload = switch (size) {
- 1 => try noasync in_stream.readIntLittle(u8),
- 2 => try noasync in_stream.readIntLittle(u16),
- 4 => try noasync in_stream.readIntLittle(u32),
- 8 => try noasync in_stream.readIntLittle(u64),
- -1 => blk: {
- if (signed) {
- const x = try noasync leb.readILEB128(i64, in_stream);
- break :blk @bitCast(u64, x);
- } else {
- const x = try noasync leb.readULEB128(u64, in_stream);
- break :blk x;
+ // Checking that we are not reading garbage after the (possibly) multiple block fragments.
+ if (line_index != subsection_end_index) {
+ return error.InvalidDebugInfo;
+ }
+ }
+ },
+ else => {},
}
- },
- else => @compileError("Invalid size"),
- },
- },
- };
-}
-
-// TODO the noasyncs here are workarounds
-fn parseFormValueDwarfOffsetSize(in_stream: var, is_64: bool) !u64 {
- return if (is_64) try noasync in_stream.readIntLittle(u64) else @as(u64, try noasync in_stream.readIntLittle(u32));
-}
-
-// TODO the noasyncs here are workarounds
-fn parseFormValueTargetAddrSize(in_stream: var) !u64 {
- if (@sizeOf(usize) == 4) {
- // TODO this cast should not be needed
- return @as(u64, try noasync in_stream.readIntLittle(u32));
- } else if (@sizeOf(usize) == 8) {
- return noasync in_stream.readIntLittle(u64);
- } else {
- unreachable;
- }
-}
-
-// TODO the noasyncs here are workarounds
-fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, size: i32) !FormValue {
- return FormValue{
- .Ref = switch (size) {
- 1 => try noasync in_stream.readIntLittle(u8),
- 2 => try noasync in_stream.readIntLittle(u16),
- 4 => try noasync in_stream.readIntLittle(u32),
- 8 => try noasync in_stream.readIntLittle(u64),
- -1 => try noasync leb.readULEB128(u64, in_stream),
- else => unreachable,
- },
- };
-}
-
-// TODO the noasyncs here are workarounds
-fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64: bool) anyerror!FormValue {
- return switch (form_id) {
- DW.FORM_addr => FormValue{ .Address = try parseFormValueTargetAddrSize(in_stream) },
- DW.FORM_block1 => parseFormValueBlock(allocator, in_stream, 1),
- DW.FORM_block2 => parseFormValueBlock(allocator, in_stream, 2),
- DW.FORM_block4 => parseFormValueBlock(allocator, in_stream, 4),
- DW.FORM_block => x: {
- const block_len = try noasync leb.readULEB128(usize, in_stream);
- return parseFormValueBlockLen(allocator, in_stream, block_len);
- },
- DW.FORM_data1 => parseFormValueConstant(allocator, in_stream, false, 1),
- DW.FORM_data2 => parseFormValueConstant(allocator, in_stream, false, 2),
- DW.FORM_data4 => parseFormValueConstant(allocator, in_stream, false, 4),
- DW.FORM_data8 => parseFormValueConstant(allocator, in_stream, false, 8),
- DW.FORM_udata, DW.FORM_sdata => {
- const signed = form_id == DW.FORM_sdata;
- return parseFormValueConstant(allocator, in_stream, signed, -1);
- },
- DW.FORM_exprloc => {
- const size = try noasync leb.readULEB128(usize, in_stream);
- const buf = try readAllocBytes(allocator, in_stream, size);
- return FormValue{ .ExprLoc = buf };
- },
- DW.FORM_flag => FormValue{ .Flag = (try noasync in_stream.readByte()) != 0 },
- DW.FORM_flag_present => FormValue{ .Flag = true },
- DW.FORM_sec_offset => FormValue{ .SecOffset = try parseFormValueDwarfOffsetSize(in_stream, is_64) },
-
- DW.FORM_ref1 => parseFormValueRef(allocator, in_stream, 1),
- DW.FORM_ref2 => parseFormValueRef(allocator, in_stream, 2),
- DW.FORM_ref4 => parseFormValueRef(allocator, in_stream, 4),
- DW.FORM_ref8 => parseFormValueRef(allocator, in_stream, 8),
- DW.FORM_ref_udata => parseFormValueRef(allocator, in_stream, -1),
-
- DW.FORM_ref_addr => FormValue{ .RefAddr = try parseFormValueDwarfOffsetSize(in_stream, is_64) },
- DW.FORM_ref_sig8 => FormValue{ .Ref = try noasync in_stream.readIntLittle(u64) },
-
- DW.FORM_string => FormValue{ .String = try readStringRaw(allocator, in_stream) },
- DW.FORM_strp => FormValue{ .StrPtr = try parseFormValueDwarfOffsetSize(in_stream, is_64) },
- DW.FORM_indirect => {
- const child_form_id = try noasync leb.readULEB128(u64, in_stream);
- const F = @TypeOf(async parseFormValue(allocator, in_stream, child_form_id, is_64));
- var frame = try allocator.create(F);
- defer allocator.destroy(frame);
- return await @asyncCall(frame, {}, parseFormValue, allocator, in_stream, child_form_id, is_64);
- },
- else => error.InvalidDebugInfo,
- };
-}
-fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*const AbbrevTableEntry {
- for (abbrev_table.toSliceConst()) |*table_entry| {
- if (table_entry.abbrev_code == abbrev_code) return table_entry;
- }
- return null;
-}
+ if (sect_offset > subsect_info.len)
+ return error.InvalidDebugInfo;
+ } else {
+ break :subsections null;
+ }
+ };
-/// TODO resources https://github.com/ziglang/zig/issues/4353
-fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, address: usize) !LineInfo {
- const ofile = symbol.ofile orelse return error.MissingDebugInfo;
- const gop = try di.ofiles.getOrPut(ofile);
- const dwarf_info = if (gop.found_existing) &gop.kv.value else blk: {
- errdefer _ = di.ofiles.remove(ofile);
- const ofile_path = mem.toSliceConst(u8, @ptrCast([*:0]const u8, di.strings.ptr + ofile.n_strx));
-
- var exe_file = try std.fs.openFileAbsoluteC(ofile_path, .{});
- errdefer exe_file.close();
-
- const exe_len = math.cast(usize, try exe_file.getEndPos()) catch
- return error.DebugInfoTooLarge;
- const exe_mmap = try os.mmap(
- null,
- exe_len,
- os.PROT_READ,
- os.MAP_SHARED,
- exe_file.handle,
- 0,
- );
- errdefer os.munmap(exe_mmap);
-
- const hdr = @ptrCast(
- *const macho.mach_header_64,
- @alignCast(@alignOf(macho.mach_header_64), exe_mmap.ptr),
- );
- if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo;
-
- const hdr_base = @ptrCast([*]const u8, hdr);
- var ptr = hdr_base + @sizeOf(macho.mach_header_64);
- var ncmd: u32 = hdr.ncmds;
- const segcmd = while (ncmd != 0) : (ncmd -= 1) {
- const lc = @ptrCast(*const std.macho.load_command, ptr);
- switch (lc.cmd) {
- std.macho.LC_SEGMENT_64 => {
- break @ptrCast(
- *const std.macho.segment_command_64,
- @alignCast(@alignOf(std.macho.segment_command_64), ptr),
- );
+ return SymbolInfo{
+ .symbol_name = symbol_name,
+ .compile_unit_name = obj_basename,
+ .line_info = opt_line_info,
+ };
+ }
+ },
+ .linux, .freebsd => struct {
+ base_address: usize,
+ dwarf: DW.DwarfInfo,
+ mapped_memory: []const u8,
+
+ fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {
+ // Translate the VA into an address into this object
+ const relocated_address = address - self.base_address;
+
+ if (self.dwarf.findCompileUnit(relocated_address)) |compile_unit| {
+ return SymbolInfo{
+ .symbol_name = self.dwarf.getSymbolName(relocated_address) orelse "???",
+ .compile_unit_name = compile_unit.die.getAttrString(&self.dwarf, DW.AT_name) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => "???",
+ else => return err,
+ },
+ .line_info = self.dwarf.getLineNumberInfo(compile_unit.*, relocated_address) catch |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => null,
+ else => return err,
+ },
+ };
+ } else |err| switch (err) {
+ error.MissingDebugInfo, error.InvalidDebugInfo => {
+ return SymbolInfo{};
},
- else => {},
+ else => return err,
}
- ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize);
- } else {
- return error.MissingDebugInfo;
- };
- var opt_debug_line: ?*const macho.section_64 = null;
- var opt_debug_info: ?*const macho.section_64 = null;
- var opt_debug_abbrev: ?*const macho.section_64 = null;
- var opt_debug_str: ?*const macho.section_64 = null;
- var opt_debug_ranges: ?*const macho.section_64 = null;
-
- const sections = @ptrCast([*]const macho.section_64, @alignCast(@alignOf(macho.section_64), ptr + @sizeOf(std.macho.segment_command_64)))[0..segcmd.nsects];
- for (sections) |*sect| {
- // The section name may not exceed 16 chars and a trailing null may
- // not be present
- const name = if (mem.indexOfScalar(u8, sect.sectname[0..], 0)) |last|
- sect.sectname[0..last]
- else
- sect.sectname[0..];
-
- if (mem.eql(u8, name, "__debug_line")) {
- opt_debug_line = sect;
- } else if (mem.eql(u8, name, "__debug_info")) {
- opt_debug_info = sect;
- } else if (mem.eql(u8, name, "__debug_abbrev")) {
- opt_debug_abbrev = sect;
- } else if (mem.eql(u8, name, "__debug_str")) {
- opt_debug_str = sect;
- } else if (mem.eql(u8, name, "__debug_ranges")) {
- opt_debug_ranges = sect;
- }
+ unreachable;
}
-
- var debug_line = opt_debug_line orelse
- return error.MissingDebugInfo;
- var debug_info = opt_debug_info orelse
- return error.MissingDebugInfo;
- var debug_str = opt_debug_str orelse
- return error.MissingDebugInfo;
- var debug_abbrev = opt_debug_abbrev orelse
- return error.MissingDebugInfo;
-
- gop.kv.value = DwarfInfo{
- .endian = .Little,
- .debug_info = exe_mmap[@intCast(usize, debug_info.offset)..@intCast(usize, debug_info.offset + debug_info.size)],
- .debug_abbrev = exe_mmap[@intCast(usize, debug_abbrev.offset)..@intCast(usize, debug_abbrev.offset + debug_abbrev.size)],
- .debug_str = exe_mmap[@intCast(usize, debug_str.offset)..@intCast(usize, debug_str.offset + debug_str.size)],
- .debug_line = exe_mmap[@intCast(usize, debug_line.offset)..@intCast(usize, debug_line.offset + debug_line.size)],
- .debug_ranges = if (opt_debug_ranges) |debug_ranges|
- exe_mmap[@intCast(usize, debug_ranges.offset)..@intCast(usize, debug_ranges.offset + debug_ranges.size)]
- else
- null,
- };
- try openDwarfDebugInfo(&gop.kv.value, di.allocator());
-
- break :blk &gop.kv.value;
- };
-
- const o_file_address = address - symbol.reloc;
- const compile_unit = try dwarf_info.findCompileUnit(o_file_address);
- return dwarf_info.getLineNumberInfo(compile_unit.*, o_file_address);
-}
-
-const Func = struct {
- pc_range: ?PcRange,
- name: ?[]u8,
+ },
+ else => DW.DwarfInfo,
};
-fn readInitialLength(comptime E: type, in_stream: *io.InStream(E), is_64: *bool) !u64 {
- const first_32_bits = try in_stream.readIntLittle(u32);
- is_64.* = (first_32_bits == 0xffffffff);
- if (is_64.*) {
- return in_stream.readIntLittle(u64);
- } else {
- if (first_32_bits >= 0xfffffff0) return error.InvalidDebugInfo;
- // TODO this cast should not be needed
- return @as(u64, first_32_bits);
- }
-}
-
/// TODO multithreaded awareness
var debug_info_allocator: ?*mem.Allocator = null;
var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;
diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig
@@ -1,682 +1,889 @@
-pub const TAG_padding = 0x00;
-pub const TAG_array_type = 0x01;
-pub const TAG_class_type = 0x02;
-pub const TAG_entry_point = 0x03;
-pub const TAG_enumeration_type = 0x04;
-pub const TAG_formal_parameter = 0x05;
-pub const TAG_imported_declaration = 0x08;
-pub const TAG_label = 0x0a;
-pub const TAG_lexical_block = 0x0b;
-pub const TAG_member = 0x0d;
-pub const TAG_pointer_type = 0x0f;
-pub const TAG_reference_type = 0x10;
-pub const TAG_compile_unit = 0x11;
-pub const TAG_string_type = 0x12;
-pub const TAG_structure_type = 0x13;
-pub const TAG_subroutine = 0x14;
-pub const TAG_subroutine_type = 0x15;
-pub const TAG_typedef = 0x16;
-pub const TAG_union_type = 0x17;
-pub const TAG_unspecified_parameters = 0x18;
-pub const TAG_variant = 0x19;
-pub const TAG_common_block = 0x1a;
-pub const TAG_common_inclusion = 0x1b;
-pub const TAG_inheritance = 0x1c;
-pub const TAG_inlined_subroutine = 0x1d;
-pub const TAG_module = 0x1e;
-pub const TAG_ptr_to_member_type = 0x1f;
-pub const TAG_set_type = 0x20;
-pub const TAG_subrange_type = 0x21;
-pub const TAG_with_stmt = 0x22;
-pub const TAG_access_declaration = 0x23;
-pub const TAG_base_type = 0x24;
-pub const TAG_catch_block = 0x25;
-pub const TAG_const_type = 0x26;
-pub const TAG_constant = 0x27;
-pub const TAG_enumerator = 0x28;
-pub const TAG_file_type = 0x29;
-pub const TAG_friend = 0x2a;
-pub const TAG_namelist = 0x2b;
-pub const TAG_namelist_item = 0x2c;
-pub const TAG_packed_type = 0x2d;
-pub const TAG_subprogram = 0x2e;
-pub const TAG_template_type_param = 0x2f;
-pub const TAG_template_value_param = 0x30;
-pub const TAG_thrown_type = 0x31;
-pub const TAG_try_block = 0x32;
-pub const TAG_variant_part = 0x33;
-pub const TAG_variable = 0x34;
-pub const TAG_volatile_type = 0x35;
-
-// DWARF 3
-pub const TAG_dwarf_procedure = 0x36;
-pub const TAG_restrict_type = 0x37;
-pub const TAG_interface_type = 0x38;
-pub const TAG_namespace = 0x39;
-pub const TAG_imported_module = 0x3a;
-pub const TAG_unspecified_type = 0x3b;
-pub const TAG_partial_unit = 0x3c;
-pub const TAG_imported_unit = 0x3d;
-pub const TAG_condition = 0x3f;
-pub const TAG_shared_type = 0x40;
-
-// DWARF 4
-pub const TAG_type_unit = 0x41;
-pub const TAG_rvalue_reference_type = 0x42;
-pub const TAG_template_alias = 0x43;
-
-pub const TAG_lo_user = 0x4080;
-pub const TAG_hi_user = 0xffff;
-
-// SGI/MIPS Extensions.
-pub const DW_TAG_MIPS_loop = 0x4081;
-
-// HP extensions. See: ftp://ftp.hp.com/pub/lang/tools/WDB/wdb-4.0.tar.gz .
-pub const TAG_HP_array_descriptor = 0x4090;
-pub const TAG_HP_Bliss_field = 0x4091;
-pub const TAG_HP_Bliss_field_set = 0x4092;
-
-// GNU extensions.
-pub const TAG_format_label = 0x4101; // For FORTRAN 77 and Fortran 90.
-pub const TAG_function_template = 0x4102; // For C++.
-pub const TAG_class_template = 0x4103; //For C++.
-pub const TAG_GNU_BINCL = 0x4104;
-pub const TAG_GNU_EINCL = 0x4105;
-
-// Template template parameter.
-// See http://gcc.gnu.org/wiki/TemplateParmsDwarf .
-pub const TAG_GNU_template_template_param = 0x4106;
-
-// Template parameter pack extension = specified at
-// http://wiki.dwarfstd.org/index.php?title=C%2B%2B0x:_Variadic_templates
-// The values of these two TAGS are in the DW_TAG_GNU_* space until the tags
-// are properly part of DWARF 5.
-pub const TAG_GNU_template_parameter_pack = 0x4107;
-pub const TAG_GNU_formal_parameter_pack = 0x4108;
-// The GNU call site extension = specified at
-// http://www.dwarfstd.org/ShowIssue.php?issue=100909.2&type=open .
-// The values of these two TAGS are in the DW_TAG_GNU_* space until the tags
-// are properly part of DWARF 5.
-pub const TAG_GNU_call_site = 0x4109;
-pub const TAG_GNU_call_site_parameter = 0x410a;
-// Extensions for UPC. See: http://dwarfstd.org/doc/DWARF4.pdf.
-pub const TAG_upc_shared_type = 0x8765;
-pub const TAG_upc_strict_type = 0x8766;
-pub const TAG_upc_relaxed_type = 0x8767;
-// PGI (STMicroelectronics; extensions. No documentation available.
-pub const TAG_PGI_kanji_type = 0xA000;
-pub const TAG_PGI_interface_block = 0xA020;
-
-pub const FORM_addr = 0x01;
-pub const FORM_block2 = 0x03;
-pub const FORM_block4 = 0x04;
-pub const FORM_data2 = 0x05;
-pub const FORM_data4 = 0x06;
-pub const FORM_data8 = 0x07;
-pub const FORM_string = 0x08;
-pub const FORM_block = 0x09;
-pub const FORM_block1 = 0x0a;
-pub const FORM_data1 = 0x0b;
-pub const FORM_flag = 0x0c;
-pub const FORM_sdata = 0x0d;
-pub const FORM_strp = 0x0e;
-pub const FORM_udata = 0x0f;
-pub const FORM_ref_addr = 0x10;
-pub const FORM_ref1 = 0x11;
-pub const FORM_ref2 = 0x12;
-pub const FORM_ref4 = 0x13;
-pub const FORM_ref8 = 0x14;
-pub const FORM_ref_udata = 0x15;
-pub const FORM_indirect = 0x16;
-pub const FORM_sec_offset = 0x17;
-pub const FORM_exprloc = 0x18;
-pub const FORM_flag_present = 0x19;
-pub const FORM_ref_sig8 = 0x20;
-
-// Extensions for Fission. See http://gcc.gnu.org/wiki/DebugFission.
-pub const FORM_GNU_addr_index = 0x1f01;
-pub const FORM_GNU_str_index = 0x1f02;
-
-// Extensions for DWZ multifile.
-// See http://www.dwarfstd.org/ShowIssue.php?issue=120604.1&type=open .
-pub const FORM_GNU_ref_alt = 0x1f20;
-pub const FORM_GNU_strp_alt = 0x1f21;
-
-pub const AT_sibling = 0x01;
-pub const AT_location = 0x02;
-pub const AT_name = 0x03;
-pub const AT_ordering = 0x09;
-pub const AT_subscr_data = 0x0a;
-pub const AT_byte_size = 0x0b;
-pub const AT_bit_offset = 0x0c;
-pub const AT_bit_size = 0x0d;
-pub const AT_element_list = 0x0f;
-pub const AT_stmt_list = 0x10;
-pub const AT_low_pc = 0x11;
-pub const AT_high_pc = 0x12;
-pub const AT_language = 0x13;
-pub const AT_member = 0x14;
-pub const AT_discr = 0x15;
-pub const AT_discr_value = 0x16;
-pub const AT_visibility = 0x17;
-pub const AT_import = 0x18;
-pub const AT_string_length = 0x19;
-pub const AT_common_reference = 0x1a;
-pub const AT_comp_dir = 0x1b;
-pub const AT_const_value = 0x1c;
-pub const AT_containing_type = 0x1d;
-pub const AT_default_value = 0x1e;
-pub const AT_inline = 0x20;
-pub const AT_is_optional = 0x21;
-pub const AT_lower_bound = 0x22;
-pub const AT_producer = 0x25;
-pub const AT_prototyped = 0x27;
-pub const AT_return_addr = 0x2a;
-pub const AT_start_scope = 0x2c;
-pub const AT_bit_stride = 0x2e;
-pub const AT_upper_bound = 0x2f;
-pub const AT_abstract_origin = 0x31;
-pub const AT_accessibility = 0x32;
-pub const AT_address_class = 0x33;
-pub const AT_artificial = 0x34;
-pub const AT_base_types = 0x35;
-pub const AT_calling_convention = 0x36;
-pub const AT_count = 0x37;
-pub const AT_data_member_location = 0x38;
-pub const AT_decl_column = 0x39;
-pub const AT_decl_file = 0x3a;
-pub const AT_decl_line = 0x3b;
-pub const AT_declaration = 0x3c;
-pub const AT_discr_list = 0x3d;
-pub const AT_encoding = 0x3e;
-pub const AT_external = 0x3f;
-pub const AT_frame_base = 0x40;
-pub const AT_friend = 0x41;
-pub const AT_identifier_case = 0x42;
-pub const AT_macro_info = 0x43;
-pub const AT_namelist_items = 0x44;
-pub const AT_priority = 0x45;
-pub const AT_segment = 0x46;
-pub const AT_specification = 0x47;
-pub const AT_static_link = 0x48;
-pub const AT_type = 0x49;
-pub const AT_use_location = 0x4a;
-pub const AT_variable_parameter = 0x4b;
-pub const AT_virtuality = 0x4c;
-pub const AT_vtable_elem_location = 0x4d;
-
-// DWARF 3 values.
-pub const AT_allocated = 0x4e;
-pub const AT_associated = 0x4f;
-pub const AT_data_location = 0x50;
-pub const AT_byte_stride = 0x51;
-pub const AT_entry_pc = 0x52;
-pub const AT_use_UTF8 = 0x53;
-pub const AT_extension = 0x54;
-pub const AT_ranges = 0x55;
-pub const AT_trampoline = 0x56;
-pub const AT_call_column = 0x57;
-pub const AT_call_file = 0x58;
-pub const AT_call_line = 0x59;
-pub const AT_description = 0x5a;
-pub const AT_binary_scale = 0x5b;
-pub const AT_decimal_scale = 0x5c;
-pub const AT_small = 0x5d;
-pub const AT_decimal_sign = 0x5e;
-pub const AT_digit_count = 0x5f;
-pub const AT_picture_string = 0x60;
-pub const AT_mutable = 0x61;
-pub const AT_threads_scaled = 0x62;
-pub const AT_explicit = 0x63;
-pub const AT_object_pointer = 0x64;
-pub const AT_endianity = 0x65;
-pub const AT_elemental = 0x66;
-pub const AT_pure = 0x67;
-pub const AT_recursive = 0x68;
-
-// DWARF 4.
-pub const AT_signature = 0x69;
-pub const AT_main_subprogram = 0x6a;
-pub const AT_data_bit_offset = 0x6b;
-pub const AT_const_expr = 0x6c;
-pub const AT_enum_class = 0x6d;
-pub const AT_linkage_name = 0x6e;
-
-// DWARF 5
-pub const AT_alignment = 0x88;
-
-pub const AT_lo_user = 0x2000; // Implementation-defined range start.
-pub const AT_hi_user = 0x3fff; // Implementation-defined range end.
-
-// SGI/MIPS extensions.
-pub const AT_MIPS_fde = 0x2001;
-pub const AT_MIPS_loop_begin = 0x2002;
-pub const AT_MIPS_tail_loop_begin = 0x2003;
-pub const AT_MIPS_epilog_begin = 0x2004;
-pub const AT_MIPS_loop_unroll_factor = 0x2005;
-pub const AT_MIPS_software_pipeline_depth = 0x2006;
-pub const AT_MIPS_linkage_name = 0x2007;
-pub const AT_MIPS_stride = 0x2008;
-pub const AT_MIPS_abstract_name = 0x2009;
-pub const AT_MIPS_clone_origin = 0x200a;
-pub const AT_MIPS_has_inlines = 0x200b;
-
-// HP extensions.
-pub const AT_HP_block_index = 0x2000;
-pub const AT_HP_unmodifiable = 0x2001; // Same as DW_AT_MIPS_fde.
-pub const AT_HP_prologue = 0x2005; // Same as DW_AT_MIPS_loop_unroll.
-pub const AT_HP_epilogue = 0x2008; // Same as DW_AT_MIPS_stride.
-pub const AT_HP_actuals_stmt_list = 0x2010;
-pub const AT_HP_proc_per_section = 0x2011;
-pub const AT_HP_raw_data_ptr = 0x2012;
-pub const AT_HP_pass_by_reference = 0x2013;
-pub const AT_HP_opt_level = 0x2014;
-pub const AT_HP_prof_version_id = 0x2015;
-pub const AT_HP_opt_flags = 0x2016;
-pub const AT_HP_cold_region_low_pc = 0x2017;
-pub const AT_HP_cold_region_high_pc = 0x2018;
-pub const AT_HP_all_variables_modifiable = 0x2019;
-pub const AT_HP_linkage_name = 0x201a;
-pub const AT_HP_prof_flags = 0x201b; // In comp unit of procs_info for -g.
-pub const AT_HP_unit_name = 0x201f;
-pub const AT_HP_unit_size = 0x2020;
-pub const AT_HP_widened_byte_size = 0x2021;
-pub const AT_HP_definition_points = 0x2022;
-pub const AT_HP_default_location = 0x2023;
-pub const AT_HP_is_result_param = 0x2029;
-
-// GNU extensions.
-pub const AT_sf_names = 0x2101;
-pub const AT_src_info = 0x2102;
-pub const AT_mac_info = 0x2103;
-pub const AT_src_coords = 0x2104;
-pub const AT_body_begin = 0x2105;
-pub const AT_body_end = 0x2106;
-pub const AT_GNU_vector = 0x2107;
-// Thread-safety annotations.
-// See http://gcc.gnu.org/wiki/ThreadSafetyAnnotation .
-pub const AT_GNU_guarded_by = 0x2108;
-pub const AT_GNU_pt_guarded_by = 0x2109;
-pub const AT_GNU_guarded = 0x210a;
-pub const AT_GNU_pt_guarded = 0x210b;
-pub const AT_GNU_locks_excluded = 0x210c;
-pub const AT_GNU_exclusive_locks_required = 0x210d;
-pub const AT_GNU_shared_locks_required = 0x210e;
-// One-definition rule violation detection.
-// See http://gcc.gnu.org/wiki/DwarfSeparateTypeInfo .
-pub const AT_GNU_odr_signature = 0x210f;
-// Template template argument name.
-// See http://gcc.gnu.org/wiki/TemplateParmsDwarf .
-pub const AT_GNU_template_name = 0x2110;
-// The GNU call site extension.
-// See http://www.dwarfstd.org/ShowIssue.php?issue=100909.2&type=open .
-pub const AT_GNU_call_site_value = 0x2111;
-pub const AT_GNU_call_site_data_value = 0x2112;
-pub const AT_GNU_call_site_target = 0x2113;
-pub const AT_GNU_call_site_target_clobbered = 0x2114;
-pub const AT_GNU_tail_call = 0x2115;
-pub const AT_GNU_all_tail_call_sites = 0x2116;
-pub const AT_GNU_all_call_sites = 0x2117;
-pub const AT_GNU_all_source_call_sites = 0x2118;
-// Section offset into .debug_macro section.
-pub const AT_GNU_macros = 0x2119;
-// Extensions for Fission. See http://gcc.gnu.org/wiki/DebugFission.
-pub const AT_GNU_dwo_name = 0x2130;
-pub const AT_GNU_dwo_id = 0x2131;
-pub const AT_GNU_ranges_base = 0x2132;
-pub const AT_GNU_addr_base = 0x2133;
-pub const AT_GNU_pubnames = 0x2134;
-pub const AT_GNU_pubtypes = 0x2135;
-// VMS extensions.
-pub const AT_VMS_rtnbeg_pd_address = 0x2201;
-// GNAT extensions.
-// GNAT descriptive type.
-// See http://gcc.gnu.org/wiki/DW_AT_GNAT_descriptive_type .
-pub const AT_use_GNAT_descriptive_type = 0x2301;
-pub const AT_GNAT_descriptive_type = 0x2302;
-// UPC extension.
-pub const AT_upc_threads_scaled = 0x3210;
-// PGI (STMicroelectronics) extensions.
-pub const AT_PGI_lbase = 0x3a00;
-pub const AT_PGI_soffset = 0x3a01;
-pub const AT_PGI_lstride = 0x3a02;
-
-pub const OP_addr = 0x03;
-pub const OP_deref = 0x06;
-pub const OP_const1u = 0x08;
-pub const OP_const1s = 0x09;
-pub const OP_const2u = 0x0a;
-pub const OP_const2s = 0x0b;
-pub const OP_const4u = 0x0c;
-pub const OP_const4s = 0x0d;
-pub const OP_const8u = 0x0e;
-pub const OP_const8s = 0x0f;
-pub const OP_constu = 0x10;
-pub const OP_consts = 0x11;
-pub const OP_dup = 0x12;
-pub const OP_drop = 0x13;
-pub const OP_over = 0x14;
-pub const OP_pick = 0x15;
-pub const OP_swap = 0x16;
-pub const OP_rot = 0x17;
-pub const OP_xderef = 0x18;
-pub const OP_abs = 0x19;
-pub const OP_and = 0x1a;
-pub const OP_div = 0x1b;
-pub const OP_minus = 0x1c;
-pub const OP_mod = 0x1d;
-pub const OP_mul = 0x1e;
-pub const OP_neg = 0x1f;
-pub const OP_not = 0x20;
-pub const OP_or = 0x21;
-pub const OP_plus = 0x22;
-pub const OP_plus_uconst = 0x23;
-pub const OP_shl = 0x24;
-pub const OP_shr = 0x25;
-pub const OP_shra = 0x26;
-pub const OP_xor = 0x27;
-pub const OP_bra = 0x28;
-pub const OP_eq = 0x29;
-pub const OP_ge = 0x2a;
-pub const OP_gt = 0x2b;
-pub const OP_le = 0x2c;
-pub const OP_lt = 0x2d;
-pub const OP_ne = 0x2e;
-pub const OP_skip = 0x2f;
-pub const OP_lit0 = 0x30;
-pub const OP_lit1 = 0x31;
-pub const OP_lit2 = 0x32;
-pub const OP_lit3 = 0x33;
-pub const OP_lit4 = 0x34;
-pub const OP_lit5 = 0x35;
-pub const OP_lit6 = 0x36;
-pub const OP_lit7 = 0x37;
-pub const OP_lit8 = 0x38;
-pub const OP_lit9 = 0x39;
-pub const OP_lit10 = 0x3a;
-pub const OP_lit11 = 0x3b;
-pub const OP_lit12 = 0x3c;
-pub const OP_lit13 = 0x3d;
-pub const OP_lit14 = 0x3e;
-pub const OP_lit15 = 0x3f;
-pub const OP_lit16 = 0x40;
-pub const OP_lit17 = 0x41;
-pub const OP_lit18 = 0x42;
-pub const OP_lit19 = 0x43;
-pub const OP_lit20 = 0x44;
-pub const OP_lit21 = 0x45;
-pub const OP_lit22 = 0x46;
-pub const OP_lit23 = 0x47;
-pub const OP_lit24 = 0x48;
-pub const OP_lit25 = 0x49;
-pub const OP_lit26 = 0x4a;
-pub const OP_lit27 = 0x4b;
-pub const OP_lit28 = 0x4c;
-pub const OP_lit29 = 0x4d;
-pub const OP_lit30 = 0x4e;
-pub const OP_lit31 = 0x4f;
-pub const OP_reg0 = 0x50;
-pub const OP_reg1 = 0x51;
-pub const OP_reg2 = 0x52;
-pub const OP_reg3 = 0x53;
-pub const OP_reg4 = 0x54;
-pub const OP_reg5 = 0x55;
-pub const OP_reg6 = 0x56;
-pub const OP_reg7 = 0x57;
-pub const OP_reg8 = 0x58;
-pub const OP_reg9 = 0x59;
-pub const OP_reg10 = 0x5a;
-pub const OP_reg11 = 0x5b;
-pub const OP_reg12 = 0x5c;
-pub const OP_reg13 = 0x5d;
-pub const OP_reg14 = 0x5e;
-pub const OP_reg15 = 0x5f;
-pub const OP_reg16 = 0x60;
-pub const OP_reg17 = 0x61;
-pub const OP_reg18 = 0x62;
-pub const OP_reg19 = 0x63;
-pub const OP_reg20 = 0x64;
-pub const OP_reg21 = 0x65;
-pub const OP_reg22 = 0x66;
-pub const OP_reg23 = 0x67;
-pub const OP_reg24 = 0x68;
-pub const OP_reg25 = 0x69;
-pub const OP_reg26 = 0x6a;
-pub const OP_reg27 = 0x6b;
-pub const OP_reg28 = 0x6c;
-pub const OP_reg29 = 0x6d;
-pub const OP_reg30 = 0x6e;
-pub const OP_reg31 = 0x6f;
-pub const OP_breg0 = 0x70;
-pub const OP_breg1 = 0x71;
-pub const OP_breg2 = 0x72;
-pub const OP_breg3 = 0x73;
-pub const OP_breg4 = 0x74;
-pub const OP_breg5 = 0x75;
-pub const OP_breg6 = 0x76;
-pub const OP_breg7 = 0x77;
-pub const OP_breg8 = 0x78;
-pub const OP_breg9 = 0x79;
-pub const OP_breg10 = 0x7a;
-pub const OP_breg11 = 0x7b;
-pub const OP_breg12 = 0x7c;
-pub const OP_breg13 = 0x7d;
-pub const OP_breg14 = 0x7e;
-pub const OP_breg15 = 0x7f;
-pub const OP_breg16 = 0x80;
-pub const OP_breg17 = 0x81;
-pub const OP_breg18 = 0x82;
-pub const OP_breg19 = 0x83;
-pub const OP_breg20 = 0x84;
-pub const OP_breg21 = 0x85;
-pub const OP_breg22 = 0x86;
-pub const OP_breg23 = 0x87;
-pub const OP_breg24 = 0x88;
-pub const OP_breg25 = 0x89;
-pub const OP_breg26 = 0x8a;
-pub const OP_breg27 = 0x8b;
-pub const OP_breg28 = 0x8c;
-pub const OP_breg29 = 0x8d;
-pub const OP_breg30 = 0x8e;
-pub const OP_breg31 = 0x8f;
-pub const OP_regx = 0x90;
-pub const OP_fbreg = 0x91;
-pub const OP_bregx = 0x92;
-pub const OP_piece = 0x93;
-pub const OP_deref_size = 0x94;
-pub const OP_xderef_size = 0x95;
-pub const OP_nop = 0x96;
-
-// DWARF 3 extensions.
-pub const OP_push_object_address = 0x97;
-pub const OP_call2 = 0x98;
-pub const OP_call4 = 0x99;
-pub const OP_call_ref = 0x9a;
-pub const OP_form_tls_address = 0x9b;
-pub const OP_call_frame_cfa = 0x9c;
-pub const OP_bit_piece = 0x9d;
-
-// DWARF 4 extensions.
-pub const OP_implicit_value = 0x9e;
-pub const OP_stack_value = 0x9f;
-
-pub const OP_lo_user = 0xe0; // Implementation-defined range start.
-pub const OP_hi_user = 0xff; // Implementation-defined range end.
-
-// GNU extensions.
-pub const OP_GNU_push_tls_address = 0xe0;
-// The following is for marking variables that are uninitialized.
-pub const OP_GNU_uninit = 0xf0;
-pub const OP_GNU_encoded_addr = 0xf1;
-// The GNU implicit pointer extension.
-// See http://www.dwarfstd.org/ShowIssue.php?issue=100831.1&type=open .
-pub const OP_GNU_implicit_pointer = 0xf2;
-// The GNU entry value extension.
-// See http://www.dwarfstd.org/ShowIssue.php?issue=100909.1&type=open .
-pub const OP_GNU_entry_value = 0xf3;
-// The GNU typed stack extension.
-// See http://www.dwarfstd.org/doc/040408.1.html .
-pub const OP_GNU_const_type = 0xf4;
-pub const OP_GNU_regval_type = 0xf5;
-pub const OP_GNU_deref_type = 0xf6;
-pub const OP_GNU_convert = 0xf7;
-pub const OP_GNU_reinterpret = 0xf9;
-// The GNU parameter ref extension.
-pub const OP_GNU_parameter_ref = 0xfa;
-// Extension for Fission. See http://gcc.gnu.org/wiki/DebugFission.
-pub const OP_GNU_addr_index = 0xfb;
-pub const OP_GNU_const_index = 0xfc;
-// HP extensions.
-pub const OP_HP_unknown = 0xe0; // Ouch, the same as GNU_push_tls_address.
-pub const OP_HP_is_value = 0xe1;
-pub const OP_HP_fltconst4 = 0xe2;
-pub const OP_HP_fltconst8 = 0xe3;
-pub const OP_HP_mod_range = 0xe4;
-pub const OP_HP_unmod_range = 0xe5;
-pub const OP_HP_tls = 0xe6;
-// PGI (STMicroelectronics) extensions.
-pub const OP_PGI_omp_thread_num = 0xf8;
-
-pub const ATE_void = 0x0;
-pub const ATE_address = 0x1;
-pub const ATE_boolean = 0x2;
-pub const ATE_complex_float = 0x3;
-pub const ATE_float = 0x4;
-pub const ATE_signed = 0x5;
-pub const ATE_signed_char = 0x6;
-pub const ATE_unsigned = 0x7;
-pub const ATE_unsigned_char = 0x8;
-
-// DWARF 3.
-pub const ATE_imaginary_float = 0x9;
-pub const ATE_packed_decimal = 0xa;
-pub const ATE_numeric_string = 0xb;
-pub const ATE_edited = 0xc;
-pub const ATE_signed_fixed = 0xd;
-pub const ATE_unsigned_fixed = 0xe;
-pub const ATE_decimal_float = 0xf;
-
-// DWARF 4.
-pub const ATE_UTF = 0x10;
-
-pub const ATE_lo_user = 0x80;
-pub const ATE_hi_user = 0xff;
-
-// HP extensions.
-pub const ATE_HP_float80 = 0x80; // Floating-point (80 bit).
-pub const ATE_HP_complex_float80 = 0x81; // Complex floating-point (80 bit).
-pub const ATE_HP_float128 = 0x82; // Floating-point (128 bit).
-pub const ATE_HP_complex_float128 = 0x83; // Complex fp (128 bit).
-pub const ATE_HP_floathpintel = 0x84; // Floating-point (82 bit IA64).
-pub const ATE_HP_imaginary_float80 = 0x85;
-pub const ATE_HP_imaginary_float128 = 0x86;
-pub const ATE_HP_VAX_float = 0x88; // F or G floating.
-pub const ATE_HP_VAX_float_d = 0x89; // D floating.
-pub const ATE_HP_packed_decimal = 0x8a; // Cobol.
-pub const ATE_HP_zoned_decimal = 0x8b; // Cobol.
-pub const ATE_HP_edited = 0x8c; // Cobol.
-pub const ATE_HP_signed_fixed = 0x8d; // Cobol.
-pub const ATE_HP_unsigned_fixed = 0x8e; // Cobol.
-pub const ATE_HP_VAX_complex_float = 0x8f; // F or G floating complex.
-pub const ATE_HP_VAX_complex_float_d = 0x90; // D floating complex.
-
-pub const CFA_advance_loc = 0x40;
-pub const CFA_offset = 0x80;
-pub const CFA_restore = 0xc0;
-pub const CFA_nop = 0x00;
-pub const CFA_set_loc = 0x01;
-pub const CFA_advance_loc1 = 0x02;
-pub const CFA_advance_loc2 = 0x03;
-pub const CFA_advance_loc4 = 0x04;
-pub const CFA_offset_extended = 0x05;
-pub const CFA_restore_extended = 0x06;
-pub const CFA_undefined = 0x07;
-pub const CFA_same_value = 0x08;
-pub const CFA_register = 0x09;
-pub const CFA_remember_state = 0x0a;
-pub const CFA_restore_state = 0x0b;
-pub const CFA_def_cfa = 0x0c;
-pub const CFA_def_cfa_register = 0x0d;
-pub const CFA_def_cfa_offset = 0x0e;
-
-// DWARF 3.
-pub const CFA_def_cfa_expression = 0x0f;
-pub const CFA_expression = 0x10;
-pub const CFA_offset_extended_sf = 0x11;
-pub const CFA_def_cfa_sf = 0x12;
-pub const CFA_def_cfa_offset_sf = 0x13;
-pub const CFA_val_offset = 0x14;
-pub const CFA_val_offset_sf = 0x15;
-pub const CFA_val_expression = 0x16;
-
-pub const CFA_lo_user = 0x1c;
-pub const CFA_hi_user = 0x3f;
-
-// SGI/MIPS specific.
-pub const CFA_MIPS_advance_loc8 = 0x1d;
-
-// GNU extensions.
-pub const CFA_GNU_window_save = 0x2d;
-pub const CFA_GNU_args_size = 0x2e;
-pub const CFA_GNU_negative_offset_extended = 0x2f;
-
-pub const CHILDREN_no = 0x00;
-pub const CHILDREN_yes = 0x01;
-
-pub const LNS_extended_op = 0x00;
-pub const LNS_copy = 0x01;
-pub const LNS_advance_pc = 0x02;
-pub const LNS_advance_line = 0x03;
-pub const LNS_set_file = 0x04;
-pub const LNS_set_column = 0x05;
-pub const LNS_negate_stmt = 0x06;
-pub const LNS_set_basic_block = 0x07;
-pub const LNS_const_add_pc = 0x08;
-pub const LNS_fixed_advance_pc = 0x09;
-pub const LNS_set_prologue_end = 0x0a;
-pub const LNS_set_epilogue_begin = 0x0b;
-pub const LNS_set_isa = 0x0c;
-
-pub const LNE_end_sequence = 0x01;
-pub const LNE_set_address = 0x02;
-pub const LNE_define_file = 0x03;
-pub const LNE_set_discriminator = 0x04;
-pub const LNE_lo_user = 0x80;
-pub const LNE_hi_user = 0xff;
-
-pub const LANG_C89 = 0x0001;
-pub const LANG_C = 0x0002;
-pub const LANG_Ada83 = 0x0003;
-pub const LANG_C_plus_plus = 0x0004;
-pub const LANG_Cobol74 = 0x0005;
-pub const LANG_Cobol85 = 0x0006;
-pub const LANG_Fortran77 = 0x0007;
-pub const LANG_Fortran90 = 0x0008;
-pub const LANG_Pascal83 = 0x0009;
-pub const LANG_Modula2 = 0x000a;
-pub const LANG_Java = 0x000b;
-pub const LANG_C99 = 0x000c;
-pub const LANG_Ada95 = 0x000d;
-pub const LANG_Fortran95 = 0x000e;
-pub const LANG_PLI = 0x000f;
-pub const LANG_ObjC = 0x0010;
-pub const LANG_ObjC_plus_plus = 0x0011;
-pub const LANG_UPC = 0x0012;
-pub const LANG_D = 0x0013;
-pub const LANG_Python = 0x0014;
-pub const LANG_Go = 0x0016;
-pub const LANG_C_plus_plus_11 = 0x001a;
-pub const LANG_Rust = 0x001c;
-pub const LANG_C11 = 0x001d;
-pub const LANG_C_plus_plus_14 = 0x0021;
-pub const LANG_Fortran03 = 0x0022;
-pub const LANG_Fortran08 = 0x0023;
-pub const LANG_lo_user = 0x8000;
-pub const LANG_hi_user = 0xffff;
-pub const LANG_Mips_Assembler = 0x8001;
-pub const LANG_Upc = 0x8765;
-pub const LANG_HP_Bliss = 0x8003;
-pub const LANG_HP_Basic91 = 0x8004;
-pub const LANG_HP_Pascal91 = 0x8005;
-pub const LANG_HP_IMacro = 0x8006;
-pub const LANG_HP_Assembler = 0x8007;
+const std = @import("std.zig");
+const builtin = @import("builtin");
+const debug = std.debug;
+const fs = std.fs;
+const io = std.io;
+const mem = std.mem;
+const math = std.math;
+const leb = @import("debug/leb128.zig");
+
+const ArrayList = std.ArrayList;
+
+usingnamespace @import("dwarf_bits.zig");
+
+pub const DwarfSeekableStream = io.SeekableStream(anyerror, anyerror);
+pub const DwarfInStream = io.InStream(anyerror);
+
+const PcRange = struct {
+ start: u64,
+ end: u64,
+};
+
+const Func = struct {
+ pc_range: ?PcRange,
+ name: ?[]const u8,
+};
+
+const CompileUnit = struct {
+ version: u16,
+ is_64: bool,
+ die: *Die,
+ pc_range: ?PcRange,
+};
+
+const AbbrevTable = ArrayList(AbbrevTableEntry);
+
+const AbbrevTableHeader = struct {
+ // offset from .debug_abbrev
+ offset: u64,
+ table: AbbrevTable,
+};
+
+const AbbrevTableEntry = struct {
+ has_children: bool,
+ abbrev_code: u64,
+ tag_id: u64,
+ attrs: ArrayList(AbbrevAttr),
+};
+
+const AbbrevAttr = struct {
+ attr_id: u64,
+ form_id: u64,
+};
+
+const FormValue = union(enum) {
+ Address: u64,
+ Block: []u8,
+ Const: Constant,
+ ExprLoc: []u8,
+ Flag: bool,
+ SecOffset: u64,
+ Ref: u64,
+ RefAddr: u64,
+ String: []const u8,
+ StrPtr: u64,
+};
+
+const Constant = struct {
+ payload: u64,
+ signed: bool,
+
+ fn asUnsignedLe(self: *const Constant) !u64 {
+ if (self.signed) return error.InvalidDebugInfo;
+ return self.payload;
+ }
+};
+
+const Die = struct {
+ tag_id: u64,
+ has_children: bool,
+ attrs: ArrayList(Attr),
+
+ const Attr = struct {
+ id: u64,
+ value: FormValue,
+ };
+
+ fn getAttr(self: *const Die, id: u64) ?*const FormValue {
+ for (self.attrs.toSliceConst()) |*attr| {
+ if (attr.id == id) return &attr.value;
+ }
+ return null;
+ }
+
+ fn getAttrAddr(self: *const Die, id: u64) !u64 {
+ const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
+ return switch (form_value.*) {
+ FormValue.Address => |value| value,
+ else => error.InvalidDebugInfo,
+ };
+ }
+
+ fn getAttrSecOffset(self: *const Die, id: u64) !u64 {
+ const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
+ return switch (form_value.*) {
+ FormValue.Const => |value| value.asUnsignedLe(),
+ FormValue.SecOffset => |value| value,
+ else => error.InvalidDebugInfo,
+ };
+ }
+
+ fn getAttrUnsignedLe(self: *const Die, id: u64) !u64 {
+ const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
+ return switch (form_value.*) {
+ FormValue.Const => |value| value.asUnsignedLe(),
+ else => error.InvalidDebugInfo,
+ };
+ }
+
+ fn getAttrRef(self: *const Die, id: u64) !u64 {
+ const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
+ return switch (form_value.*) {
+ FormValue.Ref => |value| value,
+ else => error.InvalidDebugInfo,
+ };
+ }
+
+ fn getAttrString(self: *const Die, di: *DwarfInfo, id: u64) ![]const u8 {
+ const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
+ return switch (form_value.*) {
+ FormValue.String => |value| value,
+ FormValue.StrPtr => |offset| di.getString(offset),
+ else => error.InvalidDebugInfo,
+ };
+ }
+};
+
+const FileEntry = struct {
+ file_name: []const u8,
+ dir_index: usize,
+ mtime: usize,
+ len_bytes: usize,
+};
+
+const LineNumberProgram = struct {
+ address: usize,
+ file: usize,
+ line: i64,
+ column: u64,
+ is_stmt: bool,
+ basic_block: bool,
+ end_sequence: bool,
+
+ default_is_stmt: bool,
+ target_address: usize,
+ include_dirs: []const []const u8,
+ file_entries: *ArrayList(FileEntry),
+
+ prev_address: usize,
+ prev_file: usize,
+ prev_line: i64,
+ prev_column: u64,
+ prev_is_stmt: bool,
+ prev_basic_block: bool,
+ prev_end_sequence: bool,
+
+ // Reset the state machine following the DWARF specification
+ pub fn reset(self: *LineNumberProgram) void {
+ self.address = 0;
+ self.file = 1;
+ self.line = 1;
+ self.column = 0;
+ self.is_stmt = self.default_is_stmt;
+ self.basic_block = false;
+ self.end_sequence = false;
+ // Invalidate all the remaining fields
+ self.prev_address = 0;
+ self.prev_file = undefined;
+ self.prev_line = undefined;
+ self.prev_column = undefined;
+ self.prev_is_stmt = undefined;
+ self.prev_basic_block = undefined;
+ self.prev_end_sequence = undefined;
+ }
+
+ pub fn init(is_stmt: bool, include_dirs: []const []const u8, file_entries: *ArrayList(FileEntry), target_address: usize) LineNumberProgram {
+ return LineNumberProgram{
+ .address = 0,
+ .file = 1,
+ .line = 1,
+ .column = 0,
+ .is_stmt = is_stmt,
+ .basic_block = false,
+ .end_sequence = false,
+ .include_dirs = include_dirs,
+ .file_entries = file_entries,
+ .default_is_stmt = is_stmt,
+ .target_address = target_address,
+ .prev_address = 0,
+ .prev_file = undefined,
+ .prev_line = undefined,
+ .prev_column = undefined,
+ .prev_is_stmt = undefined,
+ .prev_basic_block = undefined,
+ .prev_end_sequence = undefined,
+ };
+ }
+
+ pub fn checkLineMatch(self: *LineNumberProgram) !?debug.LineInfo {
+ if (self.target_address >= self.prev_address and self.target_address < self.address) {
+ const file_entry = if (self.prev_file == 0) {
+ return error.MissingDebugInfo;
+ } else if (self.prev_file - 1 >= self.file_entries.len) {
+ return error.InvalidDebugInfo;
+ } else
+ &self.file_entries.items[self.prev_file - 1];
+
+ const dir_name = if (file_entry.dir_index >= self.include_dirs.len) {
+ return error.InvalidDebugInfo;
+ } else
+ self.include_dirs[file_entry.dir_index];
+ const file_name = try fs.path.join(self.file_entries.allocator, &[_][]const u8{ dir_name, file_entry.file_name });
+ errdefer self.file_entries.allocator.free(file_name);
+ return debug.LineInfo{
+ .line = if (self.prev_line >= 0) @intCast(u64, self.prev_line) else 0,
+ .column = self.prev_column,
+ .file_name = file_name,
+ .allocator = self.file_entries.allocator,
+ };
+ }
+
+ self.prev_address = self.address;
+ self.prev_file = self.file;
+ self.prev_line = self.line;
+ self.prev_column = self.column;
+ self.prev_is_stmt = self.is_stmt;
+ self.prev_basic_block = self.basic_block;
+ self.prev_end_sequence = self.end_sequence;
+ return null;
+ }
+};
+
+fn readInitialLength(comptime E: type, in_stream: *io.InStream(E), is_64: *bool) !u64 {
+ const first_32_bits = try in_stream.readIntLittle(u32);
+ is_64.* = (first_32_bits == 0xffffffff);
+ if (is_64.*) {
+ return in_stream.readIntLittle(u64);
+ } else {
+ if (first_32_bits >= 0xfffffff0) return error.InvalidDebugInfo;
+ // TODO this cast should not be needed
+ return @as(u64, first_32_bits);
+ }
+}
+
+// TODO the noasyncs here are workarounds
+fn readAllocBytes(allocator: *mem.Allocator, in_stream: var, size: usize) ![]u8 {
+ const buf = try allocator.alloc(u8, size);
+ errdefer allocator.free(buf);
+ if ((try noasync in_stream.read(buf)) < size) return error.EndOfFile;
+ return buf;
+}
+
+fn parseFormValueBlockLen(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue {
+ const buf = try readAllocBytes(allocator, in_stream, size);
+ return FormValue{ .Block = buf };
+}
+
+// TODO the noasyncs here are workarounds
+fn parseFormValueBlock(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue {
+ const block_len = try noasync in_stream.readVarInt(usize, builtin.Endian.Little, size);
+ return parseFormValueBlockLen(allocator, in_stream, block_len);
+}
+
+fn parseFormValueConstant(allocator: *mem.Allocator, in_stream: var, signed: bool, comptime size: i32) !FormValue {
+ // TODO: Please forgive me, I've worked around zig not properly spilling some intermediate values here.
+ // `noasync` should be removed from all the function calls once it is fixed.
+ return FormValue{
+ .Const = Constant{
+ .signed = signed,
+ .payload = switch (size) {
+ 1 => try noasync in_stream.readIntLittle(u8),
+ 2 => try noasync in_stream.readIntLittle(u16),
+ 4 => try noasync in_stream.readIntLittle(u32),
+ 8 => try noasync in_stream.readIntLittle(u64),
+ -1 => blk: {
+ if (signed) {
+ const x = try noasync leb.readILEB128(i64, in_stream);
+ break :blk @bitCast(u64, x);
+ } else {
+ const x = try noasync leb.readULEB128(u64, in_stream);
+ break :blk x;
+ }
+ },
+ else => @compileError("Invalid size"),
+ },
+ },
+ };
+}
+
+// TODO the noasyncs here are workarounds
+fn parseFormValueDwarfOffsetSize(in_stream: var, is_64: bool) !u64 {
+ return if (is_64) try noasync in_stream.readIntLittle(u64) else @as(u64, try noasync in_stream.readIntLittle(u32));
+}
+
+// TODO the noasyncs here are workarounds
+fn parseFormValueTargetAddrSize(in_stream: var) !u64 {
+ if (@sizeOf(usize) == 4) {
+ // TODO this cast should not be needed
+ return @as(u64, try noasync in_stream.readIntLittle(u32));
+ } else if (@sizeOf(usize) == 8) {
+ return noasync in_stream.readIntLittle(u64);
+ } else {
+ unreachable;
+ }
+}
+
+// TODO the noasyncs here are workarounds
+fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, size: i32) !FormValue {
+ return FormValue{
+ .Ref = switch (size) {
+ 1 => try noasync in_stream.readIntLittle(u8),
+ 2 => try noasync in_stream.readIntLittle(u16),
+ 4 => try noasync in_stream.readIntLittle(u32),
+ 8 => try noasync in_stream.readIntLittle(u64),
+ -1 => try noasync leb.readULEB128(u64, in_stream),
+ else => unreachable,
+ },
+ };
+}
+
+// TODO the noasyncs here are workarounds
+fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64: bool) anyerror!FormValue {
+ return switch (form_id) {
+ FORM_addr => FormValue{ .Address = try parseFormValueTargetAddrSize(in_stream) },
+ FORM_block1 => parseFormValueBlock(allocator, in_stream, 1),
+ FORM_block2 => parseFormValueBlock(allocator, in_stream, 2),
+ FORM_block4 => parseFormValueBlock(allocator, in_stream, 4),
+ FORM_block => x: {
+ const block_len = try noasync leb.readULEB128(usize, in_stream);
+ return parseFormValueBlockLen(allocator, in_stream, block_len);
+ },
+ FORM_data1 => parseFormValueConstant(allocator, in_stream, false, 1),
+ FORM_data2 => parseFormValueConstant(allocator, in_stream, false, 2),
+ FORM_data4 => parseFormValueConstant(allocator, in_stream, false, 4),
+ FORM_data8 => parseFormValueConstant(allocator, in_stream, false, 8),
+ FORM_udata, FORM_sdata => {
+ const signed = form_id == FORM_sdata;
+ return parseFormValueConstant(allocator, in_stream, signed, -1);
+ },
+ FORM_exprloc => {
+ const size = try noasync leb.readULEB128(usize, in_stream);
+ const buf = try readAllocBytes(allocator, in_stream, size);
+ return FormValue{ .ExprLoc = buf };
+ },
+ FORM_flag => FormValue{ .Flag = (try noasync in_stream.readByte()) != 0 },
+ FORM_flag_present => FormValue{ .Flag = true },
+ FORM_sec_offset => FormValue{ .SecOffset = try parseFormValueDwarfOffsetSize(in_stream, is_64) },
+
+ FORM_ref1 => parseFormValueRef(allocator, in_stream, 1),
+ FORM_ref2 => parseFormValueRef(allocator, in_stream, 2),
+ FORM_ref4 => parseFormValueRef(allocator, in_stream, 4),
+ FORM_ref8 => parseFormValueRef(allocator, in_stream, 8),
+ FORM_ref_udata => parseFormValueRef(allocator, in_stream, -1),
+
+ FORM_ref_addr => FormValue{ .RefAddr = try parseFormValueDwarfOffsetSize(in_stream, is_64) },
+ FORM_ref_sig8 => FormValue{ .Ref = try noasync in_stream.readIntLittle(u64) },
+
+ FORM_string => FormValue{ .String = try in_stream.readUntilDelimiterAlloc(allocator, 0, math.maxInt(usize)) },
+ FORM_strp => FormValue{ .StrPtr = try parseFormValueDwarfOffsetSize(in_stream, is_64) },
+ FORM_indirect => {
+ const child_form_id = try noasync leb.readULEB128(u64, in_stream);
+ const F = @TypeOf(async parseFormValue(allocator, in_stream, child_form_id, is_64));
+ var frame = try allocator.create(F);
+ defer allocator.destroy(frame);
+ return await @asyncCall(frame, {}, parseFormValue, allocator, in_stream, child_form_id, is_64);
+ },
+ else => error.InvalidDebugInfo,
+ };
+}
+
+fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*const AbbrevTableEntry {
+ for (abbrev_table.toSliceConst()) |*table_entry| {
+ if (table_entry.abbrev_code == abbrev_code) return table_entry;
+ }
+ return null;
+}
+
+pub const DwarfInfo = struct {
+ endian: builtin.Endian,
+ // No memory is owned by the DwarfInfo
+ debug_info: []const u8,
+ debug_abbrev: []const u8,
+ debug_str: []const u8,
+ debug_line: []const u8,
+ debug_ranges: ?[]const u8,
+ // Filled later by the initializer
+ abbrev_table_list: ArrayList(AbbrevTableHeader) = undefined,
+ compile_unit_list: ArrayList(CompileUnit) = undefined,
+ func_list: ArrayList(Func) = undefined,
+
+ pub fn allocator(self: DwarfInfo) *mem.Allocator {
+ return self.abbrev_table_list.allocator;
+ }
+
+ fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 {
+ for (di.func_list.toSliceConst()) |*func| {
+ if (func.pc_range) |range| {
+ if (address >= range.start and address < range.end) {
+ return func.name;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ fn scanAllFunctions(di: *DwarfInfo) !void {
+ var s = io.SliceSeekableInStream.init(di.debug_info);
+ var this_unit_offset: u64 = 0;
+
+ while (true) {
+ s.seekable_stream.seekTo(this_unit_offset) catch |err| switch (err) {
+ error.EndOfStream => return,
+ else => return err,
+ };
+
+ var is_64: bool = undefined;
+ const unit_length = try readInitialLength(@TypeOf(s.stream.readFn).ReturnType.ErrorSet, &s.stream, &is_64);
+ if (unit_length == 0) return;
+ const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4));
+
+ const version = try s.stream.readInt(u16, di.endian);
+ if (version < 2 or version > 5) return error.InvalidDebugInfo;
+
+ const debug_abbrev_offset = if (is_64) try s.stream.readInt(u64, di.endian) else try s.stream.readInt(u32, di.endian);
+
+ const address_size = try s.stream.readByte();
+ if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo;
+
+ const compile_unit_pos = try s.seekable_stream.getPos();
+ const abbrev_table = try di.getAbbrevTable(debug_abbrev_offset);
+
+ try s.seekable_stream.seekTo(compile_unit_pos);
+
+ const next_unit_pos = this_unit_offset + next_offset;
+
+ while ((try s.seekable_stream.getPos()) < next_unit_pos) {
+ const die_obj = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse continue;
+ const after_die_offset = try s.seekable_stream.getPos();
+
+ switch (die_obj.tag_id) {
+ TAG_subprogram, TAG_inlined_subroutine, TAG_subroutine, TAG_entry_point => {
+ const fn_name = x: {
+ var depth: i32 = 3;
+ var this_die_obj = die_obj;
+ // Prenvent endless loops
+ while (depth > 0) : (depth -= 1) {
+ if (this_die_obj.getAttr(AT_name)) |_| {
+ const name = try this_die_obj.getAttrString(di, AT_name);
+ break :x name;
+ } else if (this_die_obj.getAttr(AT_abstract_origin)) |ref| {
+ // Follow the DIE it points to and repeat
+ const ref_offset = try this_die_obj.getAttrRef(AT_abstract_origin);
+ if (ref_offset > next_offset) return error.InvalidDebugInfo;
+ try s.seekable_stream.seekTo(this_unit_offset + ref_offset);
+ this_die_obj = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse return error.InvalidDebugInfo;
+ } else if (this_die_obj.getAttr(AT_specification)) |ref| {
+ // Follow the DIE it points to and repeat
+ const ref_offset = try this_die_obj.getAttrRef(AT_specification);
+ if (ref_offset > next_offset) return error.InvalidDebugInfo;
+ try s.seekable_stream.seekTo(this_unit_offset + ref_offset);
+ this_die_obj = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse return error.InvalidDebugInfo;
+ } else {
+ break :x null;
+ }
+ }
+
+ break :x null;
+ };
+
+ const pc_range = x: {
+ if (die_obj.getAttrAddr(AT_low_pc)) |low_pc| {
+ if (die_obj.getAttr(AT_high_pc)) |high_pc_value| {
+ const pc_end = switch (high_pc_value.*) {
+ FormValue.Address => |value| value,
+ FormValue.Const => |value| b: {
+ const offset = try value.asUnsignedLe();
+ break :b (low_pc + offset);
+ },
+ else => return error.InvalidDebugInfo,
+ };
+ break :x PcRange{
+ .start = low_pc,
+ .end = pc_end,
+ };
+ } else {
+ break :x null;
+ }
+ } else |err| {
+ if (err != error.MissingDebugInfo) return err;
+ break :x null;
+ }
+ };
+
+ try di.func_list.append(Func{
+ .name = fn_name,
+ .pc_range = pc_range,
+ });
+ },
+ else => {},
+ }
+
+ try s.seekable_stream.seekTo(after_die_offset);
+ }
+
+ this_unit_offset += next_offset;
+ }
+ }
+
+ fn scanAllCompileUnits(di: *DwarfInfo) !void {
+ var s = io.SliceSeekableInStream.init(di.debug_info);
+ var this_unit_offset: u64 = 0;
+
+ while (true) {
+ s.seekable_stream.seekTo(this_unit_offset) catch |err| switch (err) {
+ error.EndOfStream => return,
+ else => return err,
+ };
+
+ var is_64: bool = undefined;
+ const unit_length = try readInitialLength(@TypeOf(s.stream.readFn).ReturnType.ErrorSet, &s.stream, &is_64);
+ if (unit_length == 0) return;
+ const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4));
+
+ const version = try s.stream.readInt(u16, di.endian);
+ if (version < 2 or version > 5) return error.InvalidDebugInfo;
+
+ const debug_abbrev_offset = if (is_64) try s.stream.readInt(u64, di.endian) else try s.stream.readInt(u32, di.endian);
+
+ const address_size = try s.stream.readByte();
+ if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo;
+
+ const compile_unit_pos = try s.seekable_stream.getPos();
+ const abbrev_table = try di.getAbbrevTable(debug_abbrev_offset);
+
+ try s.seekable_stream.seekTo(compile_unit_pos);
+
+ const compile_unit_die = try di.allocator().create(Die);
+ compile_unit_die.* = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse return error.InvalidDebugInfo;
+
+ if (compile_unit_die.tag_id != TAG_compile_unit) return error.InvalidDebugInfo;
+
+ const pc_range = x: {
+ if (compile_unit_die.getAttrAddr(AT_low_pc)) |low_pc| {
+ if (compile_unit_die.getAttr(AT_high_pc)) |high_pc_value| {
+ const pc_end = switch (high_pc_value.*) {
+ FormValue.Address => |value| value,
+ FormValue.Const => |value| b: {
+ const offset = try value.asUnsignedLe();
+ break :b (low_pc + offset);
+ },
+ else => return error.InvalidDebugInfo,
+ };
+ break :x PcRange{
+ .start = low_pc,
+ .end = pc_end,
+ };
+ } else {
+ break :x null;
+ }
+ } else |err| {
+ if (err != error.MissingDebugInfo) return err;
+ break :x null;
+ }
+ };
+
+ try di.compile_unit_list.append(CompileUnit{
+ .version = version,
+ .is_64 = is_64,
+ .pc_range = pc_range,
+ .die = compile_unit_die,
+ });
+
+ this_unit_offset += next_offset;
+ }
+ }
+
+ fn findCompileUnit(di: *DwarfInfo, target_address: u64) !*const CompileUnit {
+ for (di.compile_unit_list.toSlice()) |*compile_unit| {
+ if (compile_unit.pc_range) |range| {
+ if (target_address >= range.start and target_address < range.end) return compile_unit;
+ }
+ if (di.debug_ranges) |debug_ranges| {
+ if (compile_unit.die.getAttrSecOffset(AT_ranges)) |ranges_offset| {
+ var s = io.SliceSeekableInStream.init(debug_ranges);
+
+ // All the addresses in the list are relative to the value
+ // specified by DW_AT_low_pc or to some other value encoded
+ // in the list itself.
+ // If no starting value is specified use zero.
+ var base_address = compile_unit.die.getAttrAddr(AT_low_pc) catch |err| switch (err) {
+ error.MissingDebugInfo => 0,
+ else => return err,
+ };
+
+ try s.seekable_stream.seekTo(ranges_offset);
+
+ while (true) {
+ const begin_addr = try s.stream.readIntLittle(usize);
+ const end_addr = try s.stream.readIntLittle(usize);
+ if (begin_addr == 0 and end_addr == 0) {
+ break;
+ }
+ // This entry selects a new value for the base address
+ if (begin_addr == math.maxInt(usize)) {
+ base_address = end_addr;
+ continue;
+ }
+ if (target_address >= base_address + begin_addr and target_address < base_address + end_addr) {
+ return compile_unit;
+ }
+ }
+ } else |err| {
+ if (err != error.MissingDebugInfo) return err;
+ continue;
+ }
+ }
+ }
+ return error.MissingDebugInfo;
+ }
+
+ /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found,
+ /// seeks in the stream and parses it.
+ fn getAbbrevTable(di: *DwarfInfo, abbrev_offset: u64) !*const AbbrevTable {
+ for (di.abbrev_table_list.toSlice()) |*header| {
+ if (header.offset == abbrev_offset) {
+ return &header.table;
+ }
+ }
+ try di.abbrev_table_list.append(AbbrevTableHeader{
+ .offset = abbrev_offset,
+ .table = try di.parseAbbrevTable(abbrev_offset),
+ });
+ return &di.abbrev_table_list.items[di.abbrev_table_list.len - 1].table;
+ }
+
+ fn parseAbbrevTable(di: *DwarfInfo, offset: u64) !AbbrevTable {
+ var s = io.SliceSeekableInStream.init(di.debug_abbrev);
+
+ try s.seekable_stream.seekTo(offset);
+ var result = AbbrevTable.init(di.allocator());
+ errdefer result.deinit();
+ while (true) {
+ const abbrev_code = try leb.readULEB128(u64, &s.stream);
+ if (abbrev_code == 0) return result;
+ try result.append(AbbrevTableEntry{
+ .abbrev_code = abbrev_code,
+ .tag_id = try leb.readULEB128(u64, &s.stream),
+ .has_children = (try s.stream.readByte()) == CHILDREN_yes,
+ .attrs = ArrayList(AbbrevAttr).init(di.allocator()),
+ });
+ const attrs = &result.items[result.len - 1].attrs;
+
+ while (true) {
+ const attr_id = try leb.readULEB128(u64, &s.stream);
+ const form_id = try leb.readULEB128(u64, &s.stream);
+ if (attr_id == 0 and form_id == 0) break;
+ try attrs.append(AbbrevAttr{
+ .attr_id = attr_id,
+ .form_id = form_id,
+ });
+ }
+ }
+ }
+
+ fn parseDie(di: *DwarfInfo, in_stream: var, abbrev_table: *const AbbrevTable, is_64: bool) !?Die {
+ const abbrev_code = try leb.readULEB128(u64, in_stream);
+ if (abbrev_code == 0) return null;
+ const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return error.InvalidDebugInfo;
+
+ var result = Die{
+ .tag_id = table_entry.tag_id,
+ .has_children = table_entry.has_children,
+ .attrs = ArrayList(Die.Attr).init(di.allocator()),
+ };
+ try result.attrs.resize(table_entry.attrs.len);
+ for (table_entry.attrs.toSliceConst()) |attr, i| {
+ result.attrs.items[i] = Die.Attr{
+ .id = attr.attr_id,
+ .value = try parseFormValue(di.allocator(), in_stream, attr.form_id, is_64),
+ };
+ }
+ return result;
+ }
+
+ fn getLineNumberInfo(di: *DwarfInfo, compile_unit: CompileUnit, target_address: usize) !debug.LineInfo {
+ var s = io.SliceSeekableInStream.init(di.debug_line);
+
+ const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT_comp_dir);
+ const line_info_offset = try compile_unit.die.getAttrSecOffset(AT_stmt_list);
+
+ try s.seekable_stream.seekTo(line_info_offset);
+
+ var is_64: bool = undefined;
+ const unit_length = try readInitialLength(@TypeOf(s.stream.readFn).ReturnType.ErrorSet, &s.stream, &is_64);
+ if (unit_length == 0) {
+ return error.MissingDebugInfo;
+ }
+ const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4));
+
+ const version = try s.stream.readInt(u16, di.endian);
+ // TODO support 3 and 5
+ if (version != 2 and version != 4) return error.InvalidDebugInfo;
+
+ const prologue_length = if (is_64) try s.stream.readInt(u64, di.endian) else try s.stream.readInt(u32, di.endian);
+ const prog_start_offset = (try s.seekable_stream.getPos()) + prologue_length;
+
+ const minimum_instruction_length = try s.stream.readByte();
+ if (minimum_instruction_length == 0) return error.InvalidDebugInfo;
+
+ if (version >= 4) {
+ // maximum_operations_per_instruction
+ _ = try s.stream.readByte();
+ }
+
+ const default_is_stmt = (try s.stream.readByte()) != 0;
+ const line_base = try s.stream.readByteSigned();
+
+ const line_range = try s.stream.readByte();
+ if (line_range == 0) return error.InvalidDebugInfo;
+
+ const opcode_base = try s.stream.readByte();
+
+ const standard_opcode_lengths = try di.allocator().alloc(u8, opcode_base - 1);
+ defer di.allocator().free(standard_opcode_lengths);
+
+ {
+ var i: usize = 0;
+ while (i < opcode_base - 1) : (i += 1) {
+ standard_opcode_lengths[i] = try s.stream.readByte();
+ }
+ }
+
+ var include_directories = ArrayList([]const u8).init(di.allocator());
+ try include_directories.append(compile_unit_cwd);
+ while (true) {
+ const dir = try s.stream.readUntilDelimiterAlloc(di.allocator(), 0, math.maxInt(usize));
+ if (dir.len == 0) break;
+ try include_directories.append(dir);
+ }
+
+ var file_entries = ArrayList(FileEntry).init(di.allocator());
+ var prog = LineNumberProgram.init(default_is_stmt, include_directories.toSliceConst(), &file_entries, target_address);
+
+ while (true) {
+ const file_name = try s.stream.readUntilDelimiterAlloc(di.allocator(), 0, math.maxInt(usize));
+ if (file_name.len == 0) break;
+ const dir_index = try leb.readULEB128(usize, &s.stream);
+ const mtime = try leb.readULEB128(usize, &s.stream);
+ const len_bytes = try leb.readULEB128(usize, &s.stream);
+ try file_entries.append(FileEntry{
+ .file_name = file_name,
+ .dir_index = dir_index,
+ .mtime = mtime,
+ .len_bytes = len_bytes,
+ });
+ }
+
+ try s.seekable_stream.seekTo(prog_start_offset);
+
+ const next_unit_pos = line_info_offset + next_offset;
+
+ while ((try s.seekable_stream.getPos()) < next_unit_pos) {
+ const opcode = try s.stream.readByte();
+
+ if (opcode == LNS_extended_op) {
+ const op_size = try leb.readULEB128(u64, &s.stream);
+ if (op_size < 1) return error.InvalidDebugInfo;
+ var sub_op = try s.stream.readByte();
+ switch (sub_op) {
+ LNE_end_sequence => {
+ prog.end_sequence = true;
+ if (try prog.checkLineMatch()) |info| return info;
+ prog.reset();
+ },
+ LNE_set_address => {
+ const addr = try s.stream.readInt(usize, di.endian);
+ prog.address = addr;
+ },
+ LNE_define_file => {
+ const file_name = try s.stream.readUntilDelimiterAlloc(di.allocator(), 0, math.maxInt(usize));
+ const dir_index = try leb.readULEB128(usize, &s.stream);
+ const mtime = try leb.readULEB128(usize, &s.stream);
+ const len_bytes = try leb.readULEB128(usize, &s.stream);
+ try file_entries.append(FileEntry{
+ .file_name = file_name,
+ .dir_index = dir_index,
+ .mtime = mtime,
+ .len_bytes = len_bytes,
+ });
+ },
+ else => {
+ const fwd_amt = math.cast(isize, op_size - 1) catch return error.InvalidDebugInfo;
+ try s.seekable_stream.seekBy(fwd_amt);
+ },
+ }
+ } else if (opcode >= opcode_base) {
+ // special opcodes
+ const adjusted_opcode = opcode - opcode_base;
+ const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range);
+ const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range);
+ prog.line += inc_line;
+ prog.address += inc_addr;
+ if (try prog.checkLineMatch()) |info| return info;
+ prog.basic_block = false;
+ } else {
+ switch (opcode) {
+ LNS_copy => {
+ if (try prog.checkLineMatch()) |info| return info;
+ prog.basic_block = false;
+ },
+ LNS_advance_pc => {
+ const arg = try leb.readULEB128(usize, &s.stream);
+ prog.address += arg * minimum_instruction_length;
+ },
+ LNS_advance_line => {
+ const arg = try leb.readILEB128(i64, &s.stream);
+ prog.line += arg;
+ },
+ LNS_set_file => {
+ const arg = try leb.readULEB128(usize, &s.stream);
+ prog.file = arg;
+ },
+ LNS_set_column => {
+ const arg = try leb.readULEB128(u64, &s.stream);
+ prog.column = arg;
+ },
+ LNS_negate_stmt => {
+ prog.is_stmt = !prog.is_stmt;
+ },
+ LNS_set_basic_block => {
+ prog.basic_block = true;
+ },
+ LNS_const_add_pc => {
+ const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range);
+ prog.address += inc_addr;
+ },
+ LNS_fixed_advance_pc => {
+ const arg = try s.stream.readInt(u16, di.endian);
+ prog.address += arg;
+ },
+ LNS_set_prologue_end => {},
+ else => {
+ if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo;
+ const len_bytes = standard_opcode_lengths[opcode - 1];
+ try s.seekable_stream.seekBy(len_bytes);
+ },
+ }
+ }
+ }
+
+ return error.MissingDebugInfo;
+ }
+
+ fn getString(di: *DwarfInfo, offset: u64) ![]const u8 {
+ if (offset > di.debug_str.len)
+ return error.InvalidDebugInfo;
+ const casted_offset = math.cast(usize, offset) catch
+ return error.InvalidDebugInfo;
+
+ // Valid strings always have a terminating zero byte
+ if (mem.indexOfScalarPos(u8, di.debug_str, casted_offset, 0)) |last| {
+ return di.debug_str[casted_offset..last];
+ }
+
+ return error.InvalidDebugInfo;
+ }
+};
+
+/// Initialize DWARF info. The caller has the responsibility to initialize most
+/// the DwarfInfo fields before calling. These fields can be left undefined:
+/// * abbrev_table_list
+/// * compile_unit_list
+pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: *mem.Allocator) !void {
+ di.abbrev_table_list = ArrayList(AbbrevTableHeader).init(allocator);
+ di.compile_unit_list = ArrayList(CompileUnit).init(allocator);
+ di.func_list = ArrayList(Func).init(allocator);
+ try di.scanAllFunctions();
+ try di.scanAllCompileUnits();
+}
diff --git a/lib/std/dwarf.zig b/lib/std/dwarf_bits.zig
diff --git a/lib/std/macho.zig b/lib/std/macho.zig
@@ -24,6 +24,17 @@ pub const load_command = extern struct {
cmdsize: u32,
};
+pub const uuid_command = extern struct {
+ /// LC_UUID
+ cmd: u32,
+
+ /// sizeof(struct uuid_command)
+ cmdsize: u32,
+
+ /// the 128-bit uuid
+ uuid: [16]u8,
+};
+
/// The symtab_command contains the offsets and sizes of the link-edit 4.3BSD
/// "stab" style symbol table information as described in the header files
/// <nlist.h> and <stab.h>.
diff --git a/test/stack_traces.zig b/test/stack_traces.zig
@@ -3,6 +3,7 @@ const std = @import("std");
const os = std.os;
const tests = @import("tests.zig");
+// zig fmt: off
pub fn addCases(cases: *tests.StackTracesContext) void {
const source_return =
\\const std = @import("std");
@@ -41,7 +42,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
\\ try foo();
\\}
;
- // zig fmt: off
+
switch (builtin.os) {
.freebsd => {
cases.addCase(
@@ -264,14 +265,14 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
[_][]const u8{
// debug
\\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in _main.0 (test.o)
+ \\source.zig:4:5: [address] in main (test)
\\ return error.TheSkyIsFalling;
\\ ^
\\
,
// release-safe
\\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in _main (test.o)
+ \\source.zig:4:5: [address] in std.start.main (test)
\\ return error.TheSkyIsFalling;
\\ ^
\\
@@ -291,20 +292,20 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
[_][]const u8{
// debug
\\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in _foo (test.o)
+ \\source.zig:4:5: [address] in foo (test)
\\ return error.TheSkyIsFalling;
\\ ^
- \\source.zig:8:5: [address] in _main.0 (test.o)
+ \\source.zig:8:5: [address] in main (test)
\\ try foo();
\\ ^
\\
,
// release-safe
\\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in _main (test.o)
+ \\source.zig:4:5: [address] in std.start.main (test)
\\ return error.TheSkyIsFalling;
\\ ^
- \\source.zig:8:5: [address] in _main (test.o)
+ \\source.zig:8:5: [address] in std.start.main (test)
\\ try foo();
\\ ^
\\
@@ -324,32 +325,32 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
[_][]const u8{
// debug
\\error: TheSkyIsFalling
- \\source.zig:12:5: [address] in _make_error (test.o)
+ \\source.zig:12:5: [address] in make_error (test)
\\ return error.TheSkyIsFalling;
\\ ^
- \\source.zig:8:5: [address] in _bar (test.o)
+ \\source.zig:8:5: [address] in bar (test)
\\ return make_error();
\\ ^
- \\source.zig:4:5: [address] in _foo (test.o)
+ \\source.zig:4:5: [address] in foo (test)
\\ try bar();
\\ ^
- \\source.zig:16:5: [address] in _main.0 (test.o)
+ \\source.zig:16:5: [address] in main (test)
\\ try foo();
\\ ^
\\
,
// release-safe
\\error: TheSkyIsFalling
- \\source.zig:12:5: [address] in _main (test.o)
+ \\source.zig:12:5: [address] in std.start.main (test)
\\ return error.TheSkyIsFalling;
\\ ^
- \\source.zig:8:5: [address] in _main (test.o)
+ \\source.zig:8:5: [address] in std.start.main (test)
\\ return make_error();
\\ ^
- \\source.zig:4:5: [address] in _main (test.o)
+ \\source.zig:4:5: [address] in std.start.main (test)
\\ try bar();
\\ ^
- \\source.zig:16:5: [address] in _main (test.o)
+ \\source.zig:16:5: [address] in std.start.main (test)
\\ try foo();
\\ ^
\\
@@ -393,7 +394,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
source_try_return,
[_][]const u8{
// debug
- \\error: TheSkyIsFalling
+ \\error: TheSkyIsFalling
\\source.zig:4:5: [address] in foo (test.obj)
\\ return error.TheSkyIsFalling;
\\ ^
@@ -419,7 +420,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
source_try_try_return_return,
[_][]const u8{
// debug
- \\error: TheSkyIsFalling
+ \\error: TheSkyIsFalling
\\source.zig:12:5: [address] in make_error (test.obj)
\\ return error.TheSkyIsFalling;
\\ ^
@@ -449,5 +450,5 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
},
else => {},
}
- // zig fmt: off
}
+// zig fmt: off