diff options
Diffstat (limited to 'ext')
| -rw-r--r-- | ext/hsdbg_trace/hsdbg_trace.c | 68 | ||||
| -rw-r--r-- | ext/hsdbg_trace/hsdbg_trace.h | 51 |
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 |
