ohmu
·
2026-10-15
1// DON'T CALL ANY FASTFETCH FUNCTION IN THIS FILE (to avoid recursion when -finstrument-functions is enabled)
2
3#include <stdio.h>
4#include <time.h>
5#include <stdatomic.h>
6#include <stdbool.h>
7#include <stdint.h>
8#include <stdlib.h>
9#include <string.h>
10#include <inttypes.h>
11
12#if !_WIN32
13 #include <dlfcn.h>
14 #include <pthread.h>
15 #include <unistd.h>
16#else
17 #include <windows.h>
18 #include <dbghelp.h>
19#endif
20
21#if _WIN32
22static LARGE_INTEGER freq;
23 #define NtCurrentProcess() ((HANDLE) (-1))
24#endif
25
26static struct trace_event {
27 uint64_t ts;
28 uint64_t tid;
29 void* func;
30} events[4 * 1024 * 1024]; // 4M events, 96 MiB memory usage
31static _Atomic uint32_t event_count;
32
33[[gnu::no_instrument_function, gnu::always_inline]] static inline uint64_t get_time_us() {
34#if !_WIN32
35 struct timespec ts;
36 clock_gettime(CLOCK_MONOTONIC, &ts);
37 return (uint64_t) (ts.tv_sec * 1000000 + ts.tv_nsec / 1000);
38#else
39 LARGE_INTEGER now;
40 QueryPerformanceCounter(&now);
41 return (uint64_t) (now.QuadPart * 1000000 / freq.QuadPart);
42#endif
43}
44
45#if _WIN32
46[[gnu::constructor, gnu::no_instrument_function]] void trace_init() {
47 QueryPerformanceFrequency(&freq);
48}
49#endif
50
51[[gnu::destructor, gnu::no_instrument_function]] void trace_fini() {
52#if _WIN32
53 uint32_t pid = (uint32_t) GetCurrentProcessId();
54#else
55 uint32_t pid = (uint32_t) getpid();
56#endif
57
58 char fileName[64];
59 snprintf(fileName, sizeof(fileName), "trace_%d.json", pid);
60 FILE* trace_file = fopen(fileName, "wb");
61 if (!trace_file) {
62 fprintf(stderr, "Failed to open trace file %s for writing\n", fileName);
63 abort();
64 }
65
66 fputs("[\n", trace_file);
67
68#if _WIN32
69 SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME);
70 SymInitialize(NtCurrentProcess(), nullptr, TRUE);
71#endif
72
73 const char* fnName = nullptr;
74
75 uint32_t count = atomic_load_explicit(&event_count, memory_order_acquire);
76 for (uint32_t i = 0; i < count; ++i) {
77 void* fn = events[i].func;
78 uint64_t ts = events[i].ts;
79 bool is_exit = (ts >> 63) != 0;
80 ts &= ~(1ULL << 63); // clear the highest bit
81 uint64_t tid = events[i].tid;
82
83#if _WIN32
84 char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
85 PSYMBOL_INFO pSymbol = (PSYMBOL_INFO) buffer;
86 pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
87 pSymbol->MaxNameLen = MAX_SYM_NAME;
88
89 DWORD64 displacement = 0;
90 if (SymFromAddr(NtCurrentProcess(), (DWORD64) fn, &displacement, pSymbol)) {
91 fnName = pSymbol->Name;
92 }
93#else
94 char buffer[32];
95 Dl_info info;
96 if (dladdr(fn, &info) && info.dli_sname) {
97 fnName = info.dli_sname;
98 }
99#endif
100
101 else {
102 snprintf(buffer, sizeof(buffer), "%p", fn);
103 fnName = buffer;
104 }
105
106 fprintf(trace_file, "{\"name\":\"%s\",\"ph\":\"%c\",\"pid\":%" PRIu32 ",\"tid\":%" PRIu64 ",\"ts\":%" PRIu64 "}%c\n", fnName, is_exit ? 'E' : 'B', pid, tid, ts, i < count - 1 ? ',' : ' ');
107 }
108
109 fputc(']', trace_file);
110 fclose(trace_file);
111 fprintf(stderr, "Trace written to trace_%d.json with %u events. Use https://ui.perfetto.dev/ to view it.\n", pid, count);
112
113#if _WIN32
114 SymCleanup(NtCurrentProcess());
115#endif
116}
117
118[[gnu::no_instrument_function]] static void write_event(void* this_fn, bool is_exit) {
119 uint32_t idx = atomic_fetch_add_explicit(&event_count, 1, memory_order_relaxed);
120 if (__builtin_expect(idx >= sizeof(events) / sizeof(events[0]), false)) {
121 abort();
122 }
123
124 events[idx].ts = get_time_us();
125#if ENABLE_THREADS
126 #if _WIN32
127 events[idx].tid = (uint64_t) GetCurrentThreadId();
128 #else
129 events[idx].tid = (uint64_t) (uintptr_t) pthread_self();
130 #endif
131#else
132 events[idx].tid = 0;
133#endif
134 events[idx].func = this_fn;
135 if (is_exit) {
136 events[idx].ts |= (1ULL << 63); // set the highest bit to indicate function exit
137 }
138}
139
140[[gnu::no_instrument_function]] void __cyg_profile_func_enter(void* this_fn, void* call_site) {
141 (void) call_site;
142 write_event(this_fn, false);
143}
144
145[[gnu::no_instrument_function]] void __cyg_profile_func_exit(void* this_fn, void* call_site) {
146 (void) call_site;
147 write_event(this_fn, true);
148}