ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
symbols.c File Reference

🔍 Symbol resolution cache: llvm-symbolizer/addr2line wrapper with hashtable-backed caching More...

Go to the source code of this file.

Data Structures

struct  symbol_entry_t
 Symbol cache entry structure for address-to-symbol mapping. More...
 

Macros

#define LLVM_SYMBOLIZER_BIN   "llvm-symbolizer"
 
#define ADDR2LINE_BIN   "addr2line"
 
#define NULL_SENTINEL   "[NULL]"
 

Enumerations

enum  symbolizer_type_t { SYMBOLIZER_NONE = 0 , SYMBOLIZER_LLVM = 1 , SYMBOLIZER_ADDR2LINE = 2 }
 

Functions

asciichat_error_t symbol_cache_init (void)
 
void symbol_cache_destroy (void)
 
const char * symbol_cache_lookup (void *addr)
 
bool symbol_cache_insert (void *addr, const char *symbol)
 
void symbol_cache_get_stats (uint64_t *hits_out, uint64_t *misses_out, size_t *entries_out)
 
void symbol_cache_print_stats (void)
 
char ** symbol_cache_resolve_batch (void *const *buffer, int size)
 
void symbol_cache_free_symbols (char **symbols)
 

Detailed Description

🔍 Symbol resolution cache: llvm-symbolizer/addr2line wrapper with hashtable-backed caching

Definition in file symbols.c.

Macro Definition Documentation

◆ ADDR2LINE_BIN

#define ADDR2LINE_BIN   "addr2line"

Definition at line 15 of file symbols.c.

◆ LLVM_SYMBOLIZER_BIN

#define LLVM_SYMBOLIZER_BIN   "llvm-symbolizer"

Definition at line 14 of file symbols.c.

◆ NULL_SENTINEL

#define NULL_SENTINEL   "[NULL]"

Definition at line 43 of file symbols.c.

Enumeration Type Documentation

◆ symbolizer_type_t

Enumerator
SYMBOLIZER_NONE 
SYMBOLIZER_LLVM 
SYMBOLIZER_ADDR2LINE 

Definition at line 49 of file symbols.c.

49 {
50 SYMBOLIZER_NONE = 0, // No symbolizer available, use raw addresses
51 SYMBOLIZER_LLVM = 1, // llvm-symbolizer (preferred on all platforms)
52 SYMBOLIZER_ADDR2LINE = 2, // addr2line (fallback)
symbolizer_type_t
Definition symbols.c:49
@ SYMBOLIZER_NONE
Definition symbols.c:50
@ SYMBOLIZER_LLVM
Definition symbols.c:51
@ SYMBOLIZER_ADDR2LINE
Definition symbols.c:52

Function Documentation

◆ symbol_cache_destroy()

void symbol_cache_destroy ( void  )

Definition at line 292 of file symbols.c.

292 {
293 if (!atomic_load(&g_symbol_cache_initialized)) {
294 return;
295 }
296
297 // Mark as uninitialized FIRST to prevent new inserts during cleanup
298 atomic_store(&g_symbol_cache_initialized, false);
299
300 // Acquire write lock to prevent any concurrent operations
301 rwlock_wrlock(&g_symbol_cache_lock);
302
303 // Count entries before freeing for debugging
304 size_t entry_count = HASH_COUNT(g_symbol_cache);
305
306 // Free all symbol entries using HASH_ITER
307 symbol_entry_t *entry = NULL, *tmp = NULL;
308 size_t freed_count = 0;
309 HASH_ITER(hh, g_symbol_cache, entry, tmp) {
310 if (entry) {
311 HASH_DEL(g_symbol_cache, entry);
312 if (entry->symbol) {
313 free(entry->symbol);
314 }
315 free(entry);
316 freed_count++;
317 }
318 }
319
320 // Release lock and destroy rwlock
321 rwlock_wrunlock(&g_symbol_cache_lock);
322 rwlock_destroy(&g_symbol_cache_lock);
323
324 g_symbol_cache = NULL;
325
326 log_dev("Symbol cache cleaned up: %zu entries counted, %zu entries freed (hits=%llu, misses=%llu)", entry_count,
327 freed_count, (unsigned long long)atomic_load(&g_cache_hits),
328 (unsigned long long)atomic_load(&g_cache_misses));
329}
Symbol cache entry structure for address-to-symbol mapping.
Definition symbols.c:105
char * symbol
Resolved symbol string (allocated, owned by cache)
Definition symbols.c:109

References symbol_entry_t::symbol.

Referenced by server_main().

◆ symbol_cache_free_symbols()

void symbol_cache_free_symbols ( char **  symbols)

Definition at line 921 of file symbols.c.

921 {
922 if (!symbols) {
923 return;
924 }
925
926 // The array is NULL-terminated (allocated with size+1, with result[size] = NULL)
927 // The terminator is a SINGLE NULL at index 'size'
928 // Failed allocations use the NULL_SENTINEL string "[NULL]" instead of NULL,
929 // so there are no NULL entries in the middle - only at the terminator
930 // This makes iteration safe: we can iterate until we find the first NULL (the terminator)
931
932 // Iterate through entries, freeing all non-NULL entries until we hit the NULL terminator
933 for (int i = 0; i < 64; i++) { // Reasonable limit to prevent infinite loop
934 if (symbols[i] == NULL) {
935 // Found NULL - this is the terminator, stop here
936 break;
937 }
938
939 // Found a non-NULL entry - check if it's the sentinel string
940 // Both regular strings and sentinel strings are allocated (with strdup),
941 // so we free them all
942 SAFE_FREE(symbols[i]);
943 symbols[i] = NULL; // Clear pointer after freeing
944 }
945
946 // Free the array itself
947 SAFE_FREE(symbols);
948}

◆ symbol_cache_get_stats()

void symbol_cache_get_stats ( uint64_t *  hits_out,
uint64_t *  misses_out,
size_t *  entries_out 
)

Definition at line 412 of file symbols.c.

412 {
413 if (hits_out) {
414 *hits_out = atomic_load(&g_cache_hits);
415 }
416 if (misses_out) {
417 *misses_out = atomic_load(&g_cache_misses);
418 }
419 if (entries_out) {
420 rwlock_rdlock(&g_symbol_cache_lock);
421 *entries_out = HASH_COUNT(g_symbol_cache);
422 rwlock_rdunlock(&g_symbol_cache_lock);
423 }
424}

◆ symbol_cache_init()

asciichat_error_t symbol_cache_init ( void  )

Definition at line 264 of file symbols.c.

264 {
265 bool expected = false;
266 if (!atomic_compare_exchange_strong(&g_symbol_cache_initialized, &expected, true)) {
267 return 0; // Already initialized
268 }
269
270 // Detect which symbolizer is available (once at init)
271 expected = false;
272 if (atomic_compare_exchange_strong(&g_symbolizer_detected, &expected, true)) {
273 g_symbolizer_type = detect_symbolizer();
274 }
275
276 // Initialize rwlock for thread safety (uthash requires external locking)
277 if (rwlock_init(&g_symbol_cache_lock) != 0) {
278 atomic_store(&g_symbol_cache_initialized, false);
279 return SET_ERRNO(ERROR_THREAD, "Failed to initialize symbol cache rwlock");
280 }
281
282 // Initialize uthash head to NULL (required)
283 g_symbol_cache = NULL;
284
285 atomic_store(&g_cache_hits, 0);
286 atomic_store(&g_cache_misses, 0);
287
288 log_dev("Symbol cache initialized");
289 return 0;
290}
int rwlock_init(rwlock_t *rwlock)
Definition threading.c:63

References rwlock_init().

◆ symbol_cache_insert()

bool symbol_cache_insert ( void *  addr,
const char *  symbol 
)

Definition at line 354 of file symbols.c.

354 {
355 if (!atomic_load(&g_symbol_cache_initialized) || !addr || !symbol) {
356 return false;
357 }
358
359 // Acquire write lock to make the entire operation atomic
360 rwlock_wrlock(&g_symbol_cache_lock);
361
362 // Double-check cache is still initialized after acquiring lock
363 // (cleanup might have marked it uninitialized between our check and lock acquisition)
364 if (!atomic_load(&g_symbol_cache_initialized)) {
365 rwlock_wrunlock(&g_symbol_cache_lock);
366 return false;
367 }
368
369 // Check if entry already exists
370 symbol_entry_t *existing = NULL;
371 HASH_FIND_PTR(g_symbol_cache, &addr, existing);
372
373 if (existing) {
374 // Entry exists - update symbol if different
375 if (existing->symbol && strcmp(existing->symbol, symbol) != 0) {
376 // Free old symbol and allocate new one
377 free(existing->symbol);
378 existing->symbol = platform_strdup(symbol);
379 if (!existing->symbol) {
380 rwlock_wrunlock(&g_symbol_cache_lock);
381 return false;
382 }
383 }
384 rwlock_wrunlock(&g_symbol_cache_lock);
385 return true;
386 }
387
388 // Create new entry
389 symbol_entry_t *entry = (symbol_entry_t *)malloc(sizeof(symbol_entry_t));
390 if (!entry) {
391 rwlock_wrunlock(&g_symbol_cache_lock);
392 return false;
393 }
394
395 entry->addr = addr;
396 entry->symbol = platform_strdup(symbol);
397 if (!entry->symbol) {
398 free(entry);
399 rwlock_wrunlock(&g_symbol_cache_lock);
400 return false;
401 }
402
403 // Add to hash table
404 HASH_ADD_PTR(g_symbol_cache, addr, entry);
405
406 // Release lock
407 rwlock_wrunlock(&g_symbol_cache_lock);
408
409 return true;
410}
char * platform_strdup(const char *s)
void * addr
Memory address key (used for hashtable lookup)
Definition symbols.c:107

References symbol_entry_t::addr, platform_strdup(), and symbol_entry_t::symbol.

Referenced by symbol_cache_resolve_batch().

◆ symbol_cache_lookup()

const char * symbol_cache_lookup ( void *  addr)

Definition at line 331 of file symbols.c.

331 {
332 if (!atomic_load(&g_symbol_cache_initialized) || !addr) {
333 return NULL;
334 }
335
336 rwlock_rdlock(&g_symbol_cache_lock);
337
338 symbol_entry_t *entry = NULL;
339 HASH_FIND_PTR(g_symbol_cache, &addr, entry);
340
341 const char *result = NULL;
342 if (entry) {
343 // Copy string while holding lock to prevent use-after-free
344 result = platform_strdup(entry->symbol);
345 atomic_fetch_add(&g_cache_hits, 1);
346 } else {
347 atomic_fetch_add(&g_cache_misses, 1);
348 }
349
350 rwlock_rdunlock(&g_symbol_cache_lock);
351 return result;
352}

References platform_strdup(), and symbol_entry_t::symbol.

Referenced by symbol_cache_resolve_batch().

◆ symbol_cache_print_stats()

void symbol_cache_print_stats ( void  )

Definition at line 426 of file symbols.c.

426 {
427 uint64_t hits = atomic_load(&g_cache_hits);
428 uint64_t misses = atomic_load(&g_cache_misses);
429
430 rwlock_rdlock(&g_symbol_cache_lock);
431 size_t entries = HASH_COUNT(g_symbol_cache);
432 rwlock_rdunlock(&g_symbol_cache_lock);
433
434 uint64_t total = hits + misses;
435 double hit_rate = total > 0 ? (100.0 * (double)hits / (double)total) : 0.0;
436
437 log_debug("Symbol Cache Stats: %zu entries, %llu hits, %llu misses (%.1f%% hit rate)", entries,
438 (unsigned long long)hits, (unsigned long long)misses, hit_rate);
439}

◆ symbol_cache_resolve_batch()

char ** symbol_cache_resolve_batch ( void *const *  buffer,
int  size 
)

Definition at line 804 of file symbols.c.

804 {
805 if (size <= 0 || !buffer) {
806 log_error("Invalid parameters: buffer=%p, size=%d", (void *)buffer, size);
807 return NULL;
808 }
809
810 // DO NOT auto-initialize here - causes circular dependency during lock_debug_init()
811 // The cache must be initialized explicitly by platform_init() before use
812 if (!atomic_load(&g_symbol_cache_initialized)) {
813 // Cache not initialized - fall back to uncached resolution
814 // This happens during early initialization before platform_init() completes
815 char **result = run_llvm_symbolizer_batch(buffer, size);
816 if (!result) {
817 result = run_addr2line_batch(buffer, size);
818 }
819 return result;
820 }
821
822 // Allocate result array (size + 1 for NULL terminator)
823 // CALLOC zeros the memory, so result[size] is already NULL
824 char **result = SAFE_CALLOC((size_t)(size + 1), sizeof(char *), char **);
825 if (!result) {
826 return NULL;
827 }
828
829 // Ensure NULL terminator is explicitly set (CALLOC already did this, but be explicit)
830 result[size] = NULL;
831
832 // First pass: check cache for all addresses
833 int uncached_count = 0;
834 void *uncached_addrs[size];
835 int uncached_indices[size];
836
837 for (int i = 0; i < size; i++) {
838 const char *cached = symbol_cache_lookup(buffer[i]);
839 if (cached) {
840 // Cache hit - symbol_cache_lookup already duplicated the string
841 result[i] = (char *)cached;
842 } else {
843 // Cache miss - track for batch resolution
844 uncached_addrs[uncached_count] = buffer[i];
845 uncached_indices[uncached_count] = i;
846 uncached_count++;
847 }
848 }
849
850 // Second pass: resolve uncached addresses with selected symbolizer
851 if (uncached_count > 0) {
852 char **resolved = NULL;
853
854 // Use the detected symbolizer type
855 switch (g_symbolizer_type) {
856 case SYMBOLIZER_LLVM:
857 resolved = run_llvm_symbolizer_batch(uncached_addrs, uncached_count);
858 break;
860 resolved = run_addr2line_batch(uncached_addrs, uncached_count);
861 break;
862 case SYMBOLIZER_NONE:
863 default:
864 // No symbolizer available - will fall through to raw address handling
865 resolved = NULL;
866 break;
867 }
868
869 if (resolved) {
870 for (int i = 0; i < uncached_count; i++) {
871 int orig_idx = uncached_indices[i];
872 if (resolved[i]) {
873 result[orig_idx] = platform_strdup(resolved[i]);
874 // If strdup failed, use sentinel string instead of NULL
875 if (!result[orig_idx]) {
876 log_error("Failed to duplicate string for result[%d]", orig_idx);
877 result[orig_idx] = platform_strdup(NULL_SENTINEL);
878 }
879 // Only insert into cache if strdup succeeded (and it's not the sentinel)
880 if (result[orig_idx] && strcmp(result[orig_idx], NULL_SENTINEL) != 0) {
881 if (!symbol_cache_insert(uncached_addrs[i], resolved[i])) {
882 log_error("Failed to insert symbol into cache for result[%d]", orig_idx);
883 }
884 }
885 SAFE_FREE(resolved[i]);
886 } else {
887 // resolved[i] is NULL - use sentinel string
888 if (!result[orig_idx]) {
889 result[orig_idx] = platform_strdup(NULL_SENTINEL);
890 }
891 }
892 if (!result[orig_idx]) {
893 log_error("Failed to allocate memory for result[%d]", orig_idx);
894 }
895 }
896
897 SAFE_FREE(resolved);
898
899 } else {
900 // addr2line failed - fill uncached entries with raw addresses or sentinel
901 for (int i = 0; i < uncached_count; i++) {
902 int orig_idx = uncached_indices[i];
903 if (!result[orig_idx]) {
904 result[orig_idx] = SAFE_MALLOC(32, char *);
905 if (result[orig_idx]) {
906 SAFE_SNPRINTF(result[orig_idx], 32, "%p", uncached_addrs[i]);
907 } else {
908 result[orig_idx] = platform_strdup(NULL_SENTINEL);
909 }
910 }
911 if (!result[orig_idx]) {
912 log_error("Failed to allocate memory for result[%d]", orig_idx);
913 }
914 }
915 }
916 }
917
918 return result;
919}
#define NULL_SENTINEL
Definition symbols.c:43
bool symbol_cache_insert(void *addr, const char *symbol)
Definition symbols.c:354
const char * symbol_cache_lookup(void *addr)
Definition symbols.c:331

References NULL_SENTINEL, platform_strdup(), symbol_cache_insert(), symbol_cache_lookup(), SYMBOLIZER_ADDR2LINE, SYMBOLIZER_LLVM, and SYMBOLIZER_NONE.