commit ed9142ac20ea3f6bed3bc6d6f78acca6d82f7ec3
parent 87a97835e87f0a5dd3dfa330155a042a3cadef36
Author: Drew DeVault <sir@cmpwn.com>
Date: Wed, 1 Jan 2025 20:00:57 +0100
debug: add stack trace storage solution
This provides a means of storing back traces and associating them with a
unique 64-bit ID, which can be used to annotate data structures with
backtraces -- say, for example, malloc metadata.
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
5 files changed, 69 insertions(+), 4 deletions(-)
diff --git a/debug/+aarch64/walk.ha b/debug/+aarch64/walk.ha
@@ -32,7 +32,7 @@ export fn next(frame: stackframe) (stackframe | done) = {
export fn frame_pc(frame: stackframe) uintptr = frame.lr;
// Implementation detail, constructs a synthetic stack frame.
-fn mkframe(next: *stackframe, ip: uintptr) stackframe = {
+fn mkframe(next: nullable *stackframe, ip: uintptr) stackframe = {
return stackframe {
fp = next,
lr = ip,
diff --git a/debug/+riscv64/walk.ha b/debug/+riscv64/walk.ha
@@ -32,7 +32,7 @@ export fn next(frame: stackframe) (stackframe | done) = {
export fn frame_pc(frame: stackframe) uintptr = frame.ra;
// Implementation detail, constructs a synthetic stack frame.
-fn mkframe(next: *stackframe, ip: uintptr) stackframe = {
+fn mkframe(next: nullable *stackframe, ip: uintptr) stackframe = {
return stackframe {
fp = next,
ra = ip,
diff --git a/debug/+x86_64/walk.ha b/debug/+x86_64/walk.ha
@@ -32,7 +32,7 @@ export fn next(frame: stackframe) (stackframe | done) = {
export fn frame_pc(frame: stackframe) uintptr = frame.ip;
// Implementation detail, constructs a synthetic stack frame.
-fn mkframe(next: *stackframe, ip: uintptr) stackframe = {
+fn mkframe(next: nullable *stackframe, ip: uintptr) stackframe = {
return stackframe {
fp = next,
ip = ip,
diff --git a/debug/backtrace.ha b/debug/backtrace.ha
@@ -15,7 +15,8 @@ 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 = {
+// Prints a backtrace to the standard error.
+export fn backtrace(self: *image::image, frame: stackframe) void = {
let orig = frame;
let nframe = 1z;
for (true; nframe += 1) {
diff --git a/debug/traces.ha b/debug/traces.ha
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use hash;
+use hash::fnv;
+use rt;
+
+def TRACE_BUCKETS = 65535;
+
+let traces: [TRACE_BUCKETS][]trace = [[]...];
+
+export type trace = struct {
+ id: u64,
+ frames: *stackframe,
+};
+
+// Retrives a stack trace by its ID.
+export fn trace_by_id(id: u64) (stackframe | void) = {
+ let bucket = &traces[id % TRACE_BUCKETS];
+ for (let trace &.. bucket) {
+ if (trace.id == id) {
+ return *trace.frames;
+ };
+ };
+};
+
+// Stores a stack trace and returns its ID. Stored stack traces are hashed and
+// de-duplicated in a global stack list.
+export fn trace_store(frame: stackframe) u64 = {
+ static let pc: []uintptr = [];
+ pc = pc[..0];
+
+ const hash = fnv::fnv64a();
+ for (true) {
+ hash::write(&hash, &frame_pc(frame): *[size(uintptr)]u8);
+ append(pc, frame_pc(frame));
+
+ match (next(frame)) {
+ case let next: stackframe =>
+ frame = next;
+ case done => break;
+ };
+ };
+
+ const id = fnv::sum64(&hash);
+ let bucket = &traces[id % TRACE_BUCKETS];
+ for (let trace &.. bucket) {
+ if (trace.id == id) {
+ return id;
+ };
+ };
+
+ let frames: []stackframe = alloc([stackframe { ... }...], len(pc) + 1);
+ for (let i = 0z; i < len(pc); i += 1) {
+ frames[i] = mkframe(&frames[i + 1], pc[i]);
+ };
+
+ append(bucket, trace {
+ id = id,
+ frames = &frames[0],
+ });
+
+ return id;
+};