Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions core/src/microzig.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//!
//! But microzig employs a proxy tactic

const builtin = @import("builtin");
const std = @import("std");
const root = @import("root");

Expand Down Expand Up @@ -49,10 +50,17 @@ pub fn assert(expr: bool, opts: AssertOptions) void {
pub const panic = std.debug.FullPanic(struct {
pub fn panic_fn(message: []const u8, first_trace_address: ?usize) noreturn {
std.log.err("panic: {s}", .{message});
_ = first_trace_address;

// TODO: we no longer have StackIterator available to us, so we need to
// create our own.
var trace_index: usize = 0;
if (@errorReturnTrace()) |trace| trace_index = utilities.dump_error_trace(trace);

// Skip if we can't use fp based stack tracing.
if (options.panic_stack_trace) {
var it: utilities.StackIterator = .init(first_trace_address orelse @returnAddress(), null);
while (it.next()) |address| : (trace_index += 1) {
utilities.dump_trace_line(trace_index, address);
}
}

// Attach a breakpoint. this might trigger another panic internally, so
// only do that if requested.
Expand Down Expand Up @@ -85,6 +93,8 @@ pub const Options = struct {
/// reduce code size as the string literals for error names no longer have to
/// be included in the executable.
simple_panic_if_main_errors: bool = false,

panic_stack_trace: bool = builtin.optimize == .debug,
};

pub const options: Options = if (@hasDecl(root, "microzig_options")) root.microzig_options else .{};
Expand Down
121 changes: 119 additions & 2 deletions core/src/utilities.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const builtin = @import("builtin");
const std = @import("std");
const assert = std.debug.assert;

Expand Down Expand Up @@ -280,7 +281,7 @@ pub fn GenerateInterruptOptions(sources: []const Source) type {

var field_names: [count][]const u8 = undefined;
var field_types: [count]type = undefined;
var field_attrs: [count]std.builtin.Type.Struct.FieldAttributes = undefined;
var field_attrs: [count]std.lang.Type.Struct.FieldAttributes = undefined;

var i: usize = 0;
for (sources) |source| {
Expand Down Expand Up @@ -472,7 +473,7 @@ test "SliceVector.Iterator.next_chunk" {
}
}

pub fn dump_stack_trace(trace: *std.builtin.StackTrace) usize {
pub fn dump_stack_trace(trace: *std.lang.StackTrace) usize {
const frame_count = @min(trace.index, trace.instruction_addresses.len);

var frame_index: usize = 0;
Expand Down Expand Up @@ -704,3 +705,119 @@ pub fn IntFracDiv(int_bits: comptime_int, frac_bits: comptime_int) type {
}
};
}

pub fn dump_error_trace(trace: *std.lang.StackTrace) usize {
const frame_count = @min(trace.index, trace.instruction_addresses.len);

var frame_index: usize = 0;
var frames_left: usize = frame_count;
while (frames_left != 0) : ({
frames_left -= 1;
frame_index = (frame_index + 1) % trace.instruction_addresses.len;
}) {
const address = trace.instruction_addresses[frame_index];
dump_trace_line(frame_index, address);
}

return frame_count;
}

pub fn dump_trace_line(index: usize, address: usize) void {
std.log.err("{d: >3}: 0x{X:0>8}", .{ index, address });
}

pub const StackIterator = struct {
const native_arch = builtin.cpu.arch;

// Last known value of the frame pointer register.
fp: usize,
first_address: ?usize,

pub fn init(first_address: ?usize, fp: ?usize) StackIterator {
if (native_arch.isSPARC()) {
// Flush all the register windows on stack.
asm volatile (if (builtin.cpu.has(.sparc, .v9))
"flushw"
else
"ta 3" // ST_FLUSH_WINDOWS
::: .{ .memory = true });
}

return .{
.first_address = first_address,
// TODO: this is a workaround for #16876
//.fp = fp orelse @frameAddress(),
.fp = fp orelse blk: {
const fa = @frameAddress();
break :blk fa;
},
};
}

// Offset of the saved BP wrt the frame pointer.
const fp_offset = if (native_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 if (native_arch.isSPARC())
// On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS.
14 * @sizeOf(usize)
else
0;

const fp_bias = if (native_arch.isSPARC())
// On SPARC frame pointers are biased by a constant.
2047
else
0;

// Positive offset of the saved PC wrt the frame pointer.
const pc_offset = if (native_arch == .powerpc64le)
2 * @sizeOf(usize)
else
@sizeOf(usize);

pub fn next(it: *StackIterator) ?usize {
var address = it.next_internal() orelse return null;

if (it.first_address) |first_address| {
while (address != first_address) {
address = it.next_internal() orelse return null;
}
it.first_address = null;
}

return address;
}

pub fn next_internal(it: *StackIterator) ?usize {
if (builtin.omit_frame_pointer) return null;

const fp = if (comptime native_arch.isSPARC())
// On SPARC the offset is positive. (!)
std.math.add(usize, it.fp, fp_offset) catch return null
else
std.math.sub(usize, it.fp, fp_offset) catch return null;

// Sanity check.
if (fp == 0 or !std.mem.isAligned(fp, @alignOf(usize))) return null;
const new_fp = std.math.add(usize, load(usize, fp), fp_bias) catch
return null;

// 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 < it.fp) return null;
const new_pc = load(usize, std.math.add(usize, fp, pc_offset) catch return null);

it.fp = new_fp;

return new_pc;
}

fn load(T: type, address: usize) T {
return @as(*const T, @ptrFromInt(address)).*;
}
};
Loading