aboutsummaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/hsdbg_trace/hsdbg_trace.c68
-rw-r--r--ext/hsdbg_trace/hsdbg_trace.h51
2 files changed, 119 insertions, 0 deletions
diff --git a/ext/hsdbg_trace/hsdbg_trace.c b/ext/hsdbg_trace/hsdbg_trace.c
new file mode 100644
index 0000000..cf87ffc
--- /dev/null
+++ b/ext/hsdbg_trace/hsdbg_trace.c
@@ -0,0 +1,68 @@
+#include "hsdbg_trace.h"
+
+#include <pthread.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+// the hooks and their helpers must never instrument themselves, or every record
+// would recurse into another record
+#define NO_INSTR __attribute__((no_instrument_function))
+
+struct HsdbgTraceRecord hsdbg_trace_records[HSDBG_TRACE_CAPACITY];
+const uint64_t hsdbg_trace_capacity = HSDBG_TRACE_CAPACITY;
+uint64_t hsdbg_trace_head = 0;
+
+// slots are hedged out with a private atomic so concurrent threads never claim
+// the same one; hsdbg_trace_head is the separate, reader-facing high-water mark
+static _Atomic uint64_t g_next = 0;
+
+static NO_INSTR uint64_t now_ns(void)
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
+}
+
+static NO_INSTR uint64_t current_thread(void)
+{
+ uint64_t id = 0;
+ pthread_threadid_np(NULL, &id);
+ return id;
+}
+
+static NO_INSTR void record(uint32_t kind, void* function)
+{
+ const uint64_t slot = atomic_fetch_add_explicit(&g_next, 1, memory_order_relaxed);
+
+ struct HsdbgTraceRecord* entry = &hsdbg_trace_records[slot % HSDBG_TRACE_CAPACITY];
+ entry->timestamp_ns = now_ns();
+ entry->function = (uint64_t)(uintptr_t)function;
+ entry->thread_id = current_thread();
+ entry->kind = kind;
+ entry->reserved = 0;
+
+ // publish only once the record is complete, so a reader that sees the new
+ // head always finds fully written fields
+ atomic_store_explicit((_Atomic uint64_t*)&hsdbg_trace_head, slot + 1, memory_order_release);
+}
+
+NO_INSTR void __cyg_profile_func_enter(void* this_fn, void* call_site)
+{
+ (void)call_site;
+ record(HSDBG_TRACE_ENTER, this_fn);
+}
+
+NO_INSTR void __cyg_profile_func_exit(void* this_fn, void* call_site)
+{
+ (void)call_site;
+ record(HSDBG_TRACE_EXIT, this_fn);
+}
+
+// opt-in sanity check when running the target on its own, outside hsdbg
+__attribute__((destructor)) NO_INSTR static void hsdbg_trace_report(void)
+{
+ if (getenv("HSDBG_TRACE_DEBUG"))
+ fprintf(stderr, "[hsdbg_trace] %llu records written\n", (unsigned long long)hsdbg_trace_head);
+}
diff --git a/ext/hsdbg_trace/hsdbg_trace.h b/ext/hsdbg_trace/hsdbg_trace.h
new file mode 100644
index 0000000..a819f9c
--- /dev/null
+++ b/ext/hsdbg_trace/hsdbg_trace.h
@@ -0,0 +1,51 @@
+// hsdbg function-trace runtime.
+//
+// Build your program with -finstrument-functions and link this in; the compiler
+// then calls the enter/exit hooks below on every function, and they append a
+// timestamped record to a fixed ring buffer. hsdbg reads that buffer straight
+// out of the live process and turns it into the flame-chart timeline, so no
+// breakpoints and no hand-picking of functions are needed.
+//
+// clang -finstrument-functions -g your_prog.c hsdbg_trace.c -o your_prog
+//
+// The record layout and the three exported symbols are the contract hsdbg reads
+// against; keep them in sync with the reader on the debugger side.
+#pragma once
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ enum
+ {
+ HSDBG_TRACE_ENTER = 0,
+ HSDBG_TRACE_EXIT = 1,
+
+ // 1,048,576 records ~= 32 MB; plenty for a short profiled run
+ HSDBG_TRACE_CAPACITY = 1u << 20,
+ };
+
+ // 32 bytes, naturally aligned so hsdbg can read the array with one memory
+ // fetch and cast without worrying about padding differences
+ struct HsdbgTraceRecord
+ {
+ uint64_t timestamp_ns; // CLOCK_MONOTONIC nanoseconds
+ uint64_t function; // address of the entered/exited function
+ uint64_t thread_id; // pthread_threadid_np of the running thread
+ uint32_t kind; // HSDBG_TRACE_ENTER or HSDBG_TRACE_EXIT
+ uint32_t reserved;
+ };
+
+ // the ring buffer and how far it has been filled. hsdbg reads hsdbg_trace_head
+ // first, then the records below it; head is published only after a record's
+ // fields are fully written
+ extern struct HsdbgTraceRecord hsdbg_trace_records[HSDBG_TRACE_CAPACITY];
+ extern const uint64_t hsdbg_trace_capacity;
+ extern uint64_t hsdbg_trace_head; // total records ever written (monotonic)
+
+#ifdef __cplusplus
+}
+#endif