2020-08-19 19:40:15 -07:00
|
|
|
// 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");
|
2020-02-24 22:52:27 -08:00
|
|
|
const builtin = std.builtin;
|
2017-10-31 01:47:55 -07:00
|
|
|
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;
|
2017-12-23 19:08:53 -08:00
|
|
|
const elf = std.elf;
|
|
|
|
const DW = std.dwarf;
|
2018-02-19 14:06:54 -08:00
|
|
|
const macho = std.macho;
|
2018-07-21 11:30:11 -07:00
|
|
|
const coff = std.coff;
|
|
|
|
const pdb = std.pdb;
|
2017-10-31 01:47:55 -07:00
|
|
|
const ArrayList = std.ArrayList;
|
2019-07-22 09:15:16 -07:00
|
|
|
const root = @import("root");
|
2018-10-26 11:59:58 -07:00
|
|
|
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
|
|
|
|
2019-11-04 00:58:02 -08:00
|
|
|
pub const leb = @import("debug/leb128.zig");
|
2019-05-09 14:46:12 -07:00
|
|
|
|
2018-07-05 12:09:02 -07:00
|
|
|
pub const runtime_safety = switch (builtin.mode) {
|
2019-05-26 10:17:34 -07:00
|
|
|
.Debug, .ReleaseSafe => true,
|
|
|
|
.ReleaseFast, .ReleaseSmall => false,
|
2018-07-05 12:09:02 -07:00
|
|
|
};
|
|
|
|
|
2018-11-13 05:08:37 -08:00
|
|
|
const Module = struct {
|
2018-09-02 12:58:08 -07:00
|
|
|
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,
|
2018-09-02 12:58:08 -07:00
|
|
|
};
|
|
|
|
|
2020-02-08 05:15:28 -08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-07 23:26:58 -07:00
|
|
|
var stderr_mutex = std.Mutex{};
|
2019-10-28 13:33:40 -07:00
|
|
|
|
2020-06-02 10:22:22 -07:00
|
|
|
/// 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 {
|
2018-08-01 13:26:37 -07:00
|
|
|
const held = stderr_mutex.acquire();
|
|
|
|
defer held.release();
|
2020-06-15 06:51:25 -07:00
|
|
|
const stderr = io.getStdErr().writer();
|
2020-05-05 04:14:22 -07:00
|
|
|
nosuspend stderr.print(fmt, args) catch return;
|
2017-10-31 01:47:55 -07:00
|
|
|
}
|
2018-08-23 13:11:34 -07:00
|
|
|
|
2019-10-28 13:33:40 -07:00
|
|
|
pub fn getStderrMutex() *std.Mutex {
|
|
|
|
return &stderr_mutex;
|
|
|
|
}
|
|
|
|
|
2018-08-23 13:11:34 -07:00
|
|
|
/// TODO multithreaded awareness
|
|
|
|
var self_debug_info: ?DebugInfo = null;
|
|
|
|
|
2018-08-22 18:35:49 -07:00
|
|
|
pub fn getSelfDebugInfo() !*DebugInfo {
|
2018-08-23 13:11:34 -07:00
|
|
|
if (self_debug_info) |*info| {
|
2018-01-15 13:26:13 -08:00
|
|
|
return info;
|
|
|
|
} else {
|
2018-08-23 13:11:34 -07:00
|
|
|
self_debug_info = try openSelfDebugInfo(getDebugInfoAllocator());
|
|
|
|
return &self_debug_info.?;
|
2018-01-15 13:26:13 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-26 15:28:52 -08:00
|
|
|
pub fn detectTTYConfig() TTY.Config {
|
2018-07-09 09:12:37 -07:00
|
|
|
var bytes: [128]u8 = undefined;
|
|
|
|
const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator;
|
2020-01-26 06:55:04 -08:00
|
|
|
if (process.getEnvVarOwned(allocator, "ZIG_DEBUG_COLOR")) |_| {
|
|
|
|
return .escape_codes;
|
|
|
|
} else |_| {
|
2020-06-15 05:58:59 -07:00
|
|
|
const stderr_file = io.getStdErr();
|
2020-01-26 06:55:04 -08:00
|
|
|
if (stderr_file.supportsAnsiEscapeCodes()) {
|
|
|
|
return .escape_codes;
|
2020-02-24 22:52:27 -08:00
|
|
|
} else if (builtin.os.tag == .windows and stderr_file.isTty()) {
|
2020-01-26 06:55:04 -08:00
|
|
|
return .windows_api;
|
|
|
|
} else {
|
|
|
|
return .no_color;
|
|
|
|
}
|
|
|
|
}
|
2018-07-09 09:12:37 -07:00
|
|
|
}
|
|
|
|
|
2018-01-11 23:12:11 -08:00
|
|
|
/// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned.
|
2018-08-23 20:08:34 -07:00
|
|
|
/// TODO multithreaded awareness
|
2018-03-09 19:21:13 -08:00
|
|
|
pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
|
2020-05-05 04:14:22 -07:00
|
|
|
nosuspend {
|
2020-06-15 06:51:25 -07:00
|
|
|
const stderr = io.getStdErr().writer();
|
2020-05-01 21:41:19 -07:00
|
|
|
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
|
|
|
}
|
2018-01-11 23:12:11 -08:00
|
|
|
}
|
|
|
|
|
2019-07-02 10:27:40 -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 {
|
2020-06-15 06:51:25 -07:00
|
|
|
const stderr = io.getStdErr().writer();
|
2020-05-01 21:41:19 -07:00
|
|
|
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;
|
|
|
|
}
|
2019-07-02 10:27:40 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-23 10:19:06 -08:00
|
|
|
/// 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 {
|
2020-02-24 22:52:27 -08:00
|
|
|
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 {
|
2020-02-05 14:40:36 -08:00
|
|
|
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;
|
2019-02-23 10:19:06 -08:00
|
|
|
return;
|
|
|
|
};
|
2019-05-26 10:17:34 -07:00
|
|
|
}
|
|
|
|
stack_trace.index = stack_trace.instruction_addresses.len;
|
2019-02-23 10:19:06 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-31 01:47:55 -07:00
|
|
|
/// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned.
|
2018-08-23 20:08:34 -07:00
|
|
|
/// TODO multithreaded awareness
|
2019-02-23 10:19:06 -08:00
|
|
|
pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void {
|
2020-05-05 04:14:22 -07:00
|
|
|
nosuspend {
|
2020-06-15 06:51:25 -07:00
|
|
|
const stderr = io.getStdErr().writer();
|
2020-05-01 21:41:19 -07:00
|
|
|
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
|
|
|
}
|
2017-10-31 01:47:55 -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.
|
2019-02-08 15:18:47 -08:00
|
|
|
/// 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
|
2019-02-08 15:18:47 -08:00
|
|
|
/// function is the correct function to use.
|
2018-01-25 01:10:11 -08:00
|
|
|
pub fn assert(ok: bool) void {
|
2019-02-08 15:18:47 -08:00
|
|
|
if (!ok) unreachable; // assertion failure
|
2017-10-31 01:47:55 -07:00
|
|
|
}
|
|
|
|
|
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);
|
2019-11-19 18:55:55 -08:00
|
|
|
// TODO: remove conditional once wasi / LLVM defines __builtin_return_address
|
2020-02-24 22:52:27 -08:00
|
|
|
const first_trace_addr = if (builtin.os.tag == .wasi) null else @returnAddress();
|
2018-03-09 19:21:13 -08:00
|
|
|
panicExtra(null, first_trace_addr, format, args);
|
|
|
|
}
|
|
|
|
|
2020-03-13 09:55:40 -07:00
|
|
|
/// Non-zero whenever the program triggered a panic.
|
|
|
|
/// The counter is incremented/decremented atomically.
|
2019-12-20 14:48:45 -08:00
|
|
|
var panicking: u8 = 0;
|
2018-03-09 19:21:13 -08:00
|
|
|
|
2020-03-13 09:55:40 -07:00
|
|
|
// Locked to avoid interleaving panic messages from multiple threads.
|
2020-08-07 23:26:58 -07:00
|
|
|
var panic_mutex = std.Mutex{};
|
2020-03-13 09:55:40 -07:00
|
|
|
|
|
|
|
/// 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 {
|
2018-03-09 19:21:13 -08:00
|
|
|
@setCold(true);
|
2017-06-13 21:04:34 -07:00
|
|
|
|
2019-07-22 09:15:16 -07:00
|
|
|
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) {
|
2020-02-18 10:03:31 -08:00
|
|
|
0 => {
|
2020-03-13 09:55:40 -07:00
|
|
|
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();
|
|
|
|
|
2020-06-15 06:51:25 -07:00
|
|
|
const stderr = io.getStdErr().writer();
|
2020-05-01 21:41:19 -07:00
|
|
|
stderr.print(format ++ "\n", args) catch os.abort();
|
2020-03-13 09:55:40 -07:00
|
|
|
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;
|
2019-12-20 14:48:45 -08:00
|
|
|
}
|
|
|
|
},
|
2020-02-18 10:03:31 -08:00
|
|
|
1 => {
|
2020-03-13 09:55:40 -07:00
|
|
|
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()
|
2020-06-15 06:51:25 -07:00
|
|
|
const stderr = io.getStdErr().writer();
|
2020-05-01 21:41:19 -07:00
|
|
|
stderr.print("Panicked during a panic. Aborting.\n", .{}) catch os.abort();
|
2019-12-20 14:48:45 -08:00
|
|
|
},
|
|
|
|
else => {
|
|
|
|
// Panicked while printing "Panicked during a panic."
|
|
|
|
},
|
2020-05-01 21:41:19 -07:00
|
|
|
};
|
2020-03-13 09:55:40 -07:00
|
|
|
|
2018-01-15 13:26:13 -08:00
|
|
|
os.abort();
|
|
|
|
}
|
|
|
|
|
2018-11-07 21:36:36 -08:00
|
|
|
const RED = "\x1b[31;1m";
|
2017-04-24 09:01:19 -07:00
|
|
|
const GREEN = "\x1b[32;1m";
|
2018-11-07 21:36:36 -08:00
|
|
|
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";
|
|
|
|
|
2019-02-23 10:19:06 -08:00
|
|
|
pub fn writeStackTrace(
|
|
|
|
stack_trace: builtin.StackTrace,
|
2020-07-11 04:09:04 -07:00
|
|
|
out_stream: anytype,
|
2019-02-23 10:19:06 -08:00
|
|
|
allocator: *mem.Allocator,
|
|
|
|
debug_info: *DebugInfo,
|
2020-01-26 06:55:04 -08:00
|
|
|
tty_config: TTY.Config,
|
2019-02-23 10:19:06 -08:00
|
|
|
) !void {
|
2019-06-14 15:45:41 -07:00
|
|
|
if (builtin.strip_debug_info) return error.MissingDebugInfo;
|
2019-02-23 10:19:06 -08:00
|
|
|
var frame_index: usize = 0;
|
2019-05-08 16:11:01 -07:00
|
|
|
var frames_left: usize = std.math.min(stack_trace.index, stack_trace.instruction_addresses.len);
|
2018-01-11 23:12:11 -08:00
|
|
|
|
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];
|
2020-01-26 06:55:04 -08:00
|
|
|
try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config);
|
2018-01-14 07:19:21 -08:00
|
|
|
}
|
|
|
|
}
|
2018-01-11 23:12:11 -08:00
|
|
|
|
2018-12-02 15:54:04 -08:00
|
|
|
pub const StackIterator = struct {
|
2020-02-05 14:40:36 -08:00
|
|
|
// 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,
|
|
|
|
|
2020-02-05 14:40:36 -08:00
|
|
|
pub fn init(first_address: ?usize, fp: ?usize) StackIterator {
|
2018-12-02 15:54:04 -08:00
|
|
|
return StackIterator{
|
2020-02-05 14:40:36 -08:00
|
|
|
.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
|
|
|
|
2019-10-13 03:13:41 -07:00
|
|
|
// On some architectures such as x86 the frame pointer is the address where
|
|
|
|
// the previous fp is stored, while on some other architectures such as
|
|
|
|
// RISC-V it points to the "top" of the frame, just above where the previous
|
|
|
|
// fp and the return address are stored.
|
2020-02-05 14:40:36 -08:00
|
|
|
const fp_offset = if (builtin.arch.isRISCV())
|
2019-10-13 03:13:41 -07:00
|
|
|
2 * @sizeOf(usize)
|
|
|
|
else
|
|
|
|
0;
|
|
|
|
|
2020-05-13 08:21:15 -07:00
|
|
|
pub fn next(self: *StackIterator) ?usize {
|
2020-02-05 14:40:36 -08:00
|
|
|
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
|
|
|
}
|
2020-02-05 14:40:36 -08:00
|
|
|
self.first_address = null;
|
2018-12-02 15:54:04 -08:00
|
|
|
}
|
|
|
|
|
2020-02-05 14:40:36 -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, fp + @sizeOf(usize)).*;
|
|
|
|
|
|
|
|
self.fp = new_fp;
|
|
|
|
|
|
|
|
return new_pc;
|
2018-12-02 15:54:04 -08:00
|
|
|
}
|
|
|
|
};
|
2018-07-29 20:25:40 -07:00
|
|
|
|
2020-01-26 06:55:04 -08:00
|
|
|
pub fn writeCurrentStackTrace(
|
2020-07-11 04:09:04 -07:00
|
|
|
out_stream: anytype,
|
2020-01-26 06:55:04 -08:00
|
|
|
debug_info: *DebugInfo,
|
|
|
|
tty_config: TTY.Config,
|
|
|
|
start_addr: ?usize,
|
|
|
|
) !void {
|
2020-02-24 22:52:27 -08:00
|
|
|
if (builtin.os.tag == .windows) {
|
2020-01-26 06:55:04 -08:00
|
|
|
return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr);
|
2018-08-29 13:35:51 -07:00
|
|
|
}
|
2020-02-05 14:40:36 -08:00
|
|
|
var it = StackIterator.init(start_addr, null);
|
2018-12-02 15:54:04 -08:00
|
|
|
while (it.next()) |return_address| {
|
2020-01-26 06:55:04 -08:00
|
|
|
try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config);
|
2018-01-14 07:19:21 -08:00
|
|
|
}
|
|
|
|
}
|
2018-01-11 23:12:11 -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,
|
2020-01-26 06:55:04 -08:00
|
|
|
tty_config: TTY.Config,
|
2018-09-12 11:26:21 -07:00
|
|
|
start_addr: ?usize,
|
|
|
|
) !void {
|
2018-08-29 13:35:51 -07:00
|
|
|
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);
|
2018-08-29 13:35:51 -07:00
|
|
|
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| {
|
2020-01-27 04:12:01 -08:00
|
|
|
try printSourceAtAddress(debug_info, out_stream, addr - 1, tty_config);
|
2018-08-29 13:35:51 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-26 06:55:04 -08:00
|
|
|
pub const TTY = struct {
|
|
|
|
pub const Color = enum {
|
|
|
|
Red,
|
|
|
|
Green,
|
|
|
|
Cyan,
|
|
|
|
White,
|
|
|
|
Dim,
|
|
|
|
Bold,
|
|
|
|
Reset,
|
|
|
|
};
|
2020-01-21 11:51:57 -08:00
|
|
|
|
2020-01-26 06:55:04 -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) {
|
2020-01-26 06:55:04 -08:00
|
|
|
.no_color => return,
|
|
|
|
.escape_codes => switch (color) {
|
2020-03-10 12:27:45 -07:00
|
|
|
.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,
|
2020-01-26 06:55:04 -08:00
|
|
|
},
|
2020-02-24 22:52:27 -08:00
|
|
|
.windows_api => if (builtin.os.tag == .windows) {
|
2020-06-15 05:58:59 -07:00
|
|
|
const stderr_file = io.getStdErr();
|
2020-01-26 06:55:04 -08:00
|
|
|
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;
|
|
|
|
}
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-01-26 06:55:04 -08:00
|
|
|
// 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-03-10 12:27:45 -07:00
|
|
|
};
|
2018-11-07 21:36:36 -08:00
|
|
|
}
|
2020-01-26 06:55:04 -08:00
|
|
|
};
|
|
|
|
};
|
2018-09-02 12:58:08 -07:00
|
|
|
|
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 {
|
2018-09-02 12:58:08 -07:00
|
|
|
if (mod.populated)
|
|
|
|
return;
|
|
|
|
const allocator = getDebugInfoAllocator();
|
|
|
|
|
2019-03-15 13:11:27 -07:00
|
|
|
// At most one can be non-zero.
|
|
|
|
if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0)
|
2018-09-02 12:58:08 -07:00
|
|
|
return error.InvalidDebugInfo;
|
|
|
|
|
|
|
|
if (mod.mod_info.C13ByteSize == 0)
|
2019-03-15 13:11:27 -07:00
|
|
|
return;
|
2018-09-02 12:58:08 -07:00
|
|
|
|
|
|
|
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);
|
2018-09-02 12:58:08 -07:00
|
|
|
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);
|
2018-09-02 12:58:08 -07:00
|
|
|
|
|
|
|
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);
|
2018-09-02 12:58:08 -07:00
|
|
|
|
|
|
|
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;
|
2018-09-02 12:58:08 -07:00
|
|
|
},
|
|
|
|
else => {},
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sect_offset > mod.subsect_info.len)
|
|
|
|
return error.InvalidDebugInfo;
|
|
|
|
}
|
|
|
|
|
|
|
|
mod.populated = true;
|
2018-08-28 14:32:32 -07:00
|
|
|
}
|
|
|
|
|
2018-08-23 13:11:34 -07:00
|
|
|
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-08 05:15:28 -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(
|
2020-02-08 05:15:28 -08:00
|
|
|
out_stream,
|
2020-02-22 03:44:21 -08:00
|
|
|
symbol_info.line_info,
|
2020-02-08 05:15:28 -08:00
|
|
|
address,
|
2020-02-22 03:44:21 -08:00
|
|
|
symbol_info.symbol_name,
|
|
|
|
symbol_info.compile_unit_name,
|
2020-02-08 05:15:28 -08:00
|
|
|
tty_config,
|
|
|
|
printLineFromFileAnyOs,
|
|
|
|
);
|
2018-12-02 16:34:11 -08:00
|
|
|
}
|
|
|
|
|
2018-08-23 13:11:34 -07:00
|
|
|
fn printLineInfo(
|
2020-07-11 04:09:04 -07:00
|
|
|
out_stream: anytype,
|
2020-01-21 11:51:57 -08:00
|
|
|
line_info: ?LineInfo,
|
2018-08-23 13:11:34 -07:00
|
|
|
address: usize,
|
|
|
|
symbol_name: []const u8,
|
|
|
|
compile_unit_name: []const u8,
|
2020-01-26 06:55:04 -08:00
|
|
|
tty_config: TTY.Config,
|
2020-07-11 04:09:04 -07:00
|
|
|
comptime printLineFromFile: anytype,
|
2018-08-23 13:11:34 -07:00
|
|
|
) !void {
|
2020-05-05 04:14:22 -07:00
|
|
|
nosuspend {
|
2020-03-10 12:27:45 -07:00
|
|
|
tty_config.setColor(out_stream, .White);
|
2020-01-21 11:51:57 -08:00
|
|
|
|
2020-03-10 12:27:45 -07: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,
|
2018-01-11 23:12:11 -08:00
|
|
|
}
|
2018-08-23 13:11:34 -07:00
|
|
|
}
|
2018-01-11 23:12:11 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-23 13:11:34 -07:00
|
|
|
// TODO use this
|
2018-11-13 05:08:37 -08:00
|
|
|
pub const OpenSelfDebugInfoError = error{
|
2018-08-23 13:11:34 -07:00
|
|
|
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 {
|
2020-03-10 12:27:45 -07:00
|
|
|
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-03-10 12:27:45 -07:00
|
|
|
.macosx,
|
|
|
|
.windows,
|
|
|
|
=> return DebugInfo.init(allocator),
|
|
|
|
else => @compileError("openSelfDebugInfo unsupported for this platform"),
|
|
|
|
}
|
2020-02-09 02:55:43 -08:00
|
|
|
}
|
2020-02-10 04:18:28 -08:00
|
|
|
}
|
2018-08-30 12:33:50 -07:00
|
|
|
|
2020-03-27 13:13:26 -07: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.
|
2020-03-27 13:13:26 -07:00
|
|
|
fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInfo {
|
2020-05-05 04:14:22 -07:00
|
|
|
nosuspend {
|
2020-03-26 05:41:53 -07:00
|
|
|
errdefer coff_file.close();
|
2020-02-20 11:18:51 -08:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
const coff_obj = try allocator.create(coff.Coff);
|
|
|
|
coff_obj.* = coff.Coff.init(allocator, coff_file);
|
2018-08-30 12:33:50 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
var di = ModuleDebugInfo{
|
|
|
|
.base_address = undefined,
|
|
|
|
.coff = coff_obj,
|
|
|
|
.pdb = undefined,
|
|
|
|
.sect_contribs = undefined,
|
|
|
|
.modules = undefined,
|
|
|
|
};
|
2018-02-19 14:06:54 -08:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
try di.coff.loadHeader();
|
2018-07-21 11:30:11 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
var path_buf: [windows.MAX_PATH]u8 = undefined;
|
|
|
|
const len = try di.coff.getPdbPath(path_buf[0..]);
|
|
|
|
const raw_path = path_buf[0..len];
|
2018-08-28 15:55:51 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
const path = try fs.path.resolve(allocator, &[_][]const u8{raw_path});
|
2018-08-28 14:32:32 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
try di.pdb.openFile(di.coff, path);
|
2018-08-28 14:32:32 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
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.
|
2018-08-28 14:32:32 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
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
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
const HashTableHeader = packed struct {
|
|
|
|
Size: u32,
|
|
|
|
Capacity: u32,
|
2018-08-31 16:50:03 -07:00
|
|
|
|
2020-03-26 05:41:53 -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;
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity))
|
|
|
|
return error.InvalidDebugInfo;
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
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);
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
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-05-01 16:02:16 -07:00
|
|
|
const name = mem.spanZ(@ptrCast([*:0]u8, name_bytes.ptr + name_offset));
|
2020-03-26 05:41:53 -07:00
|
|
|
if (mem.eql(u8, name, "/names")) {
|
|
|
|
break :str_tab_index name_index;
|
|
|
|
}
|
2018-09-02 12:58:08 -07:00
|
|
|
}
|
2020-03-26 05:41:53 -07:00
|
|
|
return error.MissingDebugInfo;
|
|
|
|
};
|
2018-08-31 16:50:03 -07:00
|
|
|
|
2020-03-26 05:41:53 -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
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
const dbi = di.pdb.dbi;
|
2018-08-31 16:50:03 -07:00
|
|
|
|
2020-03-26 05:41:53 -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;
|
2019-07-28 10:03:36 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
const mod_info_size = dbi_stream_header.ModInfoSize;
|
|
|
|
const section_contrib_size = dbi_stream_header.SectionContributionSize;
|
2018-08-31 16:50:03 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
var modules = ArrayList(Module).init(allocator);
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
// 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);
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
const module_name = try dbi.readNullTermString(allocator);
|
|
|
|
this_record_len += module_name.len + 1;
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
const obj_file_name = try dbi.readNullTermString(allocator);
|
|
|
|
this_record_len += obj_file_name.len + 1;
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
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
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
try modules.append(Module{
|
|
|
|
.mod_info = mod_info,
|
|
|
|
.module_name = module_name,
|
|
|
|
.obj_file_name = obj_file_name,
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
.populated = false,
|
|
|
|
.symbols = undefined,
|
|
|
|
.subsect_info = undefined,
|
|
|
|
.checksum_offset = null,
|
|
|
|
});
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
mod_info_offset += this_record_len;
|
|
|
|
if (mod_info_offset > mod_info_size)
|
|
|
|
return error.InvalidDebugInfo;
|
|
|
|
}
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
di.modules = modules.toOwnedSlice();
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
// 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);
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
if (sect_cont_offset > section_contrib_size)
|
|
|
|
return error.InvalidDebugInfo;
|
|
|
|
}
|
2018-09-02 12:58:08 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
di.sect_contribs = sect_contribs.toOwnedSlice();
|
2018-08-31 12:02:41 -07:00
|
|
|
|
2020-03-26 05:41:53 -07:00
|
|
|
return di;
|
|
|
|
}
|
2018-08-28 14:32:32 -07:00
|
|
|
}
|
|
|
|
|
2020-07-11 04:09:04 -07:00
|
|
|
fn readSparseBitVector(stream: anytype, allocator: *mem.Allocator) ![]usize {
|
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) {
|
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);
|
|
|
|
}
|
2018-10-26 11:59:58 -07:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2020-03-27 13:13:26 -07:00
|
|
|
/// 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.
|
2020-03-27 13:13:26 -07:00
|
|
|
pub fn readElfDebugInfo(allocator: *mem.Allocator, elf_file: File) !ModuleDebugInfo {
|
2020-05-05 04:14:22 -07:00
|
|
|
nosuspend {
|
2020-03-27 13:13:26 -07:00
|
|
|
const mapped_mem = try mapWholeFile(elf_file);
|
2020-03-10 12:27:45 -07:00
|
|
|
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);
|
2020-03-10 15:54:24 -07:00
|
|
|
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)]),
|
2020-03-10 15:54:24 -07:00
|
|
|
);
|
|
|
|
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];
|
2020-03-10 12:27:45 -07:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
const name = std.mem.span(@ptrCast([*:0]const u8, header_strings[shdr.sh_name..].ptr));
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2020-01-23 09:07:05 -08:00
|
|
|
|
2020-03-10 12:27:45 -07:00
|
|
|
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,
|
|
|
|
};
|
2020-01-23 09:07:05 -08:00
|
|
|
|
2020-03-10 12:27:45 -07:00
|
|
|
try DW.openDwarfDebugInfo(&di, allocator);
|
2020-02-20 11:18:51 -08:00
|
|
|
|
2020-03-10 12:27:45 -07: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
|
2020-03-27 13:13:26 -07:00
|
|
|
/// 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.
|
2020-03-27 13:13:26 -07:00
|
|
|
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;
|
2018-08-23 13:11:34 -07:00
|
|
|
|
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);
|
2018-08-23 13:11:34 -07:00
|
|
|
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);
|
2018-08-23 13:11:34 -07:00
|
|
|
switch (lc.cmd) {
|
2020-02-18 03:37:25 -08:00
|
|
|
std.macho.LC_SYMTAB => break @ptrCast(*const std.macho.symtab_command, ptr),
|
2018-08-23 13:11:34 -07:00
|
|
|
else => {},
|
|
|
|
}
|
2019-12-20 04:58:19 -08:00
|
|
|
ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize);
|
2018-08-23 13:11:34 -07:00
|
|
|
} 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];
|
2020-04-16 02:10:53 -07:00
|
|
|
const strings = @ptrCast([*]const u8, hdr_base + symtab.stroff)[0 .. symtab.strsize - 1 :0];
|
2018-08-23 13:11:34 -07:00
|
|
|
|
|
|
|
const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
|
|
|
|
|
2020-02-18 03:37:25 -08:00
|
|
|
var ofile: ?*const macho.nlist_64 = null;
|
2018-08-24 11:55:55 -07:00
|
|
|
var reloc: u64 = 0;
|
2018-08-23 13:11:34 -07:00
|
|
|
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) {
|
2018-08-23 13:11:34 -07:00
|
|
|
switch (sym.n_type) {
|
2018-08-25 00:07:37 -07:00
|
|
|
std.macho.N_OSO => {
|
2018-08-24 11:55:55 -07:00
|
|
|
ofile = sym;
|
|
|
|
reloc = 0;
|
|
|
|
},
|
2018-08-25 00:07:37 -07:00
|
|
|
std.macho.N_FUN => {
|
2018-08-23 13:11:34 -07:00
|
|
|
if (sym.n_sect == 0) {
|
|
|
|
last_len = sym.n_value;
|
|
|
|
} else {
|
2018-11-13 05:08:37 -08:00
|
|
|
symbols_buf[symbol_index] = MachoSymbol{
|
2018-08-23 13:11:34 -07:00
|
|
|
.nlist = sym,
|
|
|
|
.ofile = ofile,
|
2018-08-24 11:55:55 -07:00
|
|
|
.reloc = reloc,
|
2018-08-23 13:11:34 -07:00
|
|
|
};
|
|
|
|
symbol_index += 1;
|
|
|
|
}
|
|
|
|
},
|
2018-08-25 00:07:37 -07:00
|
|
|
std.macho.N_BNSYM => {
|
2018-08-24 11:55:55 -07:00
|
|
|
if (reloc == 0) {
|
|
|
|
reloc = sym.n_value;
|
|
|
|
}
|
|
|
|
},
|
2018-08-23 13:11:34 -07:00
|
|
|
else => continue,
|
|
|
|
}
|
|
|
|
}
|
2016-05-17 13:32:43 -07:00
|
|
|
}
|
2019-02-03 13:13:28 -08:00
|
|
|
const sentinel = try allocator.create(macho.nlist_64);
|
2018-11-13 05:08:37 -08:00
|
|
|
sentinel.* = macho.nlist_64{
|
2018-08-23 13:11:34 -07:00
|
|
|
.n_strx = 0,
|
|
|
|
.n_type = 36,
|
|
|
|
.n_sect = 0,
|
|
|
|
.n_desc = 0,
|
|
|
|
.n_value = symbols_buf[symbol_index - 1].nlist.n_value + last_len,
|
|
|
|
};
|
|
|
|
|
2019-03-15 14:47:47 -07:00
|
|
|
const symbols = allocator.shrink(symbols_buf, symbol_index);
|
2018-08-23 13:11:34 -07:00
|
|
|
|
|
|
|
// 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);
|
2018-08-23 13:11:34 -07:00
|
|
|
|
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),
|
2018-08-23 13:11:34 -07:00
|
|
|
.symbols = symbols,
|
|
|
|
.strings = strings,
|
|
|
|
};
|
2016-05-17 13:32:43 -07:00
|
|
|
}
|
2016-08-11 22:25:13 -07:00
|
|
|
|
2020-07-11 04:09:04 -07:00
|
|
|
fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void {
|
2020-03-10 16:28:05 -07:00
|
|
|
// 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.
|
2020-05-01 20:17:15 -07:00
|
|
|
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
|
|
|
|
|
2019-05-24 15:27:18 -07:00
|
|
|
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) {
|
2018-01-07 13:51:46 -08:00
|
|
|
const amt_read = try f.read(buf[0..]);
|
2017-05-19 07:39:59 -07:00
|
|
|
const slice = buf[0..amt_read];
|
2017-04-24 09:01:19 -07:00
|
|
|
|
|
|
|
for (slice) |byte| {
|
|
|
|
if (line == line_info.line) {
|
2018-01-07 13:51:46 -08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-30 17:35:54 -07:00
|
|
|
if (amt_read < buf.len) return error.EndOfFile;
|
2017-04-24 09:01:19 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-13 05:08:37 -08:00
|
|
|
const MachoSymbol = struct {
|
2020-02-18 03:37:25 -08:00
|
|
|
nlist: *const macho.nlist_64,
|
|
|
|
ofile: ?*const macho.nlist_64,
|
2018-08-24 11:55:55 -07:00
|
|
|
reloc: u64,
|
2018-08-23 13:11:34 -07:00
|
|
|
|
|
|
|
/// 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 {
|
2018-08-23 13:11:34 -07:00
|
|
|
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.
|
2020-03-27 13:13:26 -07:00
|
|
|
fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {
|
2020-05-05 04:14:22 -07:00
|
|
|
nosuspend {
|
2020-03-10 12:27:45 -07:00
|
|
|
defer file.close();
|
2020-02-20 11:18:51 -08:00
|
|
|
|
2020-03-10 12:27:45 -07: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)
|
2020-02-24 22:52:27 -08:00
|
|
|
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);
|
|
|
|
|
2020-03-30 11:23:22 -07:00
|
|
|
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,
|
|
|
|
};
|
2020-03-27 13:13:26 -07:00
|
|
|
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();
|
|
|
|
|
2020-02-18 10:03:31 -08:00
|
|
|
// 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,
|
2020-02-18 10:03:31 -08:00
|
|
|
@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
|
|
|
|
2020-02-18 10:03:31 -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;
|
2020-02-18 10:03:31 -08:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2020-03-27 13:13:26 -07:00
|
|
|
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,
|
|
|
|
};
|
2020-03-27 13:13:26 -07:00
|
|
|
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
|
2020-03-31 07:25:25 -07:00
|
|
|
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 })
|
2020-03-27 13:13:26 -07:00
|
|
|
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,
|
|
|
|
};
|
2020-03-27 13:13:26 -07:00
|
|
|
|
|
|
|
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
|
|
|
};
|
|
|
|
|
2020-02-24 22:52:27 -08:00
|
|
|
pub const ModuleDebugInfo = switch (builtin.os.tag) {
|
2019-05-26 10:17:34 -07:00
|
|
|
.macosx, .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,
|
2018-08-23 13:11:34 -07:00
|
|
|
symbols: []const MachoSymbol,
|
2020-02-22 02:51:45 -08:00
|
|
|
strings: [:0]const u8,
|
2018-08-23 20:08:34 -07:00
|
|
|
ofiles: OFileTable,
|
|
|
|
|
2020-02-22 02:51:45 -08:00
|
|
|
const OFileTable = std.StringHashMap(DW.DwarfInfo);
|
2018-08-24 11:55:55 -07:00
|
|
|
|
2020-02-18 03:37:25 -08:00
|
|
|
pub fn allocator(self: @This()) *mem.Allocator {
|
2018-08-24 11:55:55 -07:00
|
|
|
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 });
|
2020-03-27 13:13:26 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {
|
2020-05-05 04:14:22 -07:00
|
|
|
nosuspend {
|
2020-05-01 16:02:16 -07:00
|
|
|
// 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
|
|
|
|
2020-05-01 16:02:16 -07: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
|
|
|
|
2020-05-01 16:02:16 -07: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
|
|
|
|
2020-05-01 16:02:16 -07:00
|
|
|
if (symbol.ofile == null)
|
2020-04-16 02:10:53 -07:00
|
|
|
return SymbolInfo{ .symbol_name = stab_symbol };
|
2020-02-22 02:51:45 -08:00
|
|
|
|
2020-05-01 16:02:16 -07:00
|
|
|
const o_file_path = mem.spanZ(self.strings[symbol.ofile.?.n_strx..]);
|
2020-02-22 02:51:45 -08:00
|
|
|
|
2020-05-01 16:02:16 -07: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
|
2020-05-01 16:02:16 -07:00
|
|
|
(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
|
|
|
},
|
2020-05-01 16:02:16 -07: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
|
|
|
},
|
2020-05-01 16:02:16 -07:00
|
|
|
else => return err,
|
|
|
|
}
|
2020-02-22 02:51:45 -08:00
|
|
|
|
2020-05-01 16:02:16 -07:00
|
|
|
unreachable;
|
|
|
|
}
|
2020-02-22 02:51:45 -08:00
|
|
|
}
|
2018-02-19 14:06:54 -08:00
|
|
|
},
|
2019-05-26 10:17:34 -07:00
|
|
|
.uefi, .windows => struct {
|
2020-02-10 04:18:28 -08:00
|
|
|
base_address: usize,
|
2018-07-21 11:30:11 -07:00
|
|
|
pdb: pdb.Pdb,
|
2018-08-30 12:33:50 -07:00
|
|
|
coff: *coff.Coff,
|
2018-09-02 12:58:08 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {
|
|
|
|
// 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.
|
2020-03-30 11:23:22 -07:00
|
|
|
coff_section = &self.coff.sections.span()[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) {
|
2020-03-30 11:23:22 -07:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
}
|
2018-07-21 11:30:11 -07:00
|
|
|
},
|
2020-03-23 15:26:21 -07:00
|
|
|
.linux, .netbsd, .freebsd, .dragonfly => 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
|
|
|
|
|
|
|
fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {
|
|
|
|
// 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
|
|
|
},
|
2020-02-08 05:15:28 -08:00
|
|
|
else => DW.DwarfInfo,
|
2016-12-18 16:40:26 -08:00
|
|
|
};
|
2016-09-20 13:10:34 -07:00
|
|
|
|
2018-08-23 13:11:34 -07:00
|
|
|
/// TODO multithreaded awareness
|
2018-05-31 07:56:59 -07:00
|
|
|
var debug_info_allocator: ?*mem.Allocator = null;
|
2018-04-15 17:15:19 -07:00
|
|
|
var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;
|
2018-05-31 07:56:59 -07:00
|
|
|
fn getDebugInfoAllocator() *mem.Allocator {
|
2018-04-15 17:15:19 -07:00
|
|
|
if (debug_info_allocator) |a| return a;
|
|
|
|
|
2019-11-25 14:25:06 -08:00
|
|
|
debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
2018-04-15 17:15:19 -07:00
|
|
|
debug_info_allocator = &debug_info_arena_allocator.allocator;
|
|
|
|
return &debug_info_arena_allocator.allocator;
|
|
|
|
}
|
2019-07-02 10:27:40 -07:00
|
|
|
|
|
|
|
/// Whether or not the current target can print useful debug information when a segfault occurs.
|
2020-04-01 02:03:36 -07:00
|
|
|
pub const have_segfault_handling_support = switch (builtin.os.tag) {
|
|
|
|
.linux, .netbsd => true,
|
|
|
|
.windows => true,
|
|
|
|
else => false,
|
|
|
|
};
|
2019-07-22 09:15:16 -07:00
|
|
|
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;
|
2019-07-02 10:27:40 -07:00
|
|
|
|
|
|
|
/// 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");
|
|
|
|
}
|
2020-02-24 22:52:27 -08:00
|
|
|
if (builtin.os.tag == .windows) {
|
2019-07-22 09:15:16 -07:00
|
|
|
windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows);
|
|
|
|
return;
|
2019-07-03 08:16:52 -07:00
|
|
|
}
|
2019-07-22 09:15:16 -07:00
|
|
|
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);
|
2019-10-31 14:24:22 -07:00
|
|
|
os.sigaction(os.SIGILL, &act, null);
|
2020-02-07 07:06:33 -08:00
|
|
|
os.sigaction(os.SIGBUS, &act, null);
|
2019-07-02 10:27:40 -07:00
|
|
|
}
|
|
|
|
|
2019-07-22 09:15:16 -07:00
|
|
|
fn resetSegfaultHandler() void {
|
2020-02-24 22:52:27 -08:00
|
|
|
if (builtin.os.tag == .windows) {
|
2019-07-22 09:15:16 -07:00
|
|
|
if (windows_segfault_handle) |handle| {
|
|
|
|
assert(windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0);
|
|
|
|
windows_segfault_handle = null;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2019-07-02 10:27:40 -07:00
|
|
|
var act = os.Sigaction{
|
|
|
|
.sigaction = os.SIG_DFL,
|
|
|
|
.mask = os.empty_sigset,
|
|
|
|
.flags = 0,
|
|
|
|
};
|
|
|
|
os.sigaction(os.SIGSEGV, &act, null);
|
2019-10-31 14:24:22 -07:00
|
|
|
os.sigaction(os.SIGILL, &act, null);
|
2020-02-07 07:06:33 -08:00
|
|
|
os.sigaction(os.SIGBUS, &act, null);
|
2019-07-22 09:15:16 -07:00
|
|
|
}
|
|
|
|
|
2020-04-01 02:03:36 -07:00
|
|
|
fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const c_void) callconv(.C) noreturn {
|
2019-07-22 09:15:16 -07:00
|
|
|
// 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();
|
2019-07-02 10:27:40 -07:00
|
|
|
|
2020-04-01 02:03:36 -07:00
|
|
|
const addr = switch (builtin.os.tag) {
|
|
|
|
.linux => @ptrToInt(info.fields.sigfault.addr),
|
|
|
|
.netbsd => @ptrToInt(info.info.reason.fault.addr),
|
|
|
|
else => unreachable,
|
|
|
|
};
|
2019-10-31 14:24:22 -07:00
|
|
|
switch (sig) {
|
|
|
|
os.SIGSEGV => std.debug.warn("Segmentation fault at address 0x{x}\n", .{addr}),
|
|
|
|
os.SIGILL => std.debug.warn("Illegal instruction at address 0x{x}\n", .{addr}),
|
2020-02-07 07:06:33 -08:00
|
|
|
os.SIGBUS => std.debug.warn("Bus error at address 0x{x}\n", .{addr}),
|
2019-10-31 14:24:22 -07:00
|
|
|
else => unreachable,
|
|
|
|
}
|
2019-10-01 06:57:38 -07:00
|
|
|
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);
|
|
|
|
},
|
2019-10-01 06:57:38 -07:00
|
|
|
.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 => {},
|
|
|
|
}
|
2019-07-02 10:27:40 -07:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
}
|
2019-07-03 08:16:52 -07:00
|
|
|
|
2019-12-23 12:52:06 -08:00
|
|
|
fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.Stdcall) c_long {
|
2019-07-03 08:16:52 -07:00
|
|
|
switch (info.ExceptionRecord.ExceptionCode) {
|
2020-01-29 06:57:43 -08:00
|
|
|
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"),
|
2019-07-03 08:16:52 -07:00
|
|
|
else => return windows.EXCEPTION_CONTINUE_SEARCH,
|
|
|
|
}
|
|
|
|
}
|
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
|
2020-01-29 06:57:43 -08:00
|
|
|
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();
|
2020-01-29 06:57:43 -08:00
|
|
|
switch (msg) {
|
|
|
|
0 => std.debug.warn("{}\n", .{format.?}),
|
|
|
|
1 => std.debug.warn("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}),
|
|
|
|
2 => std.debug.warn("Illegal instruction at address 0x{x}\n", .{regs.ip}),
|
|
|
|
else => unreachable,
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
);
|
2019-12-08 19:53:51 -08:00
|
|
|
std.debug.warn("{} sp = 0x{x}\n", .{ prefix, sp });
|
2019-09-11 17:22:49 -07:00
|
|
|
}
|
2019-10-07 20:53:17 -07:00
|
|
|
|
|
|
|
// Reference everything so it gets tested.
|
|
|
|
test "" {
|
|
|
|
_ = leb;
|
|
|
|
}
|