hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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:
Adebug/+aarch64/getfp.s | 9+++++++++
Adebug/+aarch64/walk.ha | 37+++++++++++++++++++++++++++++++++++++
Adebug/+freebsd/+aarch64/ucontext.ha | 25+++++++++++++++++++++++++
Adebug/+freebsd/+riscv64/ucontext.ha | 22++++++++++++++++++++++
Adebug/+freebsd/+x86_64/ucontext.ha | 22++++++++++++++++++++++
Adebug/+linux/+aarch64/ucontext.ha | 25+++++++++++++++++++++++++
Adebug/+linux/+riscv64/ucontext.ha | 22++++++++++++++++++++++
Adebug/+linux/+x86_64/ucontext.ha | 22++++++++++++++++++++++
Adebug/+openbsd/+aarch64/ucontext.ha | 25+++++++++++++++++++++++++
Adebug/+openbsd/+riscv64/ucontext.ha | 22++++++++++++++++++++++
Adebug/+openbsd/+x86_64/ucontext.ha | 22++++++++++++++++++++++
Adebug/+riscv64/getfp.s | 9+++++++++
Adebug/+riscv64/walk.ha | 37+++++++++++++++++++++++++++++++++++++
Adebug/+x86_64/getfp.s | 10++++++++++
Adebug/+x86_64/walk.ha | 37+++++++++++++++++++++++++++++++++++++
Adebug/README | 19+++++++++++++++++++
Adebug/abort.ha | 46++++++++++++++++++++++++++++++++++++++++++++++
Adebug/altstack.s | 5+++++
Adebug/backtrace.ha | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adebug/constants.ha | 6++++++
Adebug/fault.ha | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adebug/ident.ha | 29+++++++++++++++++++++++++++++
Adebug/symbols.ha | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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(&copy, 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; + }; +};