zig/lib/std/debug.zig

1854 lines
72 KiB
Zig
Raw Normal View History

// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2020 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
2019-03-02 13:46:04 -08:00
const std = @import("std.zig");
const builtin = std.builtin;
const math = std.math;
const mem = std.mem;
const io = std.io;
const os = std.os;
2019-05-26 10:17:34 -07:00
const fs = std.fs;
const process = std.process;
const elf = std.elf;
const DW = std.dwarf;
const macho = std.macho;
const coff = std.coff;
const pdb = std.pdb;
const ArrayList = std.ArrayList;
const root = @import("root");
const maxInt = std.math.maxInt;
2019-05-24 19:52:07 -07:00
const File = std.fs.File;
2019-05-26 10:17:34 -07:00
const windows = std.os.windows;
2016-08-17 20:11:04 -07:00
pub const runtime_safety = switch (builtin.mode) {
2019-05-26 10:17:34 -07:00
.Debug, .ReleaseSafe => true,
.ReleaseFast, .ReleaseSmall => false,
};
const Module = struct {
mod_info: pdb.ModInfo,
module_name: []u8,
obj_file_name: []u8,
populated: bool,
symbols: []u8,
subsect_info: []u8,
2018-09-02 14:58:50 -07:00
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);
}
};
var stderr_mutex = std.Mutex{};
/// Deprecated. Use `std.log` functions for logging or `std.debug.print` for
/// "printf debugging".
pub const warn = print;
/// Print to stderr, unbuffered, and silently returning on failure. Intended
/// for use in "printf debugging." Use `std.log` functions for proper logging.
2020-07-11 04:09:04 -07:00
pub fn print(comptime fmt: []const u8, args: anytype) void {
const held = stderr_mutex.acquire();
defer held.release();
const stderr = io.getStdErr().writer();
2020-05-05 04:14:22 -07:00
nosuspend stderr.print(fmt, args) catch return;
}
pub fn getStderrMutex() *std.Mutex {
return &stderr_mutex;
}
/// 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.?;
}
}
pub fn detectTTYConfig() TTY.Config {
var bytes: [128]u8 = undefined;
const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator;
if (process.getEnvVarOwned(allocator, "ZIG_DEBUG_COLOR")) |_| {
return .escape_codes;
} else |_| {
const stderr_file = io.getStdErr();
if (stderr_file.supportsAnsiEscapeCodes()) {
return .escape_codes;
} else if (builtin.os.tag == .windows and stderr_file.isTty()) {
return .windows_api;
} else {
return .no_color;
}
}
}
/// 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 {
2020-05-05 04:14:22 -07:00
nosuspend {
const stderr = io.getStdErr().writer();
if (builtin.strip_debug_info) {
stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
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, detectTTYConfig(), start_addr) catch |err| {
stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
return;
};
2019-05-26 23:16:05 -07:00
}
}
/// Tries to print the stack trace starting from the supplied base pointer to stderr,
/// unbuffered, and ignores any error returned.
/// TODO multithreaded awareness
pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
2020-05-05 04:14:22 -07:00
nosuspend {
const stderr = io.getStdErr().writer();
if (builtin.strip_debug_info) {
stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
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;
};
const tty_config = detectTTYConfig();
printSourceAtAddress(debug_info, stderr, ip, tty_config) catch return;
var it = StackIterator.init(null, bp);
while (it.next()) |return_address| {
printSourceAtAddress(debug_info, stderr, return_address - 1, tty_config) catch 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 {
if (builtin.os.tag == .windows) {
2019-05-26 10:17:34 -07:00
const addrs = stack_trace.instruction_addresses;
const u32_addrs_len = @intCast(u32, addrs.len);
const first_addr = first_address orelse {
stack_trace.index = windows.ntdll.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.ntdll.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, null);
2019-05-26 10:17:34 -07:00
for (stack_trace.instruction_addresses) |*addr, i| {
addr.* = it.next() orelse {
stack_trace.index = i;
return;
};
2019-05-26 10:17:34 -07:00
}
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 {
2020-05-05 04:14:22 -07:00
nosuspend {
const stderr = io.getStdErr().writer();
if (builtin.strip_debug_info) {
stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
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, detectTTYConfig()) catch |err| {
stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
return;
};
2019-05-26 23:16:05 -07:00
}
}
/// 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
2019-07-15 16:48:47 -07:00
/// in ReleaseFast and ReleaseSmall 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
}
2020-07-11 04:09:04 -07:00
pub fn panic(comptime format: []const u8, args: anytype) noreturn {
2018-02-28 18:19:51 -08:00
@setCold(true);
// TODO: remove conditional once wasi / LLVM defines __builtin_return_address
const first_trace_addr = if (builtin.os.tag == .wasi) null else @returnAddress();
panicExtra(null, first_trace_addr, format, args);
}
/// Non-zero whenever the program triggered a panic.
/// The counter is incremented/decremented atomically.
var panicking: u8 = 0;
// Locked to avoid interleaving panic messages from multiple threads.
var panic_mutex = std.Mutex{};
/// Counts how many times the panic handler is invoked by this thread.
/// This is used to catch and handle panics triggered by the panic handler.
threadlocal var panic_stage: usize = 0;
2020-07-11 04:09:04 -07:00
pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, comptime format: []const u8, args: anytype) noreturn {
@setCold(true);
if (enable_segfault_handler) {
// If a segfault happens while panicking, we want it to actually segfault, not trigger
// the handler.
resetSegfaultHandler();
}
2020-05-05 04:14:22 -07:00
nosuspend switch (panic_stage) {
0 => {
panic_stage = 1;
_ = @atomicRmw(u8, &panicking, .Add, 1, .SeqCst);
// Make sure to release the mutex when done
{
const held = panic_mutex.acquire();
defer held.release();
const stderr = io.getStdErr().writer();
stderr.print(format ++ "\n", args) catch os.abort();
if (trace) |t| {
dumpStackTrace(t.*);
}
dumpCurrentStackTrace(first_trace_addr);
}
if (@atomicRmw(u8, &panicking, .Sub, 1, .SeqCst) != 1) {
// Another thread is panicking, wait for the last one to finish
// and call abort()
2020-03-13 11:20:18 -07:00
// Sleep forever without hammering the CPU
var event = std.ResetEvent.init();
event.wait();
unreachable;
}
},
1 => {
panic_stage = 2;
// A panic happened while trying to print a previous panic message,
// we're still holding the mutex but that's fine as we're going to
// call abort()
const stderr = io.getStdErr().writer();
stderr.print("Panicked during a panic. Aborting.\n", .{}) catch os.abort();
},
else => {
// Panicked while printing "Panicked during a panic."
},
};
os.abort();
}
const RED = "\x1b[31;1m";
2017-04-24 09:01:19 -07:00
const GREEN = "\x1b[32;1m";
const CYAN = "\x1b[36;1m";
2017-04-24 09:01:19 -07:00
const WHITE = "\x1b[37;1m";
const DIM = "\x1b[2m";
const RESET = "\x1b[0m";
pub fn writeStackTrace(
stack_trace: builtin.StackTrace,
2020-07-11 04:09:04 -07:00
out_stream: anytype,
allocator: *mem.Allocator,
debug_info: *DebugInfo,
tty_config: TTY.Config,
) !void {
if (builtin.strip_debug_info) return error.MissingDebugInfo;
var frame_index: usize = 0;
var frames_left: usize = std.math.min(stack_trace.index, stack_trace.instruction_addresses.len);
2018-01-14 07:19:21 -08:00
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 - 1, tty_config);
2018-01-14 07:19:21 -08:00
}
}
2018-12-02 15:54:04 -08:00
pub const StackIterator = struct {
// Skip every frame before this address is found.
first_address: ?usize,
// Last known value of the frame pointer register.
2018-12-02 15:54:04 -08:00
fp: usize,
pub fn init(first_address: ?usize, fp: ?usize) StackIterator {
2018-12-02 15:54:04 -08:00
return StackIterator{
.first_address = first_address,
.fp = fp orelse @frameAddress(),
2018-12-02 15:54:04 -08:00
};
2018-07-29 20:25:40 -07:00
}
2018-12-02 15:54:04 -08:00
// Negative offset of the saved BP wrt the frame pointer.
const fp_offset = if (builtin.arch.isRISCV())
// On RISC-V the frame pointer points to the top of the saved register
// area, on pretty much every other architecture it points to the stack
// slot where the previous frame pointer is saved.
2 * @sizeOf(usize)
else
0;
// Positive offset of the saved PC wrt the frame pointer.
const pc_offset = if (builtin.arch == .powerpc64le)
2 * @sizeOf(usize)
else
@sizeOf(usize);
2020-05-13 08:21:15 -07:00
pub fn next(self: *StackIterator) ?usize {
var address = self.next_internal() orelse return null;
if (self.first_address) |first_address| {
while (address != first_address) {
address = self.next_internal() orelse return null;
2018-12-02 15:54:04 -08:00
}
self.first_address = null;
2018-12-02 15:54:04 -08:00
}
return address;
}
fn next_internal(self: *StackIterator) ?usize {
const fp = math.sub(usize, self.fp, fp_offset) catch return null;
// Sanity check.
if (fp == 0 or !mem.isAligned(fp, @alignOf(usize)))
return null;
const new_fp = @intToPtr(*const usize, fp).*;
// Sanity check: the stack grows down thus all the parent frames must be
// be at addresses that are greater (or equal) than the previous one.
// A zero frame pointer often signals this is the last frame, that case
// is gracefully handled by the next call to next_internal.
if (new_fp != 0 and new_fp < self.fp)
return null;
const new_pc = @intToPtr(
*const usize,
math.add(usize, fp, pc_offset) catch return null,
).*;
self.fp = new_fp;
return new_pc;
2018-12-02 15:54:04 -08:00
}
};
2018-07-29 20:25:40 -07:00
pub fn writeCurrentStackTrace(
2020-07-11 04:09:04 -07:00
out_stream: anytype,
debug_info: *DebugInfo,
tty_config: TTY.Config,
start_addr: ?usize,
) !void {
if (builtin.os.tag == .windows) {
return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr);
}
var it = StackIterator.init(start_addr, null);
2018-12-02 15:54:04 -08:00
while (it.next()) |return_address| {
try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config);
2018-01-14 07:19:21 -08:00
}
}
2018-09-12 11:26:21 -07:00
pub fn writeCurrentStackTraceWindows(
2020-07-11 04:09:04 -07:00
out_stream: anytype,
2018-09-12 11:26:21 -07:00
debug_info: *DebugInfo,
tty_config: TTY.Config,
2018-09-12 11:26:21 -07:00
start_addr: ?usize,
) !void {
var addr_buf: [1024]usize = undefined;
2019-05-26 10:17:34 -07:00
const n = windows.ntdll.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 - 1, tty_config);
}
}
pub const TTY = struct {
pub const Color = enum {
Red,
Green,
Cyan,
White,
Dim,
Bold,
Reset,
};
2020-01-21 11:51:57 -08:00
pub const Config = enum {
no_color,
escape_codes,
// TODO give this a payload of file handle
windows_api,
2020-07-11 04:09:04 -07:00
fn setColor(conf: Config, out_stream: anytype, color: Color) void {
2020-05-05 04:14:22 -07:00
nosuspend switch (conf) {
.no_color => return,
.escape_codes => switch (color) {
.Red => out_stream.writeAll(RED) catch return,
.Green => out_stream.writeAll(GREEN) catch return,
.Cyan => out_stream.writeAll(CYAN) catch return,
.White, .Bold => out_stream.writeAll(WHITE) catch return,
.Dim => out_stream.writeAll(DIM) catch return,
.Reset => out_stream.writeAll(RESET) catch return,
},
.windows_api => if (builtin.os.tag == .windows) {
const stderr_file = io.getStdErr();
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.kernel32.GetConsoleScreenBufferInfo(stderr_file.handle, &info);
S.attrs = info.wAttributes;
}
// TODO handle errors
switch (color) {
.Red => {
_ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY) catch {};
},
.Green => {
_ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY) catch {};
},
.Cyan => {
_ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY) catch {};
},
.White, .Bold => {
_ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY) catch {};
},
.Dim => {
_ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_INTENSITY) catch {};
},
.Reset => {
_ = windows.SetConsoleTextAttribute(stderr_file.handle, S.attrs) catch {};
},
}
} else {
unreachable;
},
};
}
};
};
2020-02-01 03:15:49 -08:00
/// TODO resources https://github.com/ziglang/zig/issues/4353
2020-02-22 02:51:45 -08:00
fn populateModule(di: *ModuleDebugInfo, 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;
2020-03-10 17:22:30 -07:00
const signature = try modi.inStream().readIntLittle(u32);
if (signature != 4)
return error.InvalidDebugInfo;
mod.symbols = try allocator.alloc(u8, mod.mod_info.SymByteSize - 4);
2020-03-10 17:22:30 -07:00
try modi.inStream().readNoEof(mod.symbols);
mod.subsect_info = try allocator.alloc(u8, mod.mod_info.C13ByteSize);
2020-03-10 17:22:30 -07:00
try modi.inStream().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) {
2020-02-22 09:02:55 -08:00
.FileChecksums => {
2018-09-02 14:58:50 -07:00
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;
}
2020-02-22 03:44:21 -08:00
/// TODO resources https://github.com/ziglang/zig/issues/4353
2020-07-11 04:09:04 -07:00
pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: TTY.Config) !void {
2020-02-22 02:51:45 -08:00
const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {
2020-02-21 03:11:04 -08:00
error.MissingDebugInfo, error.InvalidDebugInfo => {
2020-02-22 03:44:21 -08:00
return printLineInfo(
out_stream,
null,
address,
"???",
"???",
tty_config,
printLineFromFileAnyOs,
);
2020-02-21 03:11:04 -08:00
},
2020-02-20 11:48:54 -08:00
else => return err,
2020-02-21 03:11:04 -08:00
};
2020-02-22 03:44:21 -08:00
const symbol_info = try module.getSymbolAtAddress(address);
defer symbol_info.deinit();
2020-02-20 11:48:54 -08:00
2020-02-09 02:55:43 -08:00
return printLineInfo(
out_stream,
2020-02-22 03:44:21 -08:00
symbol_info.line_info,
address,
2020-02-22 03:44:21 -08:00
symbol_info.symbol_name,
symbol_info.compile_unit_name,
tty_config,
printLineFromFileAnyOs,
);
}
fn printLineInfo(
2020-07-11 04:09:04 -07:00
out_stream: anytype,
2020-01-21 11:51:57 -08:00
line_info: ?LineInfo,
address: usize,
symbol_name: []const u8,
compile_unit_name: []const u8,
tty_config: TTY.Config,
2020-07-11 04:09:04 -07:00
comptime printLineFromFile: anytype,
) !void {
2020-05-05 04:14:22 -07:00
nosuspend {
tty_config.setColor(out_stream, .White);
2020-01-21 11:51:57 -08:00
if (line_info) |*li| {
try out_stream.print("{}:{}:{}", .{ li.file_name, li.line, li.column });
} else {
try out_stream.writeAll("???:?:?");
}
tty_config.setColor(out_stream, .Reset);
try out_stream.writeAll(": ");
tty_config.setColor(out_stream, .Dim);
try out_stream.print("0x{x} in {} ({})", .{ address, symbol_name, compile_unit_name });
tty_config.setColor(out_stream, .Reset);
try out_stream.writeAll("\n");
// Show the matching source code line if possible
if (line_info) |li| {
if (printLineFromFile(out_stream, li)) {
if (li.column > 0) {
// The caret already takes one char
const space_needed = @intCast(usize, li.column - 1);
try out_stream.writeByteNTimes(' ', space_needed);
tty_config.setColor(out_stream, .Green);
try out_stream.writeAll("^");
tty_config.setColor(out_stream, .Reset);
}
try out_stream.writeAll("\n");
} else |err| switch (err) {
error.EndOfFile, error.FileNotFound => {},
error.BadPathName => {},
else => return err,
}
}
}
}
// TODO use this
pub const OpenSelfDebugInfoError = error{
MissingDebugInfo,
OutOfMemory,
UnsupportedOperatingSystem,
};
2020-02-01 03:15:49 -08:00
/// TODO resources https://github.com/ziglang/zig/issues/4353
2020-02-18 04:06:17 -08:00
pub fn openSelfDebugInfo(allocator: *mem.Allocator) anyerror!DebugInfo {
2020-05-05 04:14:22 -07:00
nosuspend {
if (builtin.strip_debug_info)
return error.MissingDebugInfo;
if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) {
return root.os.debug.openSelfDebugInfo(allocator);
}
switch (builtin.os.tag) {
.linux,
.freebsd,
2020-03-08 08:04:02 -07:00
.netbsd,
.dragonfly,
2020-10-11 01:23:36 -07:00
.openbsd,
2020-10-12 01:59:43 -07:00
.macos,
.windows,
=> return DebugInfo.init(allocator),
else => return error.UnsupportedDebugInfo,
}
2020-02-09 02:55:43 -08:00
}
2020-02-10 04:18:28 -08:00
}
/// This takes ownership of coff_file: users of this function should not close
/// it themselves, even on error.
2020-02-20 11:18:51 -08:00
/// TODO resources https://github.com/ziglang/zig/issues/4353
2020-05-29 13:39:47 -07:00
/// TODO it's weird to take ownership even on error, rework this code.
fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInfo {
2020-05-05 04:14:22 -07:00
nosuspend {
errdefer coff_file.close();
2020-02-20 11:18:51 -08:00
const coff_obj = try allocator.create(coff.Coff);
coff_obj.* = coff.Coff.init(allocator, coff_file);
var di = ModuleDebugInfo{
.base_address = undefined,
.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 fs.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.inStream().readIntLittle(u32);
const signature = try pdb_stream.inStream().readIntLittle(u32);
const age = try pdb_stream.inStream().readIntLittle(u32);
var guid: [16]u8 = undefined;
try pdb_stream.inStream().readNoEof(&guid);
if (version != 20000404) // VC70, only value observed by LLVM team
return error.UnknownPDBVersion;
if (!mem.eql(u8, &di.coff.guid, &guid) or di.coff.age != age)
return error.PDBMismatch;
// We validated the executable and pdb match.
const string_table_index = str_tab_index: {
const name_bytes_len = try pdb_stream.inStream().readIntLittle(u32);
const name_bytes = try allocator.alloc(u8, name_bytes_len);
try pdb_stream.inStream().readNoEof(name_bytes);
2018-08-31 12:02:41 -07:00
const HashTableHeader = packed struct {
Size: u32,
Capacity: u32,
2018-08-31 16:50:03 -07:00
fn maxLoad(cap: u32) u32 {
return cap * 2 / 3 + 1;
}
};
const hash_tbl_hdr = try pdb_stream.inStream().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.inStream(), allocator);
if (present.len != hash_tbl_hdr.Size)
return error.InvalidDebugInfo;
const deleted = try readSparseBitVector(&pdb_stream.inStream(), 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.inStream().readIntLittle(u32);
const name_index = try pdb_stream.inStream().readIntLittle(u32);
2020-11-25 12:23:43 -08:00
const name = mem.spanZ(std.meta.assumeSentinel(name_bytes.ptr + name_offset, 0));
if (mem.eql(u8, name, "/names")) {
break :str_tab_index name_index;
}
}
return error.MissingDebugInfo;
};
2018-08-31 16:50:03 -07:00
di.pdb.string_table = di.pdb.getStreamById(string_table_index) orelse return error.MissingDebugInfo;
di.pdb.dbi = di.pdb.getStream(pdb.StreamType.Dbi) orelse return error.MissingDebugInfo;
2018-08-31 16:50:03 -07:00
const dbi = di.pdb.dbi;
2018-08-31 16:50:03 -07:00
// Dbi Header
const dbi_stream_header = try dbi.inStream().readStruct(pdb.DbiStreamHeader);
if (dbi_stream_header.VersionHeader != 19990903) // V70, only value observed by LLVM team
return error.UnknownPDBVersion;
if (dbi_stream_header.Age != age)
return error.UnmatchingPDB;
const mod_info_size = dbi_stream_header.ModInfoSize;
const section_contrib_size = dbi_stream_header.SectionContributionSize;
2018-08-31 16:50:03 -07:00
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.inStream().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.seekBy(@intCast(isize, march_forward_bytes));
this_record_len += march_forward_bytes;
}
2018-08-31 16:50:03 -07:00
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.inStream().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.inStream().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();
2018-08-31 12:02:41 -07:00
return di;
}
}
2020-07-11 04:09:04 -07:00
fn readSparseBitVector(stream: anytype, allocator: *mem.Allocator) ![]usize {
breaking API changes to all readInt/writeInt functions & more * add `@bswap` builtin function. See #767 * comptime evaluation facilities are improved to be able to handle a `@ptrCast` with a backing array. * `@truncate` allows "truncating" a u0 value to any integer type, and the result is always comptime known to be `0`. * when specifying pointer alignment in a type expression, the alignment value of pointers which do not have addresses at runtime is ignored, and always has the default/ABI alignment * threw in a fix to freebsd/x86_64.zig to update syntax from language changes * some improvements are pending #863 closes #638 closes #1733 std lib API changes * io.InStream().readIntNe renamed to readIntNative * io.InStream().readIntLe renamed to readIntLittle * io.InStream().readIntBe renamed to readIntBig * introduced io.InStream().readIntForeign * io.InStream().readInt has parameter order changed * io.InStream().readVarInt has parameter order changed * io.InStream().writeIntNe renamed to writeIntNative * introduced io.InStream().writeIntForeign * io.InStream().writeIntLe renamed to writeIntLittle * io.InStream().writeIntBe renamed to writeIntBig * io.InStream().writeInt has parameter order changed * mem.readInt has different parameters and semantics * introduced mem.readIntNative * introduced mem.readIntForeign * mem.readIntBE renamed to mem.readIntBig and different API * mem.readIntLE renamed to mem.readIntLittle and different API * introduced mem.readIntSliceNative * introduced mem.readIntSliceForeign * introduced mem.readIntSliceLittle * introduced mem.readIntSliceBig * introduced mem.readIntSlice * mem.writeInt has different parameters and semantics * introduced mem.writeIntNative * introduced mem.writeIntForeign * mem.writeIntBE renamed to mem.readIntBig and different semantics * mem.writeIntLE renamed to mem.readIntLittle and different semantics * introduced mem.writeIntSliceForeign * introduced mem.writeIntSliceNative * introduced mem.writeIntSliceBig * introduced mem.writeIntSliceLittle * introduced mem.writeIntSlice * removed mem.endianSwapIfLe * removed mem.endianSwapIfBe * removed mem.endianSwapIf * added mem.littleToNative * added mem.bigToNative * added mem.toNative * added mem.nativeTo * added mem.nativeToLittle * added mem.nativeToBig
2018-12-12 17:19:46 -08:00
const num_words = try stream.readIntLittle(u32);
2018-08-31 16:50:03 -07:00
var word_i: usize = 0;
var list = ArrayList(usize).init(allocator);
while (word_i != num_words) : (word_i += 1) {
breaking API changes to all readInt/writeInt functions & more * add `@bswap` builtin function. See #767 * comptime evaluation facilities are improved to be able to handle a `@ptrCast` with a backing array. * `@truncate` allows "truncating" a u0 value to any integer type, and the result is always comptime known to be `0`. * when specifying pointer alignment in a type expression, the alignment value of pointers which do not have addresses at runtime is ignored, and always has the default/ABI alignment * threw in a fix to freebsd/x86_64.zig to update syntax from language changes * some improvements are pending #863 closes #638 closes #1733 std lib API changes * io.InStream().readIntNe renamed to readIntNative * io.InStream().readIntLe renamed to readIntLittle * io.InStream().readIntBe renamed to readIntBig * introduced io.InStream().readIntForeign * io.InStream().readInt has parameter order changed * io.InStream().readVarInt has parameter order changed * io.InStream().writeIntNe renamed to writeIntNative * introduced io.InStream().writeIntForeign * io.InStream().writeIntLe renamed to writeIntLittle * io.InStream().writeIntBe renamed to writeIntBig * io.InStream().writeInt has parameter order changed * mem.readInt has different parameters and semantics * introduced mem.readIntNative * introduced mem.readIntForeign * mem.readIntBE renamed to mem.readIntBig and different API * mem.readIntLE renamed to mem.readIntLittle and different API * introduced mem.readIntSliceNative * introduced mem.readIntSliceForeign * introduced mem.readIntSliceLittle * introduced mem.readIntSliceBig * introduced mem.readIntSlice * mem.writeInt has different parameters and semantics * introduced mem.writeIntNative * introduced mem.writeIntForeign * mem.writeIntBE renamed to mem.readIntBig and different semantics * mem.writeIntLE renamed to mem.readIntLittle and different semantics * introduced mem.writeIntSliceForeign * introduced mem.writeIntSliceNative * introduced mem.writeIntSliceBig * introduced mem.writeIntSliceLittle * introduced mem.writeIntSlice * removed mem.endianSwapIfLe * removed mem.endianSwapIfBe * removed mem.endianSwapIf * added mem.littleToNative * added mem.bigToNative * added mem.toNative * added mem.nativeTo * added mem.nativeToLittle * added mem.nativeToBig
2018-12-12 17:19:46 -08:00
const word = try stream.readIntLittle(u32);
2018-08-31 16:50:03 -07:00
var bit_i: u5 = 0;
while (true) : (bit_i += 1) {
2019-11-06 20:25:57 -08:00
if (word & (@as(u32, 1) << bit_i) != 0) {
2018-08-31 16:50:03 -07:00
try list.append(word_i * 32 + bit_i);
}
if (bit_i == maxInt(u5)) break;
2018-08-31 16:50:03 -07:00
}
}
return list.toOwnedSlice();
}
2020-02-18 04:06:17 -08:00
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];
}
/// This takes ownership of elf_file: users of this function should not close
/// it themselves, even on error.
2020-02-01 03:15:49 -08:00
/// TODO resources https://github.com/ziglang/zig/issues/4353
2020-05-29 13:39:47 -07:00
/// TODO it's weird to take ownership even on error, rework this code.
pub fn readElfDebugInfo(allocator: *mem.Allocator, elf_file: File) !ModuleDebugInfo {
2020-05-05 04:14:22 -07:00
nosuspend {
const mapped_mem = try mapWholeFile(elf_file);
const hdr = @ptrCast(*const elf.Ehdr, &mapped_mem[0]);
if (!mem.eql(u8, hdr.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic;
if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
const endian: builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {
elf.ELFDATA2LSB => .Little,
elf.ELFDATA2MSB => .Big,
else => return error.InvalidElfEndian,
};
assert(endian == std.builtin.endian); // this is our own debug info
const shoff = hdr.e_shoff;
const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
const str_shdr = @ptrCast(
*const elf.Shdr,
2020-03-10 17:22:30 -07:00
@alignCast(@alignOf(elf.Shdr), &mapped_mem[try math.cast(usize, str_section_off)]),
);
const header_strings = mapped_mem[str_shdr.sh_offset .. str_shdr.sh_offset + str_shdr.sh_size];
const shdrs = @ptrCast(
[*]const elf.Shdr,
@alignCast(@alignOf(elf.Shdr), &mapped_mem[shoff]),
)[0..hdr.e_shnum];
var opt_debug_info: ?[]const u8 = null;
var opt_debug_abbrev: ?[]const u8 = null;
var opt_debug_str: ?[]const u8 = null;
var opt_debug_line: ?[]const u8 = null;
var opt_debug_ranges: ?[]const u8 = null;
for (shdrs) |*shdr| {
if (shdr.sh_type == elf.SHT_NULL) continue;
2020-11-25 12:23:43 -08:00
const name = std.mem.span(std.meta.assumeSentinel(header_strings[shdr.sh_name..].ptr, 0));
if (mem.eql(u8, name, ".debug_info")) {
opt_debug_info = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
} else if (mem.eql(u8, name, ".debug_abbrev")) {
opt_debug_abbrev = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
} else if (mem.eql(u8, name, ".debug_str")) {
opt_debug_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
} else if (mem.eql(u8, name, ".debug_line")) {
opt_debug_line = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
} else if (mem.eql(u8, name, ".debug_ranges")) {
opt_debug_ranges = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
}
}
var di = DW.DwarfInfo{
.endian = endian,
.debug_info = opt_debug_info orelse return error.MissingDebugInfo,
.debug_abbrev = opt_debug_abbrev orelse return error.MissingDebugInfo,
.debug_str = opt_debug_str orelse return error.MissingDebugInfo,
.debug_line = opt_debug_line orelse return error.MissingDebugInfo,
.debug_ranges = opt_debug_ranges,
};
try DW.openDwarfDebugInfo(&di, allocator);
2020-02-20 11:18:51 -08:00
return ModuleDebugInfo{
.base_address = undefined,
.dwarf = di,
.mapped_memory = mapped_mem,
};
}
2018-08-25 00:07:37 -07:00
}
2020-02-01 03:15:49 -08:00
/// TODO resources https://github.com/ziglang/zig/issues/4353
/// This takes ownership of coff_file: users of this function should not close
/// it themselves, even on error.
2020-05-29 13:39:47 -07:00
/// TODO it's weird to take ownership even on error, rework this code.
fn readMachODebugInfo(allocator: *mem.Allocator, macho_file: File) !ModuleDebugInfo {
const mapped_mem = try mapWholeFile(macho_file);
2020-02-20 11:18:51 -08:00
2020-02-18 03:37:25 -08:00
const hdr = @ptrCast(
*const macho.mach_header_64,
2020-02-20 11:18:51 -08:00
@alignCast(@alignOf(macho.mach_header_64), mapped_mem.ptr),
2020-02-18 03:37:25 -08:00
);
2020-02-22 02:51:45 -08:00
if (hdr.magic != macho.MH_MAGIC_64)
return error.InvalidDebugInfo;
2020-02-18 03:37:25 -08:00
const hdr_base = @ptrCast([*]const u8, hdr);
2018-08-25 00:07:37 -07:00
var ptr = hdr_base + @sizeOf(macho.mach_header_64);
var ncmd: u32 = hdr.ncmds;
const symtab = while (ncmd != 0) : (ncmd -= 1) {
2020-02-18 03:37:25 -08:00
const lc = @ptrCast(*const std.macho.load_command, ptr);
switch (lc.cmd) {
2020-02-18 03:37:25 -08:00
std.macho.LC_SYMTAB => break @ptrCast(*const std.macho.symtab_command, ptr),
else => {},
}
2019-12-20 04:58:19 -08:00
ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize);
} else {
return error.MissingDebugInfo;
};
2020-02-18 03:37:25 -08:00
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 - 1 :0];
const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
2020-02-18 03:37:25 -08:00
var ofile: ?*const macho.nlist_64 = null;
var reloc: u64 = 0;
var symbol_index: usize = 0;
var last_len: u64 = 0;
for (syms) |*sym| {
2018-08-25 00:07:37 -07:00
if (sym.n_type & std.macho.N_STAB != 0) {
switch (sym.n_type) {
2018-08-25 00:07:37 -07:00
std.macho.N_OSO => {
ofile = sym;
reloc = 0;
},
2018-08-25 00:07:37 -07:00
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;
}
},
2018-08-25 00:07:37 -07:00
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.
2020-06-08 13:33:35 -07:00
std.sort.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan);
2020-02-22 02:51:45 -08:00
return ModuleDebugInfo{
2020-02-18 03:37:25 -08:00
.base_address = undefined,
2020-02-20 11:18:51 -08:00
.mapped_memory = mapped_mem,
2020-02-22 02:51:45 -08:00
.ofiles = ModuleDebugInfo.OFileTable.init(allocator),
.symbols = symbols,
.strings = strings,
};
}
2020-07-11 04:09:04 -07:00
fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void {
// Need this to always block even in async I/O mode, because this could potentially
// be called from e.g. the event loop code crashing.
var f = try fs.cwd().openFile(line_info.file_name, .{ .intended_io_mode = .blocking });
2017-04-24 09:01:19 -07:00
defer f.close();
// TODO fstat and make sure that the file has the correct size
var buf: [mem.page_size]u8 = undefined;
2017-04-24 09:01:19 -07:00
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];
2017-04-24 09:01:19 -07:00
for (slice) |byte| {
if (line == line_info.line) {
try out_stream.writeByte(byte);
2017-04-24 09:01:19 -07:00
if (byte == '\n') {
return;
}
}
if (byte == '\n') {
line += 1;
column = 1;
} else {
column += 1;
}
}
if (amt_read < buf.len) return error.EndOfFile;
2017-04-24 09:01:19 -07:00
}
}
const MachoSymbol = struct {
2020-02-18 03:37:25 -08:00
nlist: *const macho.nlist_64,
ofile: ?*const macho.nlist_64,
reloc: u64,
/// Returns the address from the macho file
fn address(self: MachoSymbol) u64 {
return self.nlist.n_value;
}
2020-06-08 13:33:35 -07:00
fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
return lhs.address() < rhs.address();
}
};
2020-05-29 13:39:47 -07:00
/// `file` is expected to have been opened with .intended_io_mode == .blocking.
/// Takes ownership of file, even on error.
/// TODO it's weird to take ownership even on error, rework this code.
fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {
2020-05-05 04:14:22 -07:00
nosuspend {
defer file.close();
2020-02-20 11:18:51 -08:00
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);
return mapped_mem;
}
2020-02-20 11:18:51 -08:00
}
2020-02-09 02:55:43 -08:00
pub const DebugInfo = struct {
allocator: *mem.Allocator,
2020-02-22 02:51:45 -08:00
address_map: std.AutoHashMap(usize, *ModuleDebugInfo),
2020-02-09 02:55:43 -08:00
pub fn init(allocator: *mem.Allocator) DebugInfo {
return DebugInfo{
.allocator = allocator,
2020-02-22 02:51:45 -08:00
.address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator),
2020-02-09 02:55:43 -08:00
};
}
pub fn deinit(self: *DebugInfo) void {
2020-02-18 04:06:17 -08:00
// TODO: resources https://github.com/ziglang/zig/issues/4353
2020-02-09 02:55:43 -08:00
self.address_map.deinit();
}
2020-02-22 02:51:45 -08:00
pub fn getModuleForAddress(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
if (comptime std.Target.current.isDarwin())
return self.lookupModuleDyld(address)
else if (builtin.os.tag == .windows)
2020-02-22 02:51:45 -08:00
return self.lookupModuleWin32(address)
2020-02-10 04:18:28 -08:00
else
2020-02-22 02:51:45 -08:00
return self.lookupModuleDl(address);
2020-02-09 02:55:43 -08:00
}
2020-02-22 02:51:45 -08:00
fn lookupModuleDyld(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
2020-02-11 00:40:21 -08:00
const image_count = std.c._dyld_image_count();
var i: u32 = 0;
while (i < image_count) : (i += 1) {
2020-02-18 03:37:25 -08:00
const base_address = std.c._dyld_get_image_vmaddr_slide(i);
if (address < base_address) continue;
2020-02-11 00:40:21 -08:00
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));
2020-02-18 03:37:25 -08:00
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;
2020-02-11 00:40:21 -08:00
if (lc.cmd != macho.LC_SEGMENT_64) continue;
2020-02-18 03:37:25 -08:00
const segment_cmd = @ptrCast(
*const std.macho.segment_command_64,
@alignCast(@alignOf(std.macho.segment_command_64), lc),
);
const rebased_address = address - base_address;
const seg_start = segment_cmd.vmaddr;
const seg_end = seg_start + segment_cmd.vmsize;
if (rebased_address >= seg_start and rebased_address < seg_end) {
2020-07-04 15:25:49 -07:00
if (self.address_map.get(base_address)) |obj_di| {
2020-02-18 03:37:25 -08:00
return obj_di;
}
2020-02-22 02:51:45 -08:00
const obj_di = try self.allocator.create(ModuleDebugInfo);
2020-02-18 03:37:25 -08:00
errdefer self.allocator.destroy(obj_di);
const macho_path = mem.spanZ(std.c._dyld_get_image_name(i));
2020-05-29 13:39:47 -07:00
const macho_file = fs.cwd().openFile(macho_path, .{ .intended_io_mode = .blocking }) catch |err| switch (err) {
2020-02-22 03:44:21 -08:00
error.FileNotFound => return error.MissingDebugInfo,
else => return err,
};
obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
2020-02-18 03:37:25 -08:00
obj_di.base_address = base_address;
2020-02-22 03:44:21 -08:00
try self.address_map.putNoClobber(base_address, obj_di);
2020-02-18 03:37:25 -08:00
return obj_di;
}
2020-02-11 00:40:21 -08:00
}
}
2020-02-18 03:37:25 -08:00
2020-02-20 11:48:54 -08:00
return error.MissingDebugInfo;
2020-02-11 00:40:21 -08:00
}
2020-02-22 02:51:45 -08:00
fn lookupModuleWin32(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
2020-02-10 04:18:28 -08:00
const process_handle = windows.kernel32.GetCurrentProcess();
// Find how many modules are actually loaded
var dummy: windows.HMODULE = undefined;
var bytes_needed: windows.DWORD = undefined;
2020-02-10 04:18:28 -08:00
if (windows.kernel32.K32EnumProcessModules(
process_handle,
@ptrCast([*]windows.HMODULE, &dummy),
0,
&bytes_needed,
2020-02-10 04:18:28 -08:00
) == 0)
2020-02-20 11:48:54 -08:00
return error.MissingDebugInfo;
2020-02-10 04:18:28 -08:00
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)
2020-02-20 11:48:54 -08:00
return error.MissingDebugInfo;
// 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| {
2020-02-10 04:18:28 -08:00
var info: windows.MODULEINFO = undefined;
if (windows.kernel32.K32GetModuleInformation(
process_handle,
module,
&info,
@sizeOf(@TypeOf(info)),
) == 0)
2020-02-20 11:48:54 -08:00
return error.MissingDebugInfo;
2020-02-10 04:18:28 -08:00
const seg_start = @ptrToInt(info.lpBaseOfDll);
const seg_end = seg_start + info.SizeOfImage;
if (address >= seg_start and address < seg_end) {
2020-07-04 15:25:49 -07:00
if (self.address_map.get(seg_start)) |obj_di| {
2020-02-18 03:37:25 -08:00
return obj_di;
}
2020-02-10 11:47:07 -08:00
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(
2020-02-10 04:18:28 -08:00
process_handle,
module,
2020-02-10 11:47:07 -08:00
@ptrCast(windows.LPWSTR, &name_buffer[4]),
windows.PATH_MAX_WIDE,
2020-02-10 04:18:28 -08:00
);
assert(len > 0);
2020-02-22 02:51:45 -08:00
const obj_di = try self.allocator.create(ModuleDebugInfo);
2020-02-10 04:18:28 -08:00
errdefer self.allocator.destroy(obj_di);
const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
2020-02-22 03:44:21 -08:00
error.FileNotFound => return error.MissingDebugInfo,
else => return err,
};
obj_di.* = try readCoffDebugInfo(self.allocator, coff_file);
2020-02-10 04:18:28 -08:00
obj_di.base_address = seg_start;
2020-02-22 03:44:21 -08:00
try self.address_map.putNoClobber(seg_start, obj_di);
2020-02-10 04:18:28 -08:00
return obj_di;
}
}
2020-02-20 11:48:54 -08:00
return error.MissingDebugInfo;
2020-02-10 04:18:28 -08:00
}
2020-02-22 02:51:45 -08:00
fn lookupModuleDl(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
2020-02-18 03:37:25 -08:00
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;
2020-02-09 02:55:43 -08:00
2020-02-18 03:37:25 -08:00
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 = mem.spanZ(info.dlpi_name) orelse "";
2020-02-18 03:37:25 -08:00
context.base_address = info.dlpi_addr;
// Stop the iteration
return error.Found;
}
}
}
}.callback)) {
2020-02-20 11:48:54 -08:00
return error.MissingDebugInfo;
2020-02-20 11:18:51 -08:00
} else |err| switch (err) {
error.Found => {},
2020-02-20 11:48:54 -08:00
else => return error.MissingDebugInfo,
2020-02-18 03:37:25 -08:00
}
2020-02-09 02:55:43 -08:00
reimplement std.HashMap * breaking changes to the API. Some of the weird decisions from before are changed to what would be more expected. - `get` returns `?V`, use `getEntry` for the old API. - `put` returns `!void`, use `fetchPut` for the old API. * HashMap now has a comptime parameter of whether to store hashes with entries. AutoHashMap has heuristics on whether to set this parameter. For example, for integers, it is false, since equality checking is cheap, but for strings, it is true, since equality checking is probably expensive. * The implementation has a separate array for entry_index / distance_from_start_index. Entries no longer has holes; it is an ArrayList, and iteration is simpler and more cache coherent. This is inspired by Python's new dictionaries. * HashMap is separated into an "unmanaged" and a "managed" API. The unmanaged API is where the actual implementation is; the managed API wraps it and provides a more convenient API, storing the allocator. * Memory usage: When there are less than or equal to 8 entries, HashMap now incurs only a single pointer-size integer as overhead, opposed to using an ArrayList. * Since the entries array is separate from the indexes array, the holes in the indexes array take up less room than the holes in the entries array otherwise would. However the entries array also allocates additional capacity for appending into the array. * HashMap now maintains insertion order. Deletion performs a "swap remove". It's now possible to modify the HashMap while iterating.
2020-07-03 16:57:24 -07:00
if (self.address_map.get(ctx.base_address)) |obj_di| {
2020-02-09 02:55:43 -08:00
return obj_di;
}
2020-02-22 02:51:45 -08:00
const obj_di = try self.allocator.create(ModuleDebugInfo);
2020-02-09 02:55:43 -08:00
errdefer self.allocator.destroy(obj_di);
2020-06-04 03:11:23 -07:00
// TODO https://github.com/ziglang/zig/issues/5525
const copy = if (ctx.name.len > 0)
2020-05-29 13:39:47 -07:00
fs.cwd().openFile(ctx.name, .{ .intended_io_mode = .blocking })
else
2020-06-04 03:11:23 -07:00
fs.openSelfExe(.{ .intended_io_mode = .blocking });
2020-06-08 13:33:35 -07:00
2020-06-04 03:11:23 -07:00
const elf_file = copy catch |err| switch (err) {
2020-02-22 03:44:21 -08:00
error.FileNotFound => return error.MissingDebugInfo,
else => return err,
};
obj_di.* = try readElfDebugInfo(self.allocator, elf_file);
2020-02-20 11:18:51 -08:00
obj_di.base_address = ctx.base_address;
2020-02-09 02:55:43 -08:00
2020-02-22 03:44:21 -08:00
try self.address_map.putNoClobber(ctx.base_address, obj_di);
2020-02-09 02:55:43 -08:00
return obj_di;
}
};
2020-02-21 03:11:04 -08:00
const SymbolInfo = struct {
2020-02-22 02:51:45 -08:00
symbol_name: []const u8 = "???",
compile_unit_name: []const u8 = "???",
line_info: ?LineInfo = null,
fn deinit(self: @This()) void {
if (self.line_info) |li| {
li.deinit();
}
}
2020-02-21 03:11:04 -08:00
};
pub const ModuleDebugInfo = switch (builtin.os.tag) {
2020-10-12 01:59:43 -07:00
.macos, .ios, .watchos, .tvos => struct {
2020-02-11 00:40:21 -08:00
base_address: usize,
2020-02-20 11:18:51 -08:00
mapped_memory: []const u8,
symbols: []const MachoSymbol,
2020-02-22 02:51:45 -08:00
strings: [:0]const u8,
ofiles: OFileTable,
2020-02-22 02:51:45 -08:00
const OFileTable = std.StringHashMap(DW.DwarfInfo);
2020-02-18 03:37:25 -08:00
pub fn allocator(self: @This()) *mem.Allocator {
return self.ofiles.allocator;
}
2020-02-22 02:51:45 -08:00
fn loadOFile(self: *@This(), o_file_path: []const u8) !DW.DwarfInfo {
2020-05-29 13:39:47 -07:00
const o_file = try fs.cwd().openFile(o_file_path, .{ .intended_io_mode = .blocking });
const mapped_mem = try mapWholeFile(o_file);
2020-02-22 02:51:45 -08:00
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 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;
};
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 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;
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,
};
try DW.openDwarfDebugInfo(&di, self.allocator());
// Add the debug info to the cache
try self.ofiles.putNoClobber(o_file_path, di);
return di;
}
2020-12-03 13:40:49 -08:00
pub fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {
2020-05-05 04:14:22 -07:00
nosuspend {
// Translate the VA into an address into this object
const relocated_address = address - self.base_address;
assert(relocated_address >= 0x100000000);
2020-02-22 02:51:45 -08:00
// Find the .o file where this symbol is defined
const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse
return SymbolInfo{};
2020-02-22 02:51:45 -08:00
// Take the symbol name from the N_FUN STAB entry, we're going to
// use it if we fail to find the DWARF infos
const stab_symbol = mem.spanZ(self.strings[symbol.nlist.n_strx..]);
2020-02-22 02:51:45 -08:00
if (symbol.ofile == null)
return SymbolInfo{ .symbol_name = stab_symbol };
2020-02-22 02:51:45 -08:00
const o_file_path = mem.spanZ(self.strings[symbol.ofile.?.n_strx..]);
2020-02-22 02:51:45 -08:00
// Check if its debug infos are already in the cache
2020-07-04 15:25:49 -07:00
var o_file_di = self.ofiles.get(o_file_path) orelse
(self.loadOFile(o_file_path) catch |err| switch (err) {
error.FileNotFound,
error.MissingDebugInfo,
error.InvalidDebugInfo,
=> {
return SymbolInfo{ .symbol_name = stab_symbol };
2020-02-22 02:51:45 -08:00
},
else => return err,
});
// Translate again the address, this time into an address inside the
// .o file
const relocated_address_o = relocated_address - symbol.reloc;
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{ .symbol_name = stab_symbol };
2020-02-22 02:51:45 -08:00
},
else => return err,
}
2020-02-22 02:51:45 -08:00
unreachable;
}
2020-02-22 02:51:45 -08:00
}
},
2019-05-26 10:17:34 -07:00
.uefi, .windows => struct {
2020-02-10 04:18:28 -08:00
base_address: usize,
pdb: pdb.Pdb,
coff: *coff.Coff,
sect_contribs: []pdb.SectionContribEntry,
modules: []Module,
2020-02-22 03:05:50 -08:00
pub fn allocator(self: @This()) *mem.Allocator {
return self.coff.allocator;
}
2020-12-03 13:40:49 -08:00
pub fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {
2020-02-22 03:05:50 -08:00
// Translate the VA into an address into this object
const relocated_address = address - self.base_address;
var coff_section: *coff.Section = undefined;
const mod_index = for (self.sect_contribs) |sect_contrib| {
2020-04-01 15:00:42 -07:00
if (sect_contrib.Section > self.coff.sections.items.len) continue;
2020-02-22 03:05:50 -08:00
// Remember that SectionContribEntry.Section is 1-based.
coff_section = &self.coff.sections.items[sect_contrib.Section - 1];
2020-02-22 03:05:50 -08:00
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{};
};
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.spanZ(@ptrCast([*:0]u8, proc_sym) + @sizeOf(pdb.ProcSym));
2020-02-22 03:05:50 -08:00
}
},
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) {
2020-02-22 09:02:55 -08:00
.Lines => {
2020-02-22 03:05:50 -08:00
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,
};
}
}
// 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;
}
};
return SymbolInfo{
.symbol_name = symbol_name,
.compile_unit_name = obj_basename,
.line_info = opt_line_info,
};
}
},
2020-10-11 01:23:36 -07:00
.linux, .netbsd, .freebsd, .dragonfly, .openbsd => struct {
2020-02-09 02:55:43 -08:00
base_address: usize,
dwarf: DW.DwarfInfo,
2020-02-20 11:18:51 -08:00
mapped_memory: []const u8,
2020-02-21 03:11:04 -08:00
2020-12-03 13:40:49 -08:00
pub fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {
2020-02-21 03:11:04 -08:00
// Translate the VA into an address into this object
const relocated_address = address - self.base_address;
2020-05-05 04:14:22 -07:00
if (nosuspend self.dwarf.findCompileUnit(relocated_address)) |compile_unit| {
2020-02-21 03:11:04 -08:00
return SymbolInfo{
2020-05-05 04:14:22 -07:00
.symbol_name = nosuspend self.dwarf.getSymbolName(relocated_address) orelse "???",
2020-02-21 03:11:04 -08:00
.compile_unit_name = compile_unit.die.getAttrString(&self.dwarf, DW.AT_name) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => "???",
else => return err,
},
2020-05-05 04:14:22 -07:00
.line_info = nosuspend self.dwarf.getLineNumberInfo(compile_unit.*, relocated_address) catch |err| switch (err) {
2020-02-21 03:11:04 -08:00
error.MissingDebugInfo, error.InvalidDebugInfo => null,
else => return err,
},
};
} else |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => {
2020-02-22 02:51:45 -08:00
return SymbolInfo{};
2020-02-21 03:11:04 -08:00
},
else => return err,
}
unreachable;
}
2020-02-09 02:55:43 -08:00
},
else => DW.DwarfInfo,
2016-12-18 16:40:26 -08:00
};
/// TODO multithreaded awareness
var debug_info_allocator: ?*mem.Allocator = null;
var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;
fn getDebugInfoAllocator() *mem.Allocator {
if (debug_info_allocator) |a| return a;
debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
debug_info_allocator = &debug_info_arena_allocator.allocator;
return &debug_info_arena_allocator.allocator;
}
/// Whether or not the current target can print useful debug information when a segfault occurs.
pub const have_segfault_handling_support = switch (builtin.os.tag) {
.linux, .netbsd => true,
.windows => true,
else => false,
};
pub const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler"))
root.enable_segfault_handler
else
runtime_safety and have_segfault_handling_support;
pub fn maybeEnableSegfaultHandler() void {
if (enable_segfault_handler) {
std.debug.attachSegfaultHandler();
}
}
var windows_segfault_handle: ?windows.HANDLE = null;
/// Attaches a global SIGSEGV handler which calls @panic("segmentation fault");
pub fn attachSegfaultHandler() void {
if (!have_segfault_handling_support) {
@compileError("segfault handler not supported for this target");
}
if (builtin.os.tag == .windows) {
windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows);
return;
}
var act = os.Sigaction{
.sigaction = handleSegfaultLinux,
.mask = os.empty_sigset,
.flags = (os.SA_SIGINFO | os.SA_RESTART | os.SA_RESETHAND),
};
os.sigaction(os.SIGSEGV, &act, null);
os.sigaction(os.SIGILL, &act, null);
os.sigaction(os.SIGBUS, &act, null);
}
fn resetSegfaultHandler() void {
if (builtin.os.tag == .windows) {
if (windows_segfault_handle) |handle| {
assert(windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0);
windows_segfault_handle = null;
}
return;
}
var act = os.Sigaction{
.sigaction = os.SIG_DFL,
.mask = os.empty_sigset,
.flags = 0,
};
os.sigaction(os.SIGSEGV, &act, null);
os.sigaction(os.SIGILL, &act, null);
os.sigaction(os.SIGBUS, &act, null);
}
fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const c_void) callconv(.C) noreturn {
// Reset to the default handler so that if a segfault happens in this handler it will crash
// the process. Also when this handler returns, the original instruction will be repeated
// and the resulting segfault will crash the process rather than continually dump stack traces.
resetSegfaultHandler();
const addr = switch (builtin.os.tag) {
.linux => @ptrToInt(info.fields.sigfault.addr),
.netbsd => @ptrToInt(info.info.reason.fault.addr),
else => unreachable,
};
// Don't use std.debug.print() as stderr_mutex may still be locked.
nosuspend {
const stderr = io.getStdErr().writer();
_ = switch (sig) {
os.SIGSEGV => stderr.print("Segmentation fault at address 0x{x}\n", .{addr}),
os.SIGILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}),
os.SIGBUS => stderr.print("Bus error at address 0x{x}\n", .{addr}),
else => unreachable,
} catch os.abort();
}
switch (builtin.arch) {
2019-11-30 07:13:33 -08:00
.i386 => {
const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
const ip = @intCast(usize, ctx.mcontext.gregs[os.REG_EIP]);
const bp = @intCast(usize, ctx.mcontext.gregs[os.REG_EBP]);
dumpStackTraceFromBase(bp, ip);
},
.x86_64 => {
const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
const ip = @intCast(usize, ctx.mcontext.gregs[os.REG_RIP]);
const bp = @intCast(usize, ctx.mcontext.gregs[os.REG_RBP]);
dumpStackTraceFromBase(bp, ip);
},
.arm => {
const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
const ip = @intCast(usize, ctx.mcontext.arm_pc);
const bp = @intCast(usize, ctx.mcontext.arm_fp);
dumpStackTraceFromBase(bp, ip);
},
.aarch64 => {
const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
const ip = @intCast(usize, ctx.mcontext.pc);
// x29 is the ABI-designated frame pointer
const bp = @intCast(usize, ctx.mcontext.regs[29]);
dumpStackTraceFromBase(bp, ip);
},
else => {},
}
// We cannot allow the signal handler to return because when it runs the original instruction
// again, the memory may be mapped and undefined behavior would occur rather than repeating
// the segfault. So we simply abort here.
os.abort();
}
fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long {
switch (info.ExceptionRecord.ExceptionCode) {
windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, 0, "Unaligned Memory Access"),
windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, 1, null),
windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, 2, null),
windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, 0, "Stack Overflow"),
else => return windows.EXCEPTION_CONTINUE_SEARCH,
}
}
improvements targeted at improving async functions * Reuse bytes of async function frames when non-async functions make `noasync` calls. This prevents explosive stack growth. * Zig now passes a stack size argument to the linker when linking ELF binaries. Linux ignores this value, but it is available as a program header called GNU_STACK. I prototyped some code that memory maps extra space to the stack using this program header, but there was still a problem when accessing stack memory very far down. Stack probing is needed or not working or something. I also prototyped using `@newStackCall` to call main and that does work around the issue but it also brings its own issues. That code is commented out for now in std/special/start.zig. I'm on a plane with no Internet, but I plan to consult with the musl community for advice when I get a chance. * Added `noasync` to a bunch of function calls in std.debug. It's very messy but it's a workaround that makes stack traces functional with evented I/O enabled. Eventually these will be cleaned up as the root bugs are found and fixed. Programs built in blocking mode are unaffected. * Lowered the default stack size of std.io.InStream (for the async version) to 1 MiB instead of 4. Until we figure out how to get choosing a stack size working (see 2nd bullet point above), 4 MiB tends to cause segfaults due to stack size running out, or usage of stack memory too far apart, or something like that. * Default thread stack size is bumped from 8 MiB to 16 to match the size we give for the main thread. It's planned to eventually remove this hard coded value and have Zig able to determine this value during semantic analysis, with call graph analysis and function pointer annotations and extern function annotations.
2019-09-11 17:22:49 -07:00
2020-01-29 13:15:17 -08:00
// zig won't let me use an anon enum here https://github.com/ziglang/zig/issues/3707
fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: u8, comptime format: ?[]const u8) noreturn {
const exception_address = @ptrToInt(info.ExceptionRecord.ExceptionAddress);
2020-01-29 13:15:17 -08:00
if (@hasDecl(windows, "CONTEXT")) {
const regs = info.ContextRecord.getRegs();
// Don't use std.debug.print() as stderr_mutex may still be locked.
nosuspend {
const stderr = io.getStdErr().writer();
_ = switch (msg) {
0 => stderr.print("{s}\n", .{format.?}),
1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}),
2 => stderr.print("Illegal instruction at address 0x{x}\n", .{regs.ip}),
else => unreachable,
} catch os.abort();
}
dumpStackTraceFromBase(regs.bp, regs.ip);
os.abort();
} else {
switch (msg) {
0 => panicExtra(null, exception_address, format.?, .{}),
1 => panicExtra(null, exception_address, "Segmentation fault at address 0x{x}", .{info.ExceptionRecord.ExceptionInformation[1]}),
2 => panicExtra(null, exception_address, "Illegal Instruction", .{}),
else => unreachable,
}
}
}
improvements targeted at improving async functions * Reuse bytes of async function frames when non-async functions make `noasync` calls. This prevents explosive stack growth. * Zig now passes a stack size argument to the linker when linking ELF binaries. Linux ignores this value, but it is available as a program header called GNU_STACK. I prototyped some code that memory maps extra space to the stack using this program header, but there was still a problem when accessing stack memory very far down. Stack probing is needed or not working or something. I also prototyped using `@newStackCall` to call main and that does work around the issue but it also brings its own issues. That code is commented out for now in std/special/start.zig. I'm on a plane with no Internet, but I plan to consult with the musl community for advice when I get a chance. * Added `noasync` to a bunch of function calls in std.debug. It's very messy but it's a workaround that makes stack traces functional with evented I/O enabled. Eventually these will be cleaned up as the root bugs are found and fixed. Programs built in blocking mode are unaffected. * Lowered the default stack size of std.io.InStream (for the async version) to 1 MiB instead of 4. Until we figure out how to get choosing a stack size working (see 2nd bullet point above), 4 MiB tends to cause segfaults due to stack size running out, or usage of stack memory too far apart, or something like that. * Default thread stack size is bumped from 8 MiB to 16 to match the size we give for the main thread. It's planned to eventually remove this hard coded value and have Zig able to determine this value during semantic analysis, with call graph analysis and function pointer annotations and extern function annotations.
2019-09-11 17:22:49 -07:00
pub fn dumpStackPointerAddr(prefix: []const u8) void {
2019-09-25 08:17:29 -07:00
const sp = asm (""
: [argc] "={rsp}" (-> usize)
);
std.debug.warn("{} sp = 0x{x}\n", .{ prefix, sp });
improvements targeted at improving async functions * Reuse bytes of async function frames when non-async functions make `noasync` calls. This prevents explosive stack growth. * Zig now passes a stack size argument to the linker when linking ELF binaries. Linux ignores this value, but it is available as a program header called GNU_STACK. I prototyped some code that memory maps extra space to the stack using this program header, but there was still a problem when accessing stack memory very far down. Stack probing is needed or not working or something. I also prototyped using `@newStackCall` to call main and that does work around the issue but it also brings its own issues. That code is commented out for now in std/special/start.zig. I'm on a plane with no Internet, but I plan to consult with the musl community for advice when I get a chance. * Added `noasync` to a bunch of function calls in std.debug. It's very messy but it's a workaround that makes stack traces functional with evented I/O enabled. Eventually these will be cleaned up as the root bugs are found and fixed. Programs built in blocking mode are unaffected. * Lowered the default stack size of std.io.InStream (for the async version) to 1 MiB instead of 4. Until we figure out how to get choosing a stack size working (see 2nd bullet point above), 4 MiB tends to cause segfaults due to stack size running out, or usage of stack memory too far apart, or something like that. * Default thread stack size is bumped from 8 MiB to 16 to match the size we give for the main thread. It's planned to eventually remove this hard coded value and have Zig able to determine this value during semantic analysis, with call graph analysis and function pointer annotations and extern function annotations.
2019-09-11 17:22:49 -07:00
}