commit 9dbf9492dfac124b1c90a5606d936242ce064462
parent fffc6cf92f77e2c48aa1394e91885ddc0c535c84
Author: Drew DeVault <sir@cmpwn.com>
Date: Tue, 2 Jan 2024 12:43:31 +0100
debug: new module
The debug module provides runtime debugging support features, such as
enhanced backtraces.
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
23 files changed, 832 insertions(+), 0 deletions(-)
diff --git a/debug/+aarch64/getfp.s b/debug/+aarch64/getfp.s
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+.section ".text.debug.getfp","ax"
+.global debug.getfp
+.type debug.getfp,@function
+debug.getfp:
+ mov x0, x29
+ ret
diff --git a/debug/+aarch64/walk.ha b/debug/+aarch64/walk.ha
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+fn getfp() *stackframe;
+
+// Details for a stack frame. Contents are architecture-specific.
+export type stackframe = struct {
+ fp: nullable *stackframe,
+ lr: uintptr,
+};
+
+// Returns the caller's stack frame. Call [[next]] to walk the stack.
+export fn walk() stackframe = *getfp();
+
+// Returns the next stack frame walking the stack.
+export fn next(frame: stackframe) (stackframe | void) = {
+ match (frame.fp) {
+ case null =>
+ return;
+ case let next: *stackframe =>
+ if (next.fp == null) {
+ return;
+ };
+ return *next;
+ };
+};
+
+// Return the program counter address for the given stack frame.
+export fn frame_pc(frame: stackframe) uintptr = frame.lr;
+
+// Implementation detail, constructs a synthetic stack frame.
+fn mkframe(next: *stackframe, ip: uintptr) stackframe = {
+ return stackframe {
+ fp = next,
+ lr = ip,
+ };
+};
diff --git a/debug/+freebsd/+aarch64/ucontext.ha b/debug/+freebsd/+aarch64/ucontext.ha
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use rt;
+
+def AARCH64_FP: uint = 29; // fp is an alias of r29
+def AARCH64_LR: uint = 30; // lr is an alias of r30
+
+// Returns the stack pointer from a ucontext.
+fn uctx_sp(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.uc_mcontext.mc_gpregs.gp_sp: uintptr;
+};
+
+// Returns the instruction pointer from a ucontext.
+fn uctx_ip(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.uc_mcontext.mc_gpregs.gp_elr: uintptr;
+};
+
+// Returns the current call frame from a ucontext.
+fn uctx_frame(uctx: *opaque) stackframe = {
+ const uctx = uctx: *rt::ucontext;
+ return *(uctx.uc_mcontext.mc_gpregs.gp_x[AARCH64_FP]: uintptr: *stackframe);
+};
diff --git a/debug/+freebsd/+riscv64/ucontext.ha b/debug/+freebsd/+riscv64/ucontext.ha
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use rt;
+
+// Returns the stack pointer from a ucontext.
+fn uctx_sp(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.uc_mcontext.mc_gpregs.gp_sp: uintptr;
+};
+
+// Returns the instruction pointer from a ucontext.
+fn uctx_ip(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.uc_mcontext.mc_gpregs.gp_sepc: uintptr;
+};
+
+// Returns the current call frame from a ucontext.
+fn uctx_frame(uctx: *opaque) stackframe = {
+ const uctx = uctx: *rt::ucontext;
+ return *(uctx.uc_mcontext.mc_gpregs.gp_s[0]: uintptr: *stackframe);
+};
diff --git a/debug/+freebsd/+x86_64/ucontext.ha b/debug/+freebsd/+x86_64/ucontext.ha
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use rt;
+
+// Returns the stack pointer from a ucontext.
+fn uctx_sp(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.uc_mcontext.mc_rsp: uintptr;
+};
+
+// Returns the instruction pointer from a ucontext.
+fn uctx_ip(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.uc_mcontext.mc_rip: uintptr;
+};
+
+// Returns the current call frame from a ucontext.
+fn uctx_frame(uctx: *opaque) stackframe = {
+ const uctx = uctx: *rt::ucontext;
+ return *(uctx.uc_mcontext.mc_rbp: uintptr: *stackframe);
+};
diff --git a/debug/+linux/+aarch64/ucontext.ha b/debug/+linux/+aarch64/ucontext.ha
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use rt;
+
+def AARCH64_FP: uint = 29; // fp is an alias of r29
+def AARCH64_LR: uint = 30; // lr is an alias of r30
+
+// Returns the stack pointer from a ucontext.
+fn uctx_sp(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.uc_mcontext.sp: uintptr;
+};
+
+// Returns the instruction pointer from a ucontext.
+fn uctx_ip(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.uc_mcontext.pc: uintptr;
+};
+
+// Returns the current call frame from a ucontext.
+fn uctx_frame(uctx: *opaque) stackframe = {
+ const uctx = uctx: *rt::ucontext;
+ return *(uctx.uc_mcontext.regs[AARCH64_FP]: uintptr: *stackframe);
+};
diff --git a/debug/+linux/+riscv64/ucontext.ha b/debug/+linux/+riscv64/ucontext.ha
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use rt;
+
+// Returns the stack pointer from a ucontext.
+fn uctx_sp(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.uc_mcontext.sc_regs.sp: uintptr;
+};
+
+// Returns the instruction pointer from a ucontext.
+fn uctx_ip(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.uc_mcontext.sc_regs.pc: uintptr;
+};
+
+// Returns the current call frame from a ucontext.
+fn uctx_frame(uctx: *opaque) stackframe = {
+ const uctx = uctx: *rt::ucontext;
+ return *(uctx.uc_mcontext.sc_regs.s0: uintptr: *stackframe);
+};
diff --git a/debug/+linux/+x86_64/ucontext.ha b/debug/+linux/+x86_64/ucontext.ha
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use rt;
+
+// Returns the stack pointer from a ucontext.
+fn uctx_sp(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.uc_mcontext.sp: uintptr;
+};
+
+// Returns the instruction pointer from a ucontext.
+fn uctx_ip(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.uc_mcontext.ip: uintptr;
+};
+
+// Returns the current call frame from a ucontext.
+fn uctx_frame(uctx: *opaque) stackframe = {
+ const uctx = uctx: *rt::ucontext;
+ return *(uctx.uc_mcontext.bp: uintptr: *stackframe);
+};
diff --git a/debug/+openbsd/+aarch64/ucontext.ha b/debug/+openbsd/+aarch64/ucontext.ha
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use rt;
+
+def AARCH64_FP: uint = 29; // fp is an alias of r29
+def AARCH64_LR: uint = 30; // lr is an alias of r30
+
+// Returns the stack pointer from a ucontext.
+fn uctx_sp(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.sc_sp: uintptr;
+};
+
+// Returns the instruction pointer from a ucontext.
+fn uctx_ip(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.sc_elr: uintptr;
+};
+
+// Returns the current call frame from a ucontext.
+fn uctx_frame(uctx: *opaque) stackframe = {
+ const uctx = uctx: *rt::ucontext;
+ return *(uctx.sc_x[AARCH64_FP]: uintptr: *stackframe);
+};
diff --git a/debug/+openbsd/+riscv64/ucontext.ha b/debug/+openbsd/+riscv64/ucontext.ha
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use rt;
+
+// Returns the stack pointer from a ucontext.
+fn uctx_sp(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.sc_sp: uintptr;
+};
+
+// Returns the instruction pointer from a ucontext.
+fn uctx_ip(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.sc_sepc: uintptr;
+};
+
+// Returns the current call frame from a ucontext.
+fn uctx_frame(uctx: *opaque) stackframe = {
+ const uctx = uctx: *rt::ucontext;
+ return *(uctx.sc_s[0]: uintptr: *stackframe);
+};
diff --git a/debug/+openbsd/+x86_64/ucontext.ha b/debug/+openbsd/+x86_64/ucontext.ha
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use rt;
+
+// Returns the stack pointer from a ucontext.
+fn uctx_sp(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.sc_rsp: uintptr;
+};
+
+// Returns the instruction pointer from a ucontext.
+fn uctx_ip(uctx: *opaque) uintptr = {
+ const uctx = uctx: *rt::ucontext;
+ return uctx.sc_rip: uintptr;
+};
+
+// Returns the current call frame from a ucontext.
+fn uctx_frame(uctx: *opaque) stackframe = {
+ const uctx = uctx: *rt::ucontext;
+ return *(uctx.sc_rbp: uintptr: *stackframe);
+};
diff --git a/debug/+riscv64/getfp.s b/debug/+riscv64/getfp.s
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+.section ".text.debug.getfp","ax"
+.global debug.getfp
+.type debug.getfp,@function
+debug.getfp:
+ mv a0, fp
+ ret
diff --git a/debug/+riscv64/walk.ha b/debug/+riscv64/walk.ha
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+fn getfp() *stackframe;
+
+// Details for a stack frame. Contents are architecture-specific.
+export type stackframe = struct {
+ fp: nullable *stackframe,
+ ra: uintptr,
+};
+
+// Returns the caller's stack frame. Call [[next]] to walk the stack.
+export fn walk() stackframe = *getfp();
+
+// Returns the next stack frame walking the stack.
+export fn next(frame: stackframe) (stackframe | void) = {
+ match (frame.fp) {
+ case null =>
+ return;
+ case let next: *stackframe =>
+ if (next.fp == null) {
+ return;
+ };
+ return *next;
+ };
+};
+
+// Return the program counter address for the given stack frame.
+export fn frame_pc(frame: stackframe) uintptr = frame.ra;
+
+// Implementation detail, constructs a synthetic stack frame.
+fn mkframe(next: *stackframe, ip: uintptr) stackframe = {
+ return stackframe {
+ fp = next,
+ ra = ip,
+ };
+};
diff --git a/debug/+x86_64/getfp.s b/debug/+x86_64/getfp.s
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+.section ".text.debug.getfp","ax"
+.global debug.getfp
+.type debug.getfp,@function
+debug.getfp:
+ endbr64
+ movq %rbp,%rax
+ ret
diff --git a/debug/+x86_64/walk.ha b/debug/+x86_64/walk.ha
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+fn getfp() *stackframe;
+
+// Details for a stack frame. Contents are architecture-specific.
+export type stackframe = struct {
+ fp: nullable *stackframe,
+ ip: uintptr,
+};
+
+// Returns the caller's stack frame. Call [[next]] to walk the stack.
+export fn walk() stackframe = *getfp();
+
+// Returns the next stack frame walking the stack.
+export fn next(frame: stackframe) (stackframe | void) = {
+ match (frame.fp) {
+ case null =>
+ return;
+ case let next: *stackframe =>
+ if (next.fp == null) {
+ return;
+ };
+ return *next;
+ };
+};
+
+// Return the program counter address for the given stack frame.
+export fn frame_pc(frame: stackframe) uintptr = frame.ip;
+
+// Implementation detail, constructs a synthetic stack frame.
+fn mkframe(next: *stackframe, ip: uintptr) stackframe = {
+ return stackframe {
+ fp = next,
+ ip = ip,
+ };
+};
diff --git a/debug/README b/debug/README
@@ -0,0 +1,19 @@
+The debug module implements various runtime debugging services. It is enabled by
+default when you build programs in debug mode (you can disable this by building
+in release mode with the -R flag to hare(1)). It provides detailed backtraces in
+various error conditions, including:
+
+- Assertion failures
+- Built-in assertions (e.g. for the "as" operator)
+- Segmentation faults
+- Arithmetic exceptions (e.g. divide by zero)
+- Bus errors
+- Stack overflows
+
+In order to accomplish this, the debug module does some logic on @init which
+rigs up [[rt]] with debugging hooks and installs the relevant signal handlers
+globally. If you set your own signal handlers for terminating signals (e.g.
+SIGFPE) that the debug module handles, they will override the debug hooks.
+
+This module may also be used explicitly to inspect details of the running
+program -- for instance, you can trace the call stack with [[walk]].
diff --git a/debug/abort.ha b/debug/abort.ha
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use debug::image;
+use fmt;
+use format::elf;
+use rt;
+
+@init fn init_abort() void = {
+ rt::onabort(&debug_abort);
+};
+
+// Note: take care not to get into an abort loop when working on this code
+fn debug_abort(
+ path: *str,
+ line: u64,
+ col: u64,
+ msg: str,
+) never = {
+ fmt::errorfln("Abort: {}:{}:{}: {}", *path, line, col, msg): void;
+
+ const self = match (image::self()) {
+ case let img: image::image =>
+ yield img;
+ case => halt();
+ };
+ defer image::close(&self);
+
+ let frame = walk();
+ // Skip rt::abort and debug::debug_abort
+ for (let skip = 2; skip > 0; skip -= 1) {
+ match (next(frame)) {
+ case let next: stackframe =>
+ frame = next;
+ case void => halt();
+ };
+ };
+
+ backtrace(&self, frame);
+ halt();
+};
+
+fn halt() never = {
+ rt::kill(rt::getpid(), rt::SIGABRT): void;
+ for (true) void;
+};
diff --git a/debug/altstack.s b/debug/altstack.s
@@ -0,0 +1,5 @@
+.bss
+.globl debug.altstack
+.balign 16
+debug.altstack:
+ .balign 16384
diff --git a/debug/backtrace.ha b/debug/backtrace.ha
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use bufio;
+use debug::dwarf;
+use debug::image;
+use fmt;
+use format::elf;
+use fs;
+use io;
+use os;
+use time;
+
+def MAX_FRAMES_TOP: size = 16z;
+def MAX_FRAMES_BOTTOM: size = 16z;
+def MAX_FRAMES: size = MAX_FRAMES_TOP + MAX_FRAMES_BOTTOM;
+
+fn backtrace(self: *image::image, frame: stackframe) void = {
+ let orig = frame;
+ let nframe = 1z;
+ for (true; nframe += 1) {
+ match (next(frame)) {
+ case let next: stackframe =>
+ frame = next;
+ case void =>
+ break;
+ };
+ };
+ frame = orig;
+
+ const st = match (os::fstat(self.fd)) {
+ case let st: fs::filestat =>
+ yield st;
+ case fs::error =>
+ yield fs::filestat { mask = 0, ... };
+ };
+
+ static let seen: [MAX_FRAMES]uintptr = [0: uintptr...];
+ let seen = seen[..0];
+
+ for (let i = 0z; i < nframe; i += 1) {
+ if (i < MAX_FRAMES_TOP || i > nframe - MAX_FRAMES_BOTTOM) {
+ printframe(self, &seen, &st, frame);
+ };
+ if (i == MAX_FRAMES_TOP && nframe > MAX_FRAMES) {
+ fmt::errorfln("\t({} additional frames omitted)",
+ nframe - MAX_FRAMES): void;
+ };
+
+ match (next(frame)) {
+ case let next: stackframe =>
+ frame = next;
+ case void =>
+ break;
+ };
+ };
+};
+
+fn printframe(
+ self: *image::image,
+ seen: *[]uintptr,
+ imgstat: *fs::filestat,
+ frame: stackframe,
+) void = {
+ const pc = frame_pc(frame);
+
+ const sym = match (symbol_byaddr(self, pc)) {
+ case let sym: elf::sym64 =>
+ yield sym;
+ case =>
+ fmt::errorfln("(unknown) [0x{:x}]", pc): void;
+ return;
+ };
+ const name = match (symbol_name(self, &sym)) {
+ case let name: const str =>
+ yield name;
+ case =>
+ fmt::errorfln("(unknown) [0x{:x}]", pc): void;
+ return;
+ };
+
+ // Look for DWARF line numbers, if possible
+ const (path, line, col) = match (dwarf::addr_to_line(self, pc)) {
+ case (void | io::error) =>
+ // No line number available, print what we've got
+ fmt::errorfln("{}+0x{:x} [0x{:x}]", symname_to_ident(name),
+ pc - sym.st_value: uintptr, pc): void;
+ return;
+ case let tuple: (const str, uint, uint) =>
+ yield tuple;
+ };
+
+ const file = match (os::open(path)) {
+ case fs::error =>
+ printframe_with_symbol(&sym, name, path, (line, col), pc);
+ return;
+ case let file: io::file =>
+ yield file;
+ };
+ defer io::close(file)!;
+
+ static let linebuf: [1024]u8 = [0...];
+ const scan = bufio::newscanner_static(file, linebuf);
+
+ let context = "";
+ for (let i = 0u; i < line; i += 1) {
+ match (bufio::scan_line(&scan)) {
+ case let s: const str =>
+ context = s;
+ case io::EOF =>
+ printframe_with_symbol(&sym, name, path, (line, col), pc);
+ return;
+ };
+ };
+
+ fmt::errorf("{}:{}:{} {}+0x{:x} [0x{:x}]",
+ path, line, col, symname_to_ident(name),
+ pc - sym.st_value: uintptr, pc): void;
+
+ // Skip context on frames we've already printed
+ for (let i = 0z; i < len(seen); i += 1) {
+ if (seen[i] == pc) {
+ fmt::errorfln(" (already shown)"): void;
+ return;
+ };
+ };
+ static append(seen, pc);
+ fmt::errorln(): void;
+
+ if (imgstat.mask & fs::stat_mask::MTIME != 0) {
+ match (os::fstat(file)) {
+ case let st: fs::filestat =>
+ if (st.mask & fs::stat_mask::MTIME == 0) yield;
+ if (time::compare(st.mtime, imgstat.mtime) == 1) {
+ fmt::errorln("* Warning: file was modified after executable was built"): void;
+ };
+ case => void;
+ };
+ };
+
+ fmt::errorfln("| {}", context): void;
+
+ if (col != 0) {
+ fmt::errorf(" "): void;
+ for (let i = 1u; i < col - 1; i += 1) {
+ fmt::errorf(" "): void;
+ };
+ fmt::errorf("^"): void;
+ };
+
+ fmt::errorln(): void;
+
+};
+
+fn printframe_with_symbol(
+ sym: *elf::sym64,
+ name: str,
+ path: str,
+ loc: (uint, uint),
+ pc: uintptr,
+) void = {
+ fmt::errorfln("{}:{}:{} {}+0x{:x} [0x{:x}]",
+ path, loc.0, loc.1, symname_to_ident(name),
+ pc - sym.st_value: uintptr, pc): void;
+ fmt::errorln(): void;
+};
diff --git a/debug/constants.ha b/debug/constants.ha
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+// Maximum length of a symbol name, including nul terminator, with the
+// statically allocated debug data structures.
+def MAX_SYMNAME: size = 1024;
diff --git a/debug/fault.ha b/debug/fault.ha
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use debug::image;
+use rt;
+use unix::signal;
+use unix::signal::{sig};
+use fmt;
+
+// altstack.s
+let altstack: [ALTSTACK_SIZE]uintptr;
+
+// 16 KiB, sync with altstack.s
+def ALTSTACK_SIZE: size = 16384;
+
+@init fn init_overflow() void = {
+ rt::sigaltstack(&rt::stack_t {
+ ss_sp = &altstack,
+ ss_flags = 0,
+ ss_size = ALTSTACK_SIZE,
+ }, null)!;
+ signal::handle(sig::SEGV, &signal_handler, signal::flag::ONSTACK);
+ signal::handle(sig::FPE, &signal_handler, signal::flag::ONSTACK);
+ signal::handle(sig::BUS, &signal_handler, signal::flag::ONSTACK);
+ signal::handle(sig::ILL, &signal_handler, signal::flag::ONSTACK);
+};
+
+fn signal_handler(sig: sig, info: *signal::siginfo, uctx: *opaque) void = {
+ signal::resetall();
+
+ const ip = uctx_ip(uctx);
+ const sp = uctx_sp(uctx);
+ const addr = info.addr: uintptr;
+ let frame = uctx_frame(uctx);
+
+ switch (sig) {
+ case sig::SEGV =>
+ const is_overflow = addr & ~0xFFFF == sp & ~0xFFFF;
+ fmt::errorfln("{} ({}) at address 0x{:x}",
+ if (is_overflow) "Stack overflow"
+ else "Illegal pointer access",
+ errcode_str(sig, info.code), addr): void;
+ case sig::BUS =>
+ fmt::errorfln("Bus error ({}) at address 0x{:x}",
+ errcode_str(sig, info.code), addr): void;
+ case sig::FPE =>
+ // addr is the location of the faulting instruction, construct
+ // an additional synethetic stack frame
+ let copy = frame; frame = mkframe(©, addr);
+ fmt::errorfln("Arithmetic exception ({})",
+ errcode_str(sig, info.code)): void;
+ case => void;
+ };
+
+ const self = match (image::self()) {
+ case let img: image::image =>
+ yield img;
+ case => halt();
+ };
+ defer image::close(&self);
+
+ backtrace(&self, frame);
+
+ halt();
+};
+
+fn errcode_str(sig: sig, code: signal::code) const str = {
+ // Note: this only handles a few cases by design
+ // It also is limited only to error codes defined by POSIX
+ switch (sig) {
+ case sig::ILL =>
+ switch (code) {
+ case signal::code::ILLOPC => return "illegal opcode";
+ case signal::code::ILLOPN => return "illegal operand";
+ case signal::code::ILLADR => return "illegal addressing mode";
+ case signal::code::ILLTRP => return "illegal trap";
+ case signal::code::PRVOPC => return "privileged opcode";
+ case signal::code::PRVREG => return "privileged register";
+ case signal::code::COPROC => return "coprocessor error";
+ case signal::code::BADSTK => return "internal stack error";
+ case => void;
+ };
+ case sig::FPE =>
+ switch (code) {
+ case signal::code::INTDIV => return "integer divide by zero";
+ case signal::code::INTOVF => return "integer overflow";
+ case signal::code::FLTDIV => return "floating-point divide by zero";
+ case signal::code::FLTOVF => return "floating-point overflow";
+ case signal::code::FLTUND => return "floating-point underflow";
+ case signal::code::FLTRES => return "floating-point inexact result";
+ case signal::code::FLTINV => return "invalid floating-point operation";
+ case signal::code::FLTSUB => return "subscript out of range";
+ case => void;
+ };
+ case sig::SEGV =>
+ switch (code) {
+ case signal::code::MAPERR => return "address not mapped to object";
+ case signal::code::ACCERR => return "invalid permissions for mapped object";
+ case => void;
+ };
+ case sig::BUS =>
+ switch (code) {
+ case signal::code::ADRALN => return "invalid address alignment";
+ case signal::code::ADRERR => return "nonexistent physical address";
+ case signal::code::OBJERR => return "object-specific hardware error";
+ case => void;
+ };
+ case => void;
+ };
+
+ return "unknown reason";
+};
diff --git a/debug/ident.ha b/debug/ident.ha
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use encoding::utf8;
+use strings;
+
+// Converts a symbol name to a Hare identifier. The return value is statically
+// allocated.
+export fn symname_to_ident(name: str) const str = {
+ static let buf: [MAX_SYMNAME * 2]u8 = [0...];
+ let slice = buf[..0];
+
+ const iter = strings::iter(name);
+ for (true) {
+ const rn = match (strings::next(&iter)) {
+ case let rn: rune =>
+ yield rn;
+ case => break;
+ };
+ if (rn == '.') {
+ static append(slice, ':');
+ static append(slice, ':');
+ } else {
+ static append(slice, utf8::encoderune(rn)...);
+ };
+ };
+
+ return strings::fromutf8(slice)!;
+};
diff --git a/debug/symbols.ha b/debug/symbols.ha
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use bytes;
+use debug::image;
+use io;
+use format::elf;
+use strings;
+use types::c;
+
+// Returns symbol information by name.
+export fn symbol_byname(
+ image: *image::image,
+ name: str,
+) (elf::sym64 | io::error | void) = {
+ const strtab = match (image::section_byname(image, ".strtab")) {
+ case let sec: *elf::section64 =>
+ yield sec;
+ case null =>
+ return;
+ };
+ const symtab = match (image::section_byname(image, ".symtab")) {
+ case let sec: *elf::section64 =>
+ yield sec;
+ case null =>
+ return;
+ };
+
+ const st_name = scan_strtab(image, strtab, name) as u64;
+ const data = image::section_data(image, symtab);
+ const entsz = symtab.sh_entsize: size;
+ const nsym = len(data) / entsz;
+ for (let i = 0z; i < nsym; i += 1) {
+ const sym = &data[i * entsz]: *elf::sym64;
+ if (sym.st_name == st_name) {
+ return *sym;
+ };
+ };
+};
+
+// Returns the symbol that occupies a given address.
+export fn symbol_byaddr(
+ image: *image::image,
+ addr: uintptr,
+) (elf::sym64 | io::error | void) = {
+ const addr = addr: u64;
+ const symtab = match (image::section_byname(image, ".symtab")) {
+ case let sec: *elf::section64 =>
+ yield sec;
+ case null =>
+ return;
+ };
+
+ const data = image::section_data(image, symtab);
+ const entsz = symtab.sh_entsize: size;
+ const nsym = len(data) / entsz;
+ for (let i = 0z; i < nsym; i += 1) {
+ const sym = &data[i * entsz]: *elf::sym64;
+ const min = sym.st_value;
+ const max = sym.st_value + sym.st_size;
+ if (min <= addr && addr < max) {
+ return *sym;
+ };
+ };
+};
+
+// Returns the name of the given symbol, or void if the executable was stripped.
+export fn symbol_name(
+ image: *image::image,
+ sym: *elf::sym64,
+) (const str | io::error | void) = {
+ const strtab = match (image::section_byname(image, ".strtab")) {
+ case let sec: *elf::section64 =>
+ yield sec;
+ case null =>
+ return;
+ };
+ const data = image::section_data(image, strtab);
+ return c::tostr(&data[sym.st_name]: *const c::char)!;
+};
+
+// Scans a string table for a given name and returns the index of that name.
+fn scan_strtab(
+ image: *image::image,
+ strtab: *elf::section64,
+ name: str,
+) (u64 | io::error | void) = {
+ let buf: [4096]u8 = [0...];
+ let namebuf: [MAX_SYMNAME]u8 = [0...];
+
+ // Prepare a nul-terminated byte slice of the name
+ let name = strings::toutf8(name);
+ namebuf[..len(name)] = name;
+ namebuf[len(name)] = 0;
+ name = namebuf[..len(name)+1];
+
+ const data = image::section_data(image, strtab);
+ match (bytes::index(data, name)) {
+ case let z: size =>
+ return z: u64;
+ case void => yield;
+ };
+};