2207 lines
81 KiB
Zig
2207 lines
81 KiB
Zig
const std = @import("std.zig");
|
|
const math = std.math;
|
|
const mem = std.mem;
|
|
const io = std.io;
|
|
const os = std.os;
|
|
const elf = std.elf;
|
|
const DW = std.dwarf;
|
|
const macho = std.macho;
|
|
const coff = std.coff;
|
|
const pdb = std.pdb;
|
|
const windows = os.windows;
|
|
const ArrayList = std.ArrayList;
|
|
const builtin = @import("builtin");
|
|
const maxInt = std.math.maxInt;
|
|
|
|
pub const FailingAllocator = @import("debug/failing_allocator.zig").FailingAllocator;
|
|
pub const failing_allocator = &FailingAllocator.init(global_allocator, 0).allocator;
|
|
|
|
pub const runtime_safety = switch (builtin.mode) {
|
|
builtin.Mode.Debug, builtin.Mode.ReleaseSafe => true,
|
|
builtin.Mode.ReleaseFast, builtin.Mode.ReleaseSmall => false,
|
|
};
|
|
|
|
const Module = struct {
|
|
mod_info: pdb.ModInfo,
|
|
module_name: []u8,
|
|
obj_file_name: []u8,
|
|
|
|
populated: bool,
|
|
symbols: []u8,
|
|
subsect_info: []u8,
|
|
checksum_offset: ?usize,
|
|
};
|
|
|
|
/// Tries to write to stderr, unbuffered, and ignores any error returned.
|
|
/// Does not append a newline.
|
|
var stderr_file: os.File = undefined;
|
|
var stderr_file_out_stream: os.File.OutStream = undefined;
|
|
|
|
var stderr_stream: ?*io.OutStream(os.File.WriteError) = null;
|
|
var stderr_mutex = std.Mutex.init();
|
|
pub fn warn(comptime fmt: []const u8, args: ...) void {
|
|
const held = stderr_mutex.acquire();
|
|
defer held.release();
|
|
const stderr = getStderrStream() catch return;
|
|
stderr.print(fmt, args) catch return;
|
|
}
|
|
|
|
pub fn getStderrStream() !*io.OutStream(os.File.WriteError) {
|
|
if (stderr_stream) |st| {
|
|
return st;
|
|
} else {
|
|
stderr_file = try io.getStdErr();
|
|
stderr_file_out_stream = stderr_file.outStream();
|
|
const st = &stderr_file_out_stream.stream;
|
|
stderr_stream = st;
|
|
return st;
|
|
}
|
|
}
|
|
|
|
/// TODO multithreaded awareness
|
|
var self_debug_info: ?DebugInfo = null;
|
|
|
|
pub fn getSelfDebugInfo() !*DebugInfo {
|
|
if (self_debug_info) |*info| {
|
|
return info;
|
|
} else {
|
|
self_debug_info = try openSelfDebugInfo(getDebugInfoAllocator());
|
|
return &self_debug_info.?;
|
|
}
|
|
}
|
|
|
|
fn wantTtyColor() bool {
|
|
var bytes: [128]u8 = undefined;
|
|
const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator;
|
|
return if (std.os.getEnvVarOwned(allocator, "ZIG_DEBUG_COLOR")) |_| true else |_| stderr_file.isTty();
|
|
}
|
|
|
|
/// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned.
|
|
/// TODO multithreaded awareness
|
|
pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
|
|
const stderr = getStderrStream() catch return;
|
|
const debug_info = getSelfDebugInfo() catch |err| {
|
|
stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", @errorName(err)) catch return;
|
|
return;
|
|
};
|
|
writeCurrentStackTrace(stderr, debug_info, wantTtyColor(), start_addr) catch |err| {
|
|
stderr.print("Unable to dump stack trace: {}\n", @errorName(err)) catch return;
|
|
return;
|
|
};
|
|
}
|
|
|
|
/// Returns a slice with the same pointer as addresses, with a potentially smaller len.
|
|
/// On Windows, when first_address is not null, we ask for at least 32 stack frames,
|
|
/// and then try to find the first address. If addresses.len is more than 32, we
|
|
/// capture that many stack frames exactly, and then look for the first address,
|
|
/// chopping off the irrelevant frames and shifting so that the returned addresses pointer
|
|
/// equals the passed in addresses pointer.
|
|
pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace) void {
|
|
switch (builtin.os) {
|
|
builtin.Os.windows => {
|
|
const addrs = stack_trace.instruction_addresses;
|
|
const u32_addrs_len = @intCast(u32, addrs.len);
|
|
const first_addr = first_address orelse {
|
|
stack_trace.index = windows.RtlCaptureStackBackTrace(
|
|
0,
|
|
u32_addrs_len,
|
|
@ptrCast(**c_void, addrs.ptr),
|
|
null,
|
|
);
|
|
return;
|
|
};
|
|
var addr_buf_stack: [32]usize = undefined;
|
|
const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs;
|
|
const n = windows.RtlCaptureStackBackTrace(0, u32_addrs_len, @ptrCast(**c_void, addr_buf.ptr), null);
|
|
const first_index = for (addr_buf[0..n]) |addr, i| {
|
|
if (addr == first_addr) {
|
|
break i;
|
|
}
|
|
} else {
|
|
stack_trace.index = 0;
|
|
return;
|
|
};
|
|
const slice = addr_buf[first_index..n];
|
|
// We use a for loop here because slice and addrs may alias.
|
|
for (slice) |addr, i| {
|
|
addrs[i] = addr;
|
|
}
|
|
stack_trace.index = slice.len;
|
|
},
|
|
else => {
|
|
var it = StackIterator.init(first_address);
|
|
for (stack_trace.instruction_addresses) |*addr, i| {
|
|
addr.* = it.next() orelse {
|
|
stack_trace.index = i;
|
|
return;
|
|
};
|
|
}
|
|
stack_trace.index = stack_trace.instruction_addresses.len;
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned.
|
|
/// TODO multithreaded awareness
|
|
pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void {
|
|
const stderr = getStderrStream() catch return;
|
|
const debug_info = getSelfDebugInfo() catch |err| {
|
|
stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", @errorName(err)) catch return;
|
|
return;
|
|
};
|
|
writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, wantTtyColor()) catch |err| {
|
|
stderr.print("Unable to dump stack trace: {}\n", @errorName(err)) catch return;
|
|
return;
|
|
};
|
|
}
|
|
|
|
/// This function invokes undefined behavior when `ok` is `false`.
|
|
/// In Debug and ReleaseSafe modes, calls to this function are always
|
|
/// generated, and the `unreachable` statement triggers a panic.
|
|
/// In ReleaseFast and ReleaseSmall modes, calls to this function are
|
|
/// optimized away, and in fact the optimizer is able to use the assertion
|
|
/// in its heuristics.
|
|
/// Inside a test block, it is best to use the `std.testing` module rather
|
|
/// than this function, because this function may not detect a test failure
|
|
/// in ReleaseFast and ReleaseSafe mode. Outside of a test block, this assert
|
|
/// function is the correct function to use.
|
|
pub fn assert(ok: bool) void {
|
|
if (!ok) unreachable; // assertion failure
|
|
}
|
|
|
|
pub fn panic(comptime format: []const u8, args: ...) noreturn {
|
|
@setCold(true);
|
|
const first_trace_addr = @returnAddress();
|
|
panicExtra(null, first_trace_addr, format, args);
|
|
}
|
|
|
|
/// TODO multithreaded awareness
|
|
var panicking: u8 = 0; // TODO make this a bool
|
|
|
|
pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, comptime format: []const u8, args: ...) noreturn {
|
|
@setCold(true);
|
|
|
|
if (@atomicRmw(u8, &panicking, builtin.AtomicRmwOp.Xchg, 1, builtin.AtomicOrder.SeqCst) == 1) {
|
|
// Panicked during a panic.
|
|
|
|
// TODO detect if a different thread caused the panic, because in that case
|
|
// we would want to return here instead of calling abort, so that the thread
|
|
// which first called panic can finish printing a stack trace.
|
|
os.abort();
|
|
}
|
|
const stderr = getStderrStream() catch os.abort();
|
|
stderr.print(format ++ "\n", args) catch os.abort();
|
|
if (trace) |t| {
|
|
dumpStackTrace(t.*);
|
|
}
|
|
dumpCurrentStackTrace(first_trace_addr);
|
|
|
|
os.abort();
|
|
}
|
|
|
|
const RED = "\x1b[31;1m";
|
|
const GREEN = "\x1b[32;1m";
|
|
const CYAN = "\x1b[36;1m";
|
|
const WHITE = "\x1b[37;1m";
|
|
const DIM = "\x1b[2m";
|
|
const RESET = "\x1b[0m";
|
|
|
|
pub fn writeStackTrace(
|
|
stack_trace: builtin.StackTrace,
|
|
out_stream: var,
|
|
allocator: *mem.Allocator,
|
|
debug_info: *DebugInfo,
|
|
tty_color: bool,
|
|
) !void {
|
|
var frame_index: usize = 0;
|
|
var frames_left: usize = stack_trace.index;
|
|
|
|
while (frames_left != 0) : ({
|
|
frames_left -= 1;
|
|
frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len;
|
|
}) {
|
|
const return_address = stack_trace.instruction_addresses[frame_index];
|
|
try printSourceAtAddress(debug_info, out_stream, return_address, tty_color);
|
|
}
|
|
}
|
|
|
|
pub const StackIterator = struct {
|
|
first_addr: ?usize,
|
|
fp: usize,
|
|
|
|
pub fn init(first_addr: ?usize) StackIterator {
|
|
return StackIterator{
|
|
.first_addr = first_addr,
|
|
.fp = @frameAddress(),
|
|
};
|
|
}
|
|
|
|
fn next(self: *StackIterator) ?usize {
|
|
if (self.fp == 0) return null;
|
|
self.fp = @intToPtr(*const usize, self.fp).*;
|
|
if (self.fp == 0) return null;
|
|
|
|
if (self.first_addr) |addr| {
|
|
while (self.fp != 0) : (self.fp = @intToPtr(*const usize, self.fp).*) {
|
|
const return_address = @intToPtr(*const usize, self.fp + @sizeOf(usize)).*;
|
|
if (addr == return_address) {
|
|
self.first_addr = null;
|
|
return return_address;
|
|
}
|
|
}
|
|
}
|
|
|
|
const return_address = @intToPtr(*const usize, self.fp + @sizeOf(usize)).*;
|
|
return return_address;
|
|
}
|
|
};
|
|
|
|
pub fn writeCurrentStackTrace(out_stream: var, debug_info: *DebugInfo, tty_color: bool, start_addr: ?usize) !void {
|
|
switch (builtin.os) {
|
|
builtin.Os.windows => return writeCurrentStackTraceWindows(out_stream, debug_info, tty_color, start_addr),
|
|
else => {},
|
|
}
|
|
var it = StackIterator.init(start_addr);
|
|
while (it.next()) |return_address| {
|
|
try printSourceAtAddress(debug_info, out_stream, return_address, tty_color);
|
|
}
|
|
}
|
|
|
|
pub fn writeCurrentStackTraceWindows(
|
|
out_stream: var,
|
|
debug_info: *DebugInfo,
|
|
tty_color: bool,
|
|
start_addr: ?usize,
|
|
) !void {
|
|
var addr_buf: [1024]usize = undefined;
|
|
const n = windows.RtlCaptureStackBackTrace(0, addr_buf.len, @ptrCast(**c_void, &addr_buf), null);
|
|
const addrs = addr_buf[0..n];
|
|
var start_i: usize = if (start_addr) |saddr| blk: {
|
|
for (addrs) |addr, i| {
|
|
if (addr == saddr) break :blk i;
|
|
}
|
|
return;
|
|
} else 0;
|
|
for (addrs[start_i..]) |addr| {
|
|
try printSourceAtAddress(debug_info, out_stream, addr, tty_color);
|
|
}
|
|
}
|
|
|
|
pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: usize, tty_color: bool) !void {
|
|
switch (builtin.os) {
|
|
builtin.Os.macosx => return printSourceAtAddressMacOs(debug_info, out_stream, address, tty_color),
|
|
builtin.Os.linux, builtin.Os.freebsd, builtin.Os.netbsd => return printSourceAtAddressLinux(debug_info, out_stream, address, tty_color),
|
|
builtin.Os.windows => return printSourceAtAddressWindows(debug_info, out_stream, address, tty_color),
|
|
else => return error.UnsupportedOperatingSystem,
|
|
}
|
|
}
|
|
|
|
fn printSourceAtAddressWindows(di: *DebugInfo, out_stream: var, relocated_address: usize, tty_color: bool) !void {
|
|
const allocator = getDebugInfoAllocator();
|
|
const base_address = os.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
|
|
if (tty_color) {
|
|
try out_stream.print("???:?:?: ");
|
|
setTtyColor(TtyColor.Dim);
|
|
try out_stream.print("0x{x} in ??? (???)", relocated_address);
|
|
setTtyColor(TtyColor.Reset);
|
|
try out_stream.print("\n\n\n");
|
|
} else {
|
|
try out_stream.print("???:?:?: 0x{x} in ??? (???)\n\n\n", relocated_address);
|
|
}
|
|
return;
|
|
};
|
|
|
|
const mod = &di.modules[mod_index];
|
|
try populateModule(di, mod);
|
|
const obj_basename = os.path.basename(mod.obj_file_name);
|
|
|
|
var symbol_i: usize = 0;
|
|
const symbol_name = 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) {
|
|
pdb.SymbolKind.S_LPROC32 => {
|
|
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([*]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;
|
|
}
|
|
};
|
|
|
|
if (tty_color) {
|
|
setTtyColor(TtyColor.White);
|
|
if (opt_line_info) |li| {
|
|
try out_stream.print("{}:{}:{}", li.file_name, li.line, li.column);
|
|
} else {
|
|
try out_stream.print("???:?:?");
|
|
}
|
|
setTtyColor(TtyColor.Reset);
|
|
try out_stream.print(": ");
|
|
setTtyColor(TtyColor.Dim);
|
|
try out_stream.print("0x{x} in {} ({})", relocated_address, symbol_name, obj_basename);
|
|
setTtyColor(TtyColor.Reset);
|
|
|
|
if (opt_line_info) |line_info| {
|
|
try out_stream.print("\n");
|
|
if (printLineFromFileAnyOs(out_stream, line_info)) {
|
|
if (line_info.column == 0) {
|
|
try out_stream.write("\n");
|
|
} else {
|
|
{
|
|
var col_i: usize = 1;
|
|
while (col_i < line_info.column) : (col_i += 1) {
|
|
try out_stream.writeByte(' ');
|
|
}
|
|
}
|
|
setTtyColor(TtyColor.Green);
|
|
try out_stream.write("^");
|
|
setTtyColor(TtyColor.Reset);
|
|
try out_stream.write("\n");
|
|
}
|
|
} else |err| switch (err) {
|
|
error.EndOfFile => {},
|
|
error.FileNotFound => {
|
|
setTtyColor(TtyColor.Dim);
|
|
try out_stream.write("file not found\n\n");
|
|
setTtyColor(TtyColor.White);
|
|
},
|
|
else => return err,
|
|
}
|
|
} else {
|
|
try out_stream.print("\n\n\n");
|
|
}
|
|
} else {
|
|
if (opt_line_info) |li| {
|
|
try out_stream.print("{}:{}:{}: 0x{x} in {} ({})\n\n\n", li.file_name, li.line, li.column, relocated_address, symbol_name, obj_basename);
|
|
} else {
|
|
try out_stream.print("???:?:?: 0x{x} in {} ({})\n\n\n", relocated_address, symbol_name, obj_basename);
|
|
}
|
|
}
|
|
}
|
|
|
|
const TtyColor = enum {
|
|
Red,
|
|
Green,
|
|
Cyan,
|
|
White,
|
|
Dim,
|
|
Bold,
|
|
Reset,
|
|
};
|
|
|
|
/// TODO this is a special case hack right now. clean it up and maybe make it part of std.fmt
|
|
fn setTtyColor(tty_color: TtyColor) void {
|
|
if (os.supportsAnsiEscapeCodes(stderr_file.handle)) {
|
|
switch (tty_color) {
|
|
TtyColor.Red => {
|
|
stderr_file.write(RED) catch return;
|
|
},
|
|
TtyColor.Green => {
|
|
stderr_file.write(GREEN) catch return;
|
|
},
|
|
TtyColor.Cyan => {
|
|
stderr_file.write(CYAN) catch return;
|
|
},
|
|
TtyColor.White, TtyColor.Bold => {
|
|
stderr_file.write(WHITE) catch return;
|
|
},
|
|
TtyColor.Dim => {
|
|
stderr_file.write(DIM) catch return;
|
|
},
|
|
TtyColor.Reset => {
|
|
stderr_file.write(RESET) catch return;
|
|
},
|
|
}
|
|
} else {
|
|
const S = struct {
|
|
var attrs: windows.WORD = undefined;
|
|
var init_attrs = false;
|
|
};
|
|
if (!S.init_attrs) {
|
|
S.init_attrs = true;
|
|
var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
|
|
// TODO handle error
|
|
_ = windows.GetConsoleScreenBufferInfo(stderr_file.handle, &info);
|
|
S.attrs = info.wAttributes;
|
|
}
|
|
|
|
// TODO handle errors
|
|
switch (tty_color) {
|
|
TtyColor.Red => {
|
|
_ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY);
|
|
},
|
|
TtyColor.Green => {
|
|
_ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY);
|
|
},
|
|
TtyColor.Cyan => {
|
|
_ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY);
|
|
},
|
|
TtyColor.White, TtyColor.Bold => {
|
|
_ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY);
|
|
},
|
|
TtyColor.Dim => {
|
|
_ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_INTENSITY);
|
|
},
|
|
TtyColor.Reset => {
|
|
_ = windows.SetConsoleTextAttribute(stderr_file.handle, S.attrs);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn populateModule(di: *DebugInfo, mod: *Module) !void {
|
|
if (mod.populated)
|
|
return;
|
|
const allocator = getDebugInfoAllocator();
|
|
|
|
// At most one can be non-zero.
|
|
if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0)
|
|
return error.InvalidDebugInfo;
|
|
|
|
if (mod.mod_info.C13ByteSize == 0)
|
|
return;
|
|
|
|
const modi = di.pdb.getStreamById(mod.mod_info.ModuleSymStream) orelse return error.MissingDebugInfo;
|
|
|
|
const signature = try modi.stream.readIntLittle(u32);
|
|
if (signature != 4)
|
|
return error.InvalidDebugInfo;
|
|
|
|
mod.symbols = try allocator.alloc(u8, mod.mod_info.SymByteSize - 4);
|
|
try modi.stream.readNoEof(mod.symbols);
|
|
|
|
mod.subsect_info = try allocator.alloc(u8, mod.mod_info.C13ByteSize);
|
|
try modi.stream.readNoEof(mod.subsect_info);
|
|
|
|
var sect_offset: usize = 0;
|
|
var skip_len: usize = undefined;
|
|
while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) {
|
|
const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &mod.subsect_info[sect_offset]);
|
|
skip_len = subsect_hdr.Length;
|
|
sect_offset += @sizeOf(pdb.DebugSubsectionHeader);
|
|
|
|
switch (subsect_hdr.Kind) {
|
|
pdb.DebugSubsectionKind.FileChecksums => {
|
|
mod.checksum_offset = sect_offset;
|
|
break;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
if (sect_offset > mod.subsect_info.len)
|
|
return error.InvalidDebugInfo;
|
|
}
|
|
|
|
mod.populated = true;
|
|
}
|
|
|
|
fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
|
|
var min: usize = 0;
|
|
var max: usize = symbols.len - 1; // Exclude sentinel.
|
|
while (min < max) {
|
|
const mid = min + (max - min) / 2;
|
|
const curr = &symbols[mid];
|
|
const next = &symbols[mid + 1];
|
|
if (address >= next.address()) {
|
|
min = mid + 1;
|
|
} else if (address < curr.address()) {
|
|
max = mid;
|
|
} else {
|
|
return curr;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
fn printSourceAtAddressMacOs(di: *DebugInfo, out_stream: var, address: usize, tty_color: bool) !void {
|
|
const base_addr = std.os.getBaseAddress();
|
|
const adjusted_addr = 0x100000000 + (address - base_addr);
|
|
|
|
const symbol = machoSearchSymbols(di.symbols, adjusted_addr) orelse {
|
|
if (tty_color) {
|
|
try out_stream.print("???:?:?: " ++ DIM ++ "0x{x} in ??? (???)" ++ RESET ++ "\n\n\n", address);
|
|
} else {
|
|
try out_stream.print("???:?:?: 0x{x} in ??? (???)\n\n\n", address);
|
|
}
|
|
return;
|
|
};
|
|
|
|
const symbol_name = mem.toSliceConst(u8, di.strings.ptr + symbol.nlist.n_strx);
|
|
const compile_unit_name = if (symbol.ofile) |ofile| blk: {
|
|
const ofile_path = mem.toSliceConst(u8, di.strings.ptr + ofile.n_strx);
|
|
break :blk os.path.basename(ofile_path);
|
|
} else "???";
|
|
if (getLineNumberInfoMacOs(di, symbol.*, adjusted_addr)) |line_info| {
|
|
defer line_info.deinit();
|
|
try printLineInfo(
|
|
out_stream,
|
|
line_info,
|
|
address,
|
|
symbol_name,
|
|
compile_unit_name,
|
|
tty_color,
|
|
printLineFromFileAnyOs,
|
|
);
|
|
} else |err| switch (err) {
|
|
error.MissingDebugInfo, error.InvalidDebugInfo => {
|
|
if (tty_color) {
|
|
try out_stream.print("???:?:?: " ++ DIM ++ "0x{x} in {} ({})" ++ RESET ++ "\n\n\n", address, symbol_name, compile_unit_name);
|
|
} else {
|
|
try out_stream.print("???:?:?: 0x{x} in {} ({})\n\n\n", address, symbol_name, compile_unit_name);
|
|
}
|
|
},
|
|
else => return err,
|
|
}
|
|
}
|
|
|
|
/// This function works in freestanding mode.
|
|
/// fn printLineFromFile(out_stream: var, line_info: LineInfo) !void
|
|
pub fn printSourceAtAddressDwarf(
|
|
debug_info: *DwarfInfo,
|
|
out_stream: var,
|
|
address: usize,
|
|
tty_color: bool,
|
|
comptime printLineFromFile: var,
|
|
) !void {
|
|
const compile_unit = findCompileUnit(debug_info, address) catch {
|
|
if (tty_color) {
|
|
try out_stream.print("???:?:?: " ++ DIM ++ "0x{x} in ??? (???)" ++ RESET ++ "\n\n\n", address);
|
|
} else {
|
|
try out_stream.print("???:?:?: 0x{x} in ??? (???)\n\n\n", address);
|
|
}
|
|
return;
|
|
};
|
|
const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name);
|
|
if (getLineNumberInfoDwarf(debug_info, compile_unit.*, address - 1)) |line_info| {
|
|
defer line_info.deinit();
|
|
const symbol_name = "???";
|
|
try printLineInfo(
|
|
out_stream,
|
|
line_info,
|
|
address,
|
|
symbol_name,
|
|
compile_unit_name,
|
|
tty_color,
|
|
printLineFromFile,
|
|
);
|
|
} else |err| switch (err) {
|
|
error.MissingDebugInfo, error.InvalidDebugInfo => {
|
|
if (tty_color) {
|
|
try out_stream.print("???:?:?: " ++ DIM ++ "0x{x} in ??? ({})" ++ RESET ++ "\n\n\n", address, compile_unit_name);
|
|
} else {
|
|
try out_stream.print("???:?:?: 0x{x} in ??? ({})\n\n\n", address, compile_unit_name);
|
|
}
|
|
},
|
|
else => return err,
|
|
}
|
|
}
|
|
|
|
pub fn printSourceAtAddressLinux(debug_info: *DebugInfo, out_stream: var, address: usize, tty_color: bool) !void {
|
|
return printSourceAtAddressDwarf(debug_info, out_stream, address, tty_color, printLineFromFileAnyOs);
|
|
}
|
|
|
|
fn printLineInfo(
|
|
out_stream: var,
|
|
line_info: LineInfo,
|
|
address: usize,
|
|
symbol_name: []const u8,
|
|
compile_unit_name: []const u8,
|
|
tty_color: bool,
|
|
comptime printLineFromFile: var,
|
|
) !void {
|
|
if (tty_color) {
|
|
try out_stream.print(
|
|
WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++ DIM ++ "0x{x} in {} ({})" ++ RESET ++ "\n",
|
|
line_info.file_name,
|
|
line_info.line,
|
|
line_info.column,
|
|
address,
|
|
symbol_name,
|
|
compile_unit_name,
|
|
);
|
|
if (printLineFromFile(out_stream, line_info)) {
|
|
if (line_info.column == 0) {
|
|
try out_stream.write("\n");
|
|
} else {
|
|
{
|
|
var col_i: usize = 1;
|
|
while (col_i < line_info.column) : (col_i += 1) {
|
|
try out_stream.writeByte(' ');
|
|
}
|
|
}
|
|
try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n");
|
|
}
|
|
} else |err| switch (err) {
|
|
error.EndOfFile => {},
|
|
else => return err,
|
|
}
|
|
} else {
|
|
try out_stream.print(
|
|
"{}:{}:{}: 0x{x} in {} ({})\n",
|
|
line_info.file_name,
|
|
line_info.line,
|
|
line_info.column,
|
|
address,
|
|
symbol_name,
|
|
compile_unit_name,
|
|
);
|
|
}
|
|
}
|
|
|
|
// TODO use this
|
|
pub const OpenSelfDebugInfoError = error{
|
|
MissingDebugInfo,
|
|
OutOfMemory,
|
|
UnsupportedOperatingSystem,
|
|
};
|
|
|
|
pub fn openSelfDebugInfo(allocator: *mem.Allocator) !DebugInfo {
|
|
switch (builtin.os) {
|
|
builtin.Os.linux, builtin.Os.freebsd, builtin.Os.netbsd => return openSelfDebugInfoLinux(allocator),
|
|
builtin.Os.macosx, builtin.Os.ios => return openSelfDebugInfoMacOs(allocator),
|
|
builtin.Os.windows => return openSelfDebugInfoWindows(allocator),
|
|
else => return error.UnsupportedOperatingSystem,
|
|
}
|
|
}
|
|
|
|
fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo {
|
|
const self_file = try os.openSelfExe();
|
|
defer self_file.close();
|
|
|
|
const coff_obj = try allocator.create(coff.Coff);
|
|
coff_obj.* = coff.Coff{
|
|
.in_file = self_file,
|
|
.allocator = allocator,
|
|
.coff_header = undefined,
|
|
.pe_header = undefined,
|
|
.sections = undefined,
|
|
.guid = undefined,
|
|
.age = undefined,
|
|
};
|
|
|
|
var di = DebugInfo{
|
|
.coff = coff_obj,
|
|
.pdb = undefined,
|
|
.sect_contribs = undefined,
|
|
.modules = undefined,
|
|
};
|
|
|
|
try di.coff.loadHeader();
|
|
|
|
var path_buf: [windows.MAX_PATH]u8 = undefined;
|
|
const len = try di.coff.getPdbPath(path_buf[0..]);
|
|
const raw_path = path_buf[0..len];
|
|
|
|
const path = try os.path.resolve(allocator, [][]const u8{raw_path});
|
|
|
|
try di.pdb.openFile(di.coff, path);
|
|
|
|
var pdb_stream = di.pdb.getStream(pdb.StreamType.Pdb) orelse return error.InvalidDebugInfo;
|
|
const version = try pdb_stream.stream.readIntLittle(u32);
|
|
const signature = try pdb_stream.stream.readIntLittle(u32);
|
|
const age = try pdb_stream.stream.readIntLittle(u32);
|
|
var guid: [16]u8 = undefined;
|
|
try pdb_stream.stream.readNoEof(guid[0..]);
|
|
if (!mem.eql(u8, di.coff.guid, guid) or di.coff.age != age)
|
|
return error.InvalidDebugInfo;
|
|
// We validated the executable and pdb match.
|
|
|
|
const string_table_index = str_tab_index: {
|
|
const name_bytes_len = try pdb_stream.stream.readIntLittle(u32);
|
|
const name_bytes = try allocator.alloc(u8, name_bytes_len);
|
|
try pdb_stream.stream.readNoEof(name_bytes);
|
|
|
|
const HashTableHeader = packed struct {
|
|
Size: u32,
|
|
Capacity: u32,
|
|
|
|
fn maxLoad(cap: u32) u32 {
|
|
return cap * 2 / 3 + 1;
|
|
}
|
|
};
|
|
const hash_tbl_hdr = try pdb_stream.stream.readStruct(HashTableHeader);
|
|
if (hash_tbl_hdr.Capacity == 0)
|
|
return error.InvalidDebugInfo;
|
|
|
|
if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity))
|
|
return error.InvalidDebugInfo;
|
|
|
|
const present = try readSparseBitVector(&pdb_stream.stream, allocator);
|
|
if (present.len != hash_tbl_hdr.Size)
|
|
return error.InvalidDebugInfo;
|
|
const deleted = try readSparseBitVector(&pdb_stream.stream, allocator);
|
|
|
|
const Bucket = struct {
|
|
first: u32,
|
|
second: u32,
|
|
};
|
|
const bucket_list = try allocator.alloc(Bucket, present.len);
|
|
for (present) |_| {
|
|
const name_offset = try pdb_stream.stream.readIntLittle(u32);
|
|
const name_index = try pdb_stream.stream.readIntLittle(u32);
|
|
const name = mem.toSlice(u8, name_bytes.ptr + name_offset);
|
|
if (mem.eql(u8, name, "/names")) {
|
|
break :str_tab_index name_index;
|
|
}
|
|
}
|
|
return error.MissingDebugInfo;
|
|
};
|
|
|
|
di.pdb.string_table = di.pdb.getStreamById(string_table_index) orelse return error.InvalidDebugInfo;
|
|
di.pdb.dbi = di.pdb.getStream(pdb.StreamType.Dbi) orelse return error.MissingDebugInfo;
|
|
|
|
const dbi = di.pdb.dbi;
|
|
|
|
// Dbi Header
|
|
const dbi_stream_header = try dbi.stream.readStruct(pdb.DbiStreamHeader);
|
|
const mod_info_size = dbi_stream_header.ModInfoSize;
|
|
const section_contrib_size = dbi_stream_header.SectionContributionSize;
|
|
|
|
var modules = ArrayList(Module).init(allocator);
|
|
|
|
// Module Info Substream
|
|
var mod_info_offset: usize = 0;
|
|
while (mod_info_offset != mod_info_size) {
|
|
const mod_info = try dbi.stream.readStruct(pdb.ModInfo);
|
|
var this_record_len: usize = @sizeOf(pdb.ModInfo);
|
|
|
|
const module_name = try dbi.readNullTermString(allocator);
|
|
this_record_len += module_name.len + 1;
|
|
|
|
const obj_file_name = try dbi.readNullTermString(allocator);
|
|
this_record_len += obj_file_name.len + 1;
|
|
|
|
if (this_record_len % 4 != 0) {
|
|
const round_to_next_4 = (this_record_len | 0x3) + 1;
|
|
const march_forward_bytes = round_to_next_4 - this_record_len;
|
|
try dbi.seekForward(march_forward_bytes);
|
|
this_record_len += march_forward_bytes;
|
|
}
|
|
|
|
try modules.append(Module{
|
|
.mod_info = mod_info,
|
|
.module_name = module_name,
|
|
.obj_file_name = obj_file_name,
|
|
|
|
.populated = false,
|
|
.symbols = undefined,
|
|
.subsect_info = undefined,
|
|
.checksum_offset = null,
|
|
});
|
|
|
|
mod_info_offset += this_record_len;
|
|
if (mod_info_offset > mod_info_size)
|
|
return error.InvalidDebugInfo;
|
|
}
|
|
|
|
di.modules = modules.toOwnedSlice();
|
|
|
|
// Section Contribution Substream
|
|
var sect_contribs = ArrayList(pdb.SectionContribEntry).init(allocator);
|
|
var sect_cont_offset: usize = 0;
|
|
if (section_contrib_size != 0) {
|
|
const ver = @intToEnum(pdb.SectionContrSubstreamVersion, try dbi.stream.readIntLittle(u32));
|
|
if (ver != pdb.SectionContrSubstreamVersion.Ver60)
|
|
return error.InvalidDebugInfo;
|
|
sect_cont_offset += @sizeOf(u32);
|
|
}
|
|
while (sect_cont_offset != section_contrib_size) {
|
|
const entry = try sect_contribs.addOne();
|
|
entry.* = try dbi.stream.readStruct(pdb.SectionContribEntry);
|
|
sect_cont_offset += @sizeOf(pdb.SectionContribEntry);
|
|
|
|
if (sect_cont_offset > section_contrib_size)
|
|
return error.InvalidDebugInfo;
|
|
}
|
|
|
|
di.sect_contribs = sect_contribs.toOwnedSlice();
|
|
|
|
return di;
|
|
}
|
|
|
|
fn readSparseBitVector(stream: var, allocator: *mem.Allocator) ![]usize {
|
|
const num_words = try stream.readIntLittle(u32);
|
|
var word_i: usize = 0;
|
|
var list = ArrayList(usize).init(allocator);
|
|
while (word_i != num_words) : (word_i += 1) {
|
|
const word = try stream.readIntLittle(u32);
|
|
var bit_i: u5 = 0;
|
|
while (true) : (bit_i += 1) {
|
|
if (word & (u32(1) << bit_i) != 0) {
|
|
try list.append(word_i * 32 + bit_i);
|
|
}
|
|
if (bit_i == maxInt(u5)) break;
|
|
}
|
|
}
|
|
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.offset,
|
|
.size = elf_header.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);
|
|
try scanAllCompileUnits(di);
|
|
}
|
|
|
|
pub fn openElfDebugInfo(
|
|
allocator: *mem.Allocator,
|
|
elf_seekable_stream: *DwarfSeekableStream,
|
|
elf_in_stream: *DwarfInStream,
|
|
) !DwarfInfo {
|
|
var efile: elf.Elf = undefined;
|
|
try efile.openStream(allocator, elf_seekable_stream, elf_in_stream);
|
|
errdefer efile.close();
|
|
|
|
var di = DwarfInfo{
|
|
.dwarf_seekable_stream = elf_seekable_stream,
|
|
.dwarf_in_stream = elf_in_stream,
|
|
.endian = efile.endian,
|
|
.debug_info = (try findDwarfSectionFromElf(&efile, ".debug_info")) orelse return error.MissingDebugInfo,
|
|
.debug_abbrev = (try findDwarfSectionFromElf(&efile, ".debug_abbrev")) orelse return error.MissingDebugInfo,
|
|
.debug_str = (try findDwarfSectionFromElf(&efile, ".debug_str")) orelse return error.MissingDebugInfo,
|
|
.debug_line = (try findDwarfSectionFromElf(&efile, ".debug_line")) orelse return error.MissingDebugInfo,
|
|
.debug_ranges = (try findDwarfSectionFromElf(&efile, ".debug_ranges")),
|
|
.abbrev_table_list = undefined,
|
|
.compile_unit_list = undefined,
|
|
};
|
|
try openDwarfDebugInfo(&di, allocator);
|
|
return di;
|
|
}
|
|
|
|
fn openSelfDebugInfoLinux(allocator: *mem.Allocator) !DwarfInfo {
|
|
const S = struct {
|
|
var self_exe_file: os.File = undefined;
|
|
var self_exe_seekable_stream: os.File.SeekableStream = undefined;
|
|
var self_exe_in_stream: os.File.InStream = undefined;
|
|
};
|
|
S.self_exe_file = try os.openSelfExe();
|
|
errdefer S.self_exe_file.close();
|
|
|
|
S.self_exe_seekable_stream = S.self_exe_file.seekableStream();
|
|
S.self_exe_in_stream = S.self_exe_file.inStream();
|
|
|
|
return openElfDebugInfo(
|
|
allocator,
|
|
// TODO https://github.com/ziglang/zig/issues/764
|
|
@ptrCast(*DwarfSeekableStream, &S.self_exe_seekable_stream.stream),
|
|
// TODO https://github.com/ziglang/zig/issues/764
|
|
@ptrCast(*DwarfInStream, &S.self_exe_in_stream.stream),
|
|
);
|
|
}
|
|
|
|
fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo {
|
|
const hdr = &std.c._mh_execute_header;
|
|
assert(hdr.magic == std.macho.MH_MAGIC_64);
|
|
|
|
const hdr_base = @ptrCast([*]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);
|
|
switch (lc.cmd) {
|
|
std.macho.LC_SYMTAB => break @ptrCast(*std.macho.symtab_command, ptr),
|
|
else => {},
|
|
}
|
|
ptr += lc.cmdsize; // TODO https://github.com/ziglang/zig/issues/1403
|
|
} 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 symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
|
|
|
|
var ofile: ?*macho.nlist_64 = null;
|
|
var reloc: u64 = 0;
|
|
var symbol_index: usize = 0;
|
|
var last_len: u64 = 0;
|
|
for (syms) |*sym| {
|
|
if (sym.n_type & std.macho.N_STAB != 0) {
|
|
switch (sym.n_type) {
|
|
std.macho.N_OSO => {
|
|
ofile = sym;
|
|
reloc = 0;
|
|
},
|
|
std.macho.N_FUN => {
|
|
if (sym.n_sect == 0) {
|
|
last_len = sym.n_value;
|
|
} else {
|
|
symbols_buf[symbol_index] = MachoSymbol{
|
|
.nlist = sym,
|
|
.ofile = ofile,
|
|
.reloc = reloc,
|
|
};
|
|
symbol_index += 1;
|
|
}
|
|
},
|
|
std.macho.N_BNSYM => {
|
|
if (reloc == 0) {
|
|
reloc = sym.n_value;
|
|
}
|
|
},
|
|
else => continue,
|
|
}
|
|
}
|
|
}
|
|
const sentinel = try allocator.create(macho.nlist_64);
|
|
sentinel.* = macho.nlist_64{
|
|
.n_strx = 0,
|
|
.n_type = 36,
|
|
.n_sect = 0,
|
|
.n_desc = 0,
|
|
.n_value = symbols_buf[symbol_index - 1].nlist.n_value + last_len,
|
|
};
|
|
|
|
const symbols = allocator.shrink(symbols_buf, symbol_index);
|
|
|
|
// Even though lld emits symbols in ascending order, this debug code
|
|
// should work for programs linked in any valid way.
|
|
// This sort is so that we can binary search later.
|
|
std.sort.sort(MachoSymbol, symbols, MachoSymbol.addressLessThan);
|
|
|
|
return DebugInfo{
|
|
.ofiles = DebugInfo.OFileTable.init(allocator),
|
|
.symbols = symbols,
|
|
.strings = strings,
|
|
};
|
|
}
|
|
|
|
fn printLineFromFileAnyOs(out_stream: var, line_info: LineInfo) !void {
|
|
var f = try os.File.openRead(line_info.file_name);
|
|
defer f.close();
|
|
// TODO fstat and make sure that the file has the correct size
|
|
|
|
var buf: [os.page_size]u8 = undefined;
|
|
var line: usize = 1;
|
|
var column: usize = 1;
|
|
var abs_index: usize = 0;
|
|
while (true) {
|
|
const amt_read = try f.read(buf[0..]);
|
|
const slice = buf[0..amt_read];
|
|
|
|
for (slice) |byte| {
|
|
if (line == line_info.line) {
|
|
try out_stream.writeByte(byte);
|
|
if (byte == '\n') {
|
|
return;
|
|
}
|
|
}
|
|
if (byte == '\n') {
|
|
line += 1;
|
|
column = 1;
|
|
} else {
|
|
column += 1;
|
|
}
|
|
}
|
|
|
|
if (amt_read < buf.len) return error.EndOfFile;
|
|
}
|
|
}
|
|
|
|
const MachoSymbol = struct {
|
|
nlist: *macho.nlist_64,
|
|
ofile: ?*macho.nlist_64,
|
|
reloc: u64,
|
|
|
|
/// Returns the address from the macho file
|
|
fn address(self: MachoSymbol) u64 {
|
|
return self.nlist.n_value;
|
|
}
|
|
|
|
fn addressLessThan(lhs: MachoSymbol, rhs: MachoSymbol) bool {
|
|
return lhs.address() < rhs.address();
|
|
}
|
|
};
|
|
|
|
const MachOFile = struct {
|
|
bytes: []align(@alignOf(macho.mach_header_64)) const u8,
|
|
sect_debug_info: ?*const macho.section_64,
|
|
sect_debug_line: ?*const macho.section_64,
|
|
};
|
|
|
|
pub const DwarfSeekableStream = io.SeekableStream(anyerror, anyerror);
|
|
pub const DwarfInStream = io.InStream(anyerror);
|
|
|
|
pub const DwarfInfo = struct {
|
|
dwarf_seekable_stream: *DwarfSeekableStream,
|
|
dwarf_in_stream: *DwarfInStream,
|
|
endian: builtin.Endian,
|
|
debug_info: Section,
|
|
debug_abbrev: Section,
|
|
debug_str: Section,
|
|
debug_line: Section,
|
|
debug_ranges: ?Section,
|
|
abbrev_table_list: ArrayList(AbbrevTableHeader),
|
|
compile_unit_list: ArrayList(CompileUnit),
|
|
|
|
pub const Section = struct {
|
|
offset: usize,
|
|
size: usize,
|
|
};
|
|
|
|
pub fn allocator(self: DwarfInfo) *mem.Allocator {
|
|
return self.abbrev_table_list.allocator;
|
|
}
|
|
|
|
pub fn readString(self: *DwarfInfo) ![]u8 {
|
|
return readStringRaw(self.allocator(), self.dwarf_in_stream);
|
|
}
|
|
};
|
|
|
|
pub const DebugInfo = switch (builtin.os) {
|
|
builtin.Os.macosx, builtin.Os.ios => struct {
|
|
symbols: []const MachoSymbol,
|
|
strings: []const u8,
|
|
ofiles: OFileTable,
|
|
|
|
const OFileTable = std.HashMap(
|
|
*macho.nlist_64,
|
|
MachOFile,
|
|
std.hash_map.getHashPtrAddrFn(*macho.nlist_64),
|
|
std.hash_map.getTrivialEqlFn(*macho.nlist_64),
|
|
);
|
|
|
|
pub fn allocator(self: DebugInfo) *mem.Allocator {
|
|
return self.ofiles.allocator;
|
|
}
|
|
},
|
|
builtin.Os.uefi, builtin.Os.windows => struct {
|
|
pdb: pdb.Pdb,
|
|
coff: *coff.Coff,
|
|
sect_contribs: []pdb.SectionContribEntry,
|
|
modules: []Module,
|
|
},
|
|
builtin.Os.linux, builtin.Os.freebsd, builtin.Os.netbsd => DwarfInfo,
|
|
else => @compileError("Unsupported OS"),
|
|
};
|
|
|
|
const PcRange = struct {
|
|
start: u64,
|
|
end: u64,
|
|
};
|
|
|
|
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: []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 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| getString(di, offset),
|
|
else => error.InvalidDebugInfo,
|
|
};
|
|
}
|
|
};
|
|
|
|
const FileEntry = struct {
|
|
file_name: []const u8,
|
|
dir_index: usize,
|
|
mtime: usize,
|
|
len_bytes: usize,
|
|
};
|
|
|
|
pub const LineInfo = struct {
|
|
line: usize,
|
|
column: usize,
|
|
file_name: []const u8,
|
|
allocator: ?*mem.Allocator,
|
|
|
|
fn deinit(self: LineInfo) void {
|
|
const allocator = self.allocator orelse return;
|
|
allocator.free(self.file_name);
|
|
}
|
|
};
|
|
|
|
const LineNumberProgram = struct {
|
|
address: usize,
|
|
file: usize,
|
|
line: isize,
|
|
column: usize,
|
|
is_stmt: bool,
|
|
basic_block: bool,
|
|
end_sequence: bool,
|
|
|
|
target_address: usize,
|
|
include_dirs: []const []const u8,
|
|
file_entries: *ArrayList(FileEntry),
|
|
|
|
prev_address: usize,
|
|
prev_file: usize,
|
|
prev_line: isize,
|
|
prev_column: usize,
|
|
prev_is_stmt: bool,
|
|
prev_basic_block: bool,
|
|
prev_end_sequence: bool,
|
|
|
|
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,
|
|
.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) !?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 os.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(usize, 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 readStringRaw(allocator: *mem.Allocator, in_stream: var) ![]u8 {
|
|
var buf = ArrayList(u8).init(allocator);
|
|
while (true) {
|
|
const byte = try in_stream.readByte();
|
|
if (byte == 0) break;
|
|
try buf.append(byte);
|
|
}
|
|
return buf.toSlice();
|
|
}
|
|
|
|
fn getString(di: *DwarfInfo, offset: u64) ![]u8 {
|
|
const pos = di.debug_str.offset + offset;
|
|
try di.dwarf_seekable_stream.seekTo(pos);
|
|
return di.readString();
|
|
}
|
|
|
|
fn readAllocBytes(allocator: *mem.Allocator, in_stream: var, size: usize) ![]u8 {
|
|
const buf = try allocator.alloc(u8, size);
|
|
errdefer allocator.free(buf);
|
|
if ((try 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 };
|
|
}
|
|
|
|
fn parseFormValueBlock(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue {
|
|
const block_len = try 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, size: i32) !FormValue {
|
|
return FormValue{
|
|
.Const = Constant{
|
|
.signed = signed,
|
|
.payload = switch (size) {
|
|
1 => try in_stream.readIntLittle(u8),
|
|
2 => try in_stream.readIntLittle(u16),
|
|
4 => try in_stream.readIntLittle(u32),
|
|
8 => try in_stream.readIntLittle(u64),
|
|
-1 => if (signed) try readULeb128(in_stream) else @intCast(u64, try readILeb128(in_stream)),
|
|
else => unreachable,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
fn parseFormValueDwarfOffsetSize(in_stream: var, is_64: bool) !u64 {
|
|
return if (is_64) try in_stream.readIntLittle(u64) else u64(try in_stream.readIntLittle(u32));
|
|
}
|
|
|
|
fn parseFormValueTargetAddrSize(in_stream: var) !u64 {
|
|
return if (@sizeOf(usize) == 4) u64(try in_stream.readIntLittle(u32)) else if (@sizeOf(usize) == 8) try in_stream.readIntLittle(u64) else unreachable;
|
|
}
|
|
|
|
fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, size: i32) !FormValue {
|
|
return FormValue{
|
|
.Ref = switch (size) {
|
|
1 => try in_stream.readIntLittle(u8),
|
|
2 => try in_stream.readIntLittle(u16),
|
|
4 => try in_stream.readIntLittle(u32),
|
|
8 => try in_stream.readIntLittle(u64),
|
|
-1 => try readULeb128(in_stream),
|
|
else => unreachable,
|
|
},
|
|
};
|
|
}
|
|
|
|
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 readULeb128(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 readULeb128(in_stream);
|
|
const buf = try readAllocBytes(allocator, in_stream, size);
|
|
return FormValue{ .ExprLoc = buf };
|
|
},
|
|
DW.FORM_flag => FormValue{ .Flag = (try 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 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 readULeb128(in_stream);
|
|
return parseFormValue(allocator, in_stream, child_form_id, is_64);
|
|
},
|
|
else => error.InvalidDebugInfo,
|
|
};
|
|
}
|
|
|
|
fn parseAbbrevTable(di: *DwarfInfo) !AbbrevTable {
|
|
var result = AbbrevTable.init(di.allocator());
|
|
while (true) {
|
|
const abbrev_code = try readULeb128(di.dwarf_in_stream);
|
|
if (abbrev_code == 0) return result;
|
|
try result.append(AbbrevTableEntry{
|
|
.abbrev_code = abbrev_code,
|
|
.tag_id = try readULeb128(di.dwarf_in_stream),
|
|
.has_children = (try di.dwarf_in_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 readULeb128(di.dwarf_in_stream);
|
|
const form_id = try readULeb128(di.dwarf_in_stream);
|
|
if (attr_id == 0 and form_id == 0) break;
|
|
try attrs.append(AbbrevAttr{
|
|
.attr_id = attr_id,
|
|
.form_id = form_id,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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.dwarf_seekable_stream.seekTo(di.debug_abbrev.offset + abbrev_offset);
|
|
try di.abbrev_table_list.append(AbbrevTableHeader{
|
|
.offset = abbrev_offset,
|
|
.table = try parseAbbrevTable(di),
|
|
});
|
|
return &di.abbrev_table_list.items[di.abbrev_table_list.len - 1].table;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
fn parseDie(di: *DwarfInfo, abbrev_table: *const AbbrevTable, is_64: bool) !Die {
|
|
const abbrev_code = try readULeb128(di.dwarf_in_stream);
|
|
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(), di.dwarf_in_stream, attr.form_id, is_64),
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, target_address: usize) !LineInfo {
|
|
const ofile = symbol.ofile orelse return error.MissingDebugInfo;
|
|
const gop = try di.ofiles.getOrPut(ofile);
|
|
const mach_o_file = if (gop.found_existing) &gop.kv.value else blk: {
|
|
errdefer _ = di.ofiles.remove(ofile);
|
|
const ofile_path = mem.toSliceConst(u8, di.strings.ptr + ofile.n_strx);
|
|
|
|
gop.kv.value = MachOFile{
|
|
.bytes = try std.io.readFileAllocAligned(di.ofiles.allocator, ofile_path, @alignOf(macho.mach_header_64)),
|
|
.sect_debug_info = null,
|
|
.sect_debug_line = null,
|
|
};
|
|
const hdr = @ptrCast(*const macho.mach_header_64, gop.kv.value.bytes.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)),
|
|
else => {},
|
|
}
|
|
ptr += lc.cmdsize; // TODO https://github.com/ziglang/zig/issues/1403
|
|
} else {
|
|
return error.MissingDebugInfo;
|
|
};
|
|
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| {
|
|
if (sect.flags & macho.SECTION_TYPE == macho.S_REGULAR and
|
|
(sect.flags & macho.SECTION_ATTRIBUTES) & macho.S_ATTR_DEBUG == macho.S_ATTR_DEBUG)
|
|
{
|
|
const sect_name = mem.toSliceConst(u8, §.sectname);
|
|
if (mem.eql(u8, sect_name, "__debug_line")) {
|
|
gop.kv.value.sect_debug_line = sect;
|
|
} else if (mem.eql(u8, sect_name, "__debug_info")) {
|
|
gop.kv.value.sect_debug_info = sect;
|
|
}
|
|
}
|
|
}
|
|
|
|
break :blk &gop.kv.value;
|
|
};
|
|
|
|
const sect_debug_line = mach_o_file.sect_debug_line orelse return error.MissingDebugInfo;
|
|
var ptr = mach_o_file.bytes.ptr + sect_debug_line.offset;
|
|
|
|
var is_64: bool = undefined;
|
|
const unit_length = try readInitialLengthMem(&ptr, &is_64);
|
|
if (unit_length == 0) return error.MissingDebugInfo;
|
|
|
|
const version = readIntMem(&ptr, u16, builtin.Endian.Little);
|
|
// TODO support 3 and 5
|
|
if (version != 2 and version != 4) return error.InvalidDebugInfo;
|
|
|
|
const prologue_length = if (is_64)
|
|
readIntMem(&ptr, u64, builtin.Endian.Little)
|
|
else
|
|
readIntMem(&ptr, u32, builtin.Endian.Little);
|
|
const prog_start = ptr + prologue_length;
|
|
|
|
const minimum_instruction_length = readByteMem(&ptr);
|
|
if (minimum_instruction_length == 0) return error.InvalidDebugInfo;
|
|
|
|
if (version >= 4) {
|
|
// maximum_operations_per_instruction
|
|
ptr += 1;
|
|
}
|
|
|
|
const default_is_stmt = readByteMem(&ptr) != 0;
|
|
const line_base = readByteSignedMem(&ptr);
|
|
|
|
const line_range = readByteMem(&ptr);
|
|
if (line_range == 0) return error.InvalidDebugInfo;
|
|
|
|
const opcode_base = readByteMem(&ptr);
|
|
|
|
const standard_opcode_lengths = ptr[0 .. opcode_base - 1];
|
|
ptr += opcode_base - 1;
|
|
|
|
var include_directories = ArrayList([]const u8).init(di.allocator());
|
|
try include_directories.append("");
|
|
while (true) {
|
|
const dir = readStringMem(&ptr);
|
|
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 = readStringMem(&ptr);
|
|
if (file_name.len == 0) break;
|
|
const dir_index = try readULeb128Mem(&ptr);
|
|
const mtime = try readULeb128Mem(&ptr);
|
|
const len_bytes = try readULeb128Mem(&ptr);
|
|
try file_entries.append(FileEntry{
|
|
.file_name = file_name,
|
|
.dir_index = dir_index,
|
|
.mtime = mtime,
|
|
.len_bytes = len_bytes,
|
|
});
|
|
}
|
|
|
|
ptr = prog_start;
|
|
while (true) {
|
|
const opcode = readByteMem(&ptr);
|
|
|
|
if (opcode == DW.LNS_extended_op) {
|
|
const op_size = try readULeb128Mem(&ptr);
|
|
if (op_size < 1) return error.InvalidDebugInfo;
|
|
var sub_op = readByteMem(&ptr);
|
|
switch (sub_op) {
|
|
DW.LNE_end_sequence => {
|
|
prog.end_sequence = true;
|
|
if (try prog.checkLineMatch()) |info| return info;
|
|
return error.MissingDebugInfo;
|
|
},
|
|
DW.LNE_set_address => {
|
|
const addr = readIntMem(&ptr, usize, builtin.Endian.Little);
|
|
prog.address = symbol.reloc + addr;
|
|
},
|
|
DW.LNE_define_file => {
|
|
const file_name = readStringMem(&ptr);
|
|
const dir_index = try readULeb128Mem(&ptr);
|
|
const mtime = try readULeb128Mem(&ptr);
|
|
const len_bytes = try readULeb128Mem(&ptr);
|
|
try file_entries.append(FileEntry{
|
|
.file_name = file_name,
|
|
.dir_index = dir_index,
|
|
.mtime = mtime,
|
|
.len_bytes = len_bytes,
|
|
});
|
|
},
|
|
else => {
|
|
ptr += op_size - 1;
|
|
},
|
|
}
|
|
} 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 = i32(line_base) + 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 readULeb128Mem(&ptr);
|
|
prog.address += arg * minimum_instruction_length;
|
|
},
|
|
DW.LNS_advance_line => {
|
|
const arg = try readILeb128Mem(&ptr);
|
|
prog.line += arg;
|
|
},
|
|
DW.LNS_set_file => {
|
|
const arg = try readULeb128Mem(&ptr);
|
|
prog.file = arg;
|
|
},
|
|
DW.LNS_set_column => {
|
|
const arg = try readULeb128Mem(&ptr);
|
|
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 = readIntMem(&ptr, u16, builtin.Endian.Little);
|
|
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];
|
|
ptr += len_bytes;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
return error.MissingDebugInfo;
|
|
}
|
|
|
|
fn getLineNumberInfoDwarf(di: *DwarfInfo, compile_unit: CompileUnit, target_address: usize) !LineInfo {
|
|
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);
|
|
|
|
assert(line_info_offset < di.debug_line.size);
|
|
|
|
try di.dwarf_seekable_stream.seekTo(di.debug_line.offset + line_info_offset);
|
|
|
|
var is_64: bool = undefined;
|
|
const unit_length = try readInitialLength(@typeOf(di.dwarf_in_stream.readFn).ReturnType.ErrorSet, di.dwarf_in_stream, &is_64);
|
|
if (unit_length == 0) {
|
|
return error.MissingDebugInfo;
|
|
}
|
|
const next_offset = unit_length + (if (is_64) usize(12) else usize(4));
|
|
|
|
const version = try di.dwarf_in_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 di.dwarf_in_stream.readInt(u64, di.endian) else try di.dwarf_in_stream.readInt(u32, di.endian);
|
|
const prog_start_offset = (try di.dwarf_seekable_stream.getPos()) + prologue_length;
|
|
|
|
const minimum_instruction_length = try di.dwarf_in_stream.readByte();
|
|
if (minimum_instruction_length == 0) return error.InvalidDebugInfo;
|
|
|
|
if (version >= 4) {
|
|
// maximum_operations_per_instruction
|
|
_ = try di.dwarf_in_stream.readByte();
|
|
}
|
|
|
|
const default_is_stmt = (try di.dwarf_in_stream.readByte()) != 0;
|
|
const line_base = try di.dwarf_in_stream.readByteSigned();
|
|
|
|
const line_range = try di.dwarf_in_stream.readByte();
|
|
if (line_range == 0) return error.InvalidDebugInfo;
|
|
|
|
const opcode_base = try di.dwarf_in_stream.readByte();
|
|
|
|
const standard_opcode_lengths = try di.allocator().alloc(u8, opcode_base - 1);
|
|
|
|
{
|
|
var i: usize = 0;
|
|
while (i < opcode_base - 1) : (i += 1) {
|
|
standard_opcode_lengths[i] = try di.dwarf_in_stream.readByte();
|
|
}
|
|
}
|
|
|
|
var include_directories = ArrayList([]u8).init(di.allocator());
|
|
try include_directories.append(compile_unit_cwd);
|
|
while (true) {
|
|
const dir = try di.readString();
|
|
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 di.readString();
|
|
if (file_name.len == 0) break;
|
|
const dir_index = try readULeb128(di.dwarf_in_stream);
|
|
const mtime = try readULeb128(di.dwarf_in_stream);
|
|
const len_bytes = try readULeb128(di.dwarf_in_stream);
|
|
try file_entries.append(FileEntry{
|
|
.file_name = file_name,
|
|
.dir_index = dir_index,
|
|
.mtime = mtime,
|
|
.len_bytes = len_bytes,
|
|
});
|
|
}
|
|
|
|
try di.dwarf_seekable_stream.seekTo(prog_start_offset);
|
|
|
|
while (true) {
|
|
const opcode = try di.dwarf_in_stream.readByte();
|
|
|
|
if (opcode == DW.LNS_extended_op) {
|
|
const op_size = try readULeb128(di.dwarf_in_stream);
|
|
if (op_size < 1) return error.InvalidDebugInfo;
|
|
var sub_op = try di.dwarf_in_stream.readByte();
|
|
switch (sub_op) {
|
|
DW.LNE_end_sequence => {
|
|
prog.end_sequence = true;
|
|
if (try prog.checkLineMatch()) |info| return info;
|
|
return error.MissingDebugInfo;
|
|
},
|
|
DW.LNE_set_address => {
|
|
const addr = try di.dwarf_in_stream.readInt(usize, di.endian);
|
|
prog.address = addr;
|
|
},
|
|
DW.LNE_define_file => {
|
|
const file_name = try di.readString();
|
|
const dir_index = try readULeb128(di.dwarf_in_stream);
|
|
const mtime = try readULeb128(di.dwarf_in_stream);
|
|
const len_bytes = try readULeb128(di.dwarf_in_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 di.dwarf_seekable_stream.seekForward(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 = i32(line_base) + 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 readULeb128(di.dwarf_in_stream);
|
|
prog.address += arg * minimum_instruction_length;
|
|
},
|
|
DW.LNS_advance_line => {
|
|
const arg = try readILeb128(di.dwarf_in_stream);
|
|
prog.line += arg;
|
|
},
|
|
DW.LNS_set_file => {
|
|
const arg = try readULeb128(di.dwarf_in_stream);
|
|
prog.file = arg;
|
|
},
|
|
DW.LNS_set_column => {
|
|
const arg = try readULeb128(di.dwarf_in_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 di.dwarf_in_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 di.dwarf_seekable_stream.seekForward(len_bytes);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
return error.MissingDebugInfo;
|
|
}
|
|
|
|
fn scanAllCompileUnits(di: *DwarfInfo) !void {
|
|
const debug_info_end = di.debug_info.offset + di.debug_info.size;
|
|
var this_unit_offset = di.debug_info.offset;
|
|
|
|
while (this_unit_offset < debug_info_end) {
|
|
try di.dwarf_seekable_stream.seekTo(this_unit_offset);
|
|
|
|
var is_64: bool = undefined;
|
|
const unit_length = try readInitialLength(@typeOf(di.dwarf_in_stream.readFn).ReturnType.ErrorSet, di.dwarf_in_stream, &is_64);
|
|
if (unit_length == 0) return;
|
|
const next_offset = unit_length + (if (is_64) usize(12) else usize(4));
|
|
|
|
const version = try di.dwarf_in_stream.readInt(u16, di.endian);
|
|
if (version < 2 or version > 5) return error.InvalidDebugInfo;
|
|
|
|
const debug_abbrev_offset = if (is_64) try di.dwarf_in_stream.readInt(u64, di.endian) else try di.dwarf_in_stream.readInt(u32, di.endian);
|
|
|
|
const address_size = try di.dwarf_in_stream.readByte();
|
|
if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo;
|
|
|
|
const compile_unit_pos = try di.dwarf_seekable_stream.getPos();
|
|
const abbrev_table = try getAbbrevTable(di, debug_abbrev_offset);
|
|
|
|
try di.dwarf_seekable_stream.seekTo(compile_unit_pos);
|
|
|
|
const compile_unit_die = try di.allocator().create(Die);
|
|
compile_unit_die.* = try parseDie(di, abbrev_table, is_64);
|
|
|
|
if (compile_unit_die.tag_id != DW.TAG_compile_unit) return error.InvalidDebugInfo;
|
|
|
|
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;
|
|
}
|
|
} 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 (compile_unit.die.getAttrSecOffset(DW.AT_ranges)) |ranges_offset| {
|
|
var base_address: usize = 0;
|
|
if (di.debug_ranges) |debug_ranges| {
|
|
try di.dwarf_seekable_stream.seekTo(debug_ranges.offset + ranges_offset);
|
|
while (true) {
|
|
const begin_addr = try di.dwarf_in_stream.readIntLittle(usize);
|
|
const end_addr = try di.dwarf_in_stream.readIntLittle(usize);
|
|
if (begin_addr == 0 and end_addr == 0) {
|
|
break;
|
|
}
|
|
if (begin_addr == maxInt(usize)) {
|
|
base_address = begin_addr;
|
|
continue;
|
|
}
|
|
if (target_address >= begin_addr and target_address < end_addr) {
|
|
return compile_unit;
|
|
}
|
|
}
|
|
}
|
|
} else |err| {
|
|
if (err != error.MissingDebugInfo) return err;
|
|
continue;
|
|
}
|
|
}
|
|
return error.MissingDebugInfo;
|
|
}
|
|
|
|
fn readIntMem(ptr: *[*]const u8, comptime T: type, endian: builtin.Endian) T {
|
|
// TODO https://github.com/ziglang/zig/issues/863
|
|
const result = mem.readIntSlice(T, ptr.*[0..@sizeOf(T)], endian);
|
|
ptr.* += @sizeOf(T);
|
|
return result;
|
|
}
|
|
|
|
fn readByteMem(ptr: *[*]const u8) u8 {
|
|
const result = ptr.*[0];
|
|
ptr.* += 1;
|
|
return result;
|
|
}
|
|
|
|
fn readByteSignedMem(ptr: *[*]const u8) i8 {
|
|
return @bitCast(i8, readByteMem(ptr));
|
|
}
|
|
|
|
fn readInitialLengthMem(ptr: *[*]const u8, is_64: *bool) !u64 {
|
|
// TODO this code can be improved with https://github.com/ziglang/zig/issues/863
|
|
const first_32_bits = mem.readIntSliceLittle(u32, ptr.*[0..4]);
|
|
is_64.* = (first_32_bits == 0xffffffff);
|
|
if (is_64.*) {
|
|
ptr.* += 4;
|
|
const result = mem.readIntSliceLittle(u64, ptr.*[0..8]);
|
|
ptr.* += 8;
|
|
return result;
|
|
} else {
|
|
if (first_32_bits >= 0xfffffff0) return error.InvalidDebugInfo;
|
|
ptr.* += 4;
|
|
return u64(first_32_bits);
|
|
}
|
|
}
|
|
|
|
fn readStringMem(ptr: *[*]const u8) []const u8 {
|
|
const result = mem.toSliceConst(u8, ptr.*);
|
|
ptr.* += result.len + 1;
|
|
return result;
|
|
}
|
|
|
|
fn readULeb128Mem(ptr: *[*]const u8) !u64 {
|
|
var result: u64 = 0;
|
|
var shift: usize = 0;
|
|
var i: usize = 0;
|
|
|
|
while (true) {
|
|
const byte = ptr.*[i];
|
|
i += 1;
|
|
|
|
var operand: u64 = undefined;
|
|
|
|
if (@shlWithOverflow(u64, byte & 0b01111111, @intCast(u6, shift), &operand)) return error.InvalidDebugInfo;
|
|
|
|
result |= operand;
|
|
|
|
if ((byte & 0b10000000) == 0) {
|
|
ptr.* += i;
|
|
return result;
|
|
}
|
|
|
|
shift += 7;
|
|
}
|
|
}
|
|
fn readILeb128Mem(ptr: *[*]const u8) !i64 {
|
|
var result: i64 = 0;
|
|
var shift: usize = 0;
|
|
var i: usize = 0;
|
|
|
|
while (true) {
|
|
const byte = ptr.*[i];
|
|
i += 1;
|
|
|
|
var operand: i64 = undefined;
|
|
if (@shlWithOverflow(i64, byte & 0b01111111, @intCast(u6, shift), &operand)) return error.InvalidDebugInfo;
|
|
|
|
result |= operand;
|
|
shift += 7;
|
|
|
|
if ((byte & 0b10000000) == 0) {
|
|
if (shift < @sizeOf(i64) * 8 and (byte & 0b01000000) != 0) result |= -(i64(1) << @intCast(u6, shift));
|
|
ptr.* += i;
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
return u64(first_32_bits);
|
|
}
|
|
}
|
|
|
|
fn readULeb128(in_stream: var) !u64 {
|
|
var result: u64 = 0;
|
|
var shift: usize = 0;
|
|
|
|
while (true) {
|
|
const byte = try in_stream.readByte();
|
|
|
|
var operand: u64 = undefined;
|
|
|
|
if (@shlWithOverflow(u64, byte & 0b01111111, @intCast(u6, shift), &operand)) return error.InvalidDebugInfo;
|
|
|
|
result |= operand;
|
|
|
|
if ((byte & 0b10000000) == 0) return result;
|
|
|
|
shift += 7;
|
|
}
|
|
}
|
|
|
|
fn readILeb128(in_stream: var) !i64 {
|
|
var result: i64 = 0;
|
|
var shift: usize = 0;
|
|
|
|
while (true) {
|
|
const byte = try in_stream.readByte();
|
|
|
|
var operand: i64 = undefined;
|
|
|
|
if (@shlWithOverflow(i64, byte & 0b01111111, @intCast(u6, shift), &operand)) return error.InvalidDebugInfo;
|
|
|
|
result |= operand;
|
|
shift += 7;
|
|
|
|
if ((byte & 0b10000000) == 0) {
|
|
if (shift < @sizeOf(i64) * 8 and (byte & 0b01000000) != 0) result |= -(i64(1) << @intCast(u6, shift));
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This should only be used in temporary test programs.
|
|
pub const global_allocator = &global_fixed_allocator.allocator;
|
|
var global_fixed_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(global_allocator_mem[0..]);
|
|
var global_allocator_mem: [100 * 1024]u8 = undefined;
|
|
|
|
/// TODO multithreaded awareness
|
|
var debug_info_allocator: ?*mem.Allocator = null;
|
|
var debug_info_direct_allocator: std.heap.DirectAllocator = undefined;
|
|
var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;
|
|
fn getDebugInfoAllocator() *mem.Allocator {
|
|
if (debug_info_allocator) |a| return a;
|
|
|
|
debug_info_direct_allocator = std.heap.DirectAllocator.init();
|
|
debug_info_arena_allocator = std.heap.ArenaAllocator.init(&debug_info_direct_allocator.allocator);
|
|
debug_info_allocator = &debug_info_arena_allocator.allocator;
|
|
return &debug_info_arena_allocator.allocator;
|
|
}
|