/* * Copyright 2018-2025 Yury Gribov * * The MIT License (MIT) * * Use of this source code is governed by MIT license that can be * found in the LICENSE.txt file. */ #ifndef _GNU_SOURCE #define _GNU_SOURCE // For RTLD_DEFAULT #endif #define HAS_DLOPEN_CALLBACK $has_dlopen_callback #define HAS_DLSYM_CALLBACK $has_dlsym_callback #define NO_DLOPEN $no_dlopen #define LAZY_LOAD $lazy_load #define THREAD_SAFE $thread_safe #include #include #include #include #include #if THREAD_SAFE #include #endif // Sanity check for ARM to avoid puzzling runtime crashes #ifdef __arm__ # if defined __thumb__ && ! defined __THUMB_INTERWORK__ # error "ARM trampolines need -mthumb-interwork to work in Thumb mode" # endif #endif #ifdef __cplusplus extern "C" { #endif #define CHECK(cond, fmt, ...) do { \ if(!(cond)) { \ fprintf(stderr, "implib-gen: $load_name: " fmt "\n", ##__VA_ARGS__); \ assert(0 && "Assertion in generated code"); \ abort(); \ } \ } while(0) static void *lib_handle; static int dlopened; #if ! NO_DLOPEN #if THREAD_SAFE // We need to consider two cases: // - different threads calling intercepted APIs in parallel // - same thread calling 2 intercepted APIs recursively // due to dlopen calling library constructors // (usually happens only under IMPLIB_EXPORT_SHIMS) // Current recursive mutex approach will deadlock // if library constructor starts and joins a new thread // which (directly or indirectly) calls another library function. // Such situations should be very rare (although chances // are higher when -DIMLIB_EXPORT_SHIMS are enabled). // // Similar issue is present in Glibc so hopefully it's // not a big deal: // http://sourceware.org/bugzilla/show_bug.cgi?id=15686 // (also google for "dlopen deadlock). static pthread_mutex_t mtx; static int rec_count; static void init_lock(void) { // We need recursive lock because dlopen will call library constructors // which may call other intercepted APIs that will call load_library again. // PTHREAD_RECURSIVE_MUTEX_INITIALIZER is not portable // so we do it hard way. pthread_mutexattr_t attr; CHECK(0 == pthread_mutexattr_init(&attr), "failed to init mutex"); CHECK(0 == pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), "failed to init mutex"); CHECK(0 == pthread_mutex_init(&mtx, &attr), "failed to init mutex"); } static int lock(void) { static pthread_once_t once = PTHREAD_ONCE_INIT; CHECK(0 == pthread_once(&once, init_lock), "failed to init lock"); CHECK(0 == pthread_mutex_lock(&mtx), "failed to lock mutex"); return 0 == __sync_fetch_and_add(&rec_count, 1); } static void unlock(void) { __sync_fetch_and_add(&rec_count, -1); CHECK(0 == pthread_mutex_unlock(&mtx), "failed to unlock mutex"); } #else static int lock(void) { return 1; } static void unlock(void) {} #endif static int load_library(void) { int publish = lock(); if (lib_handle) { unlock(); return publish; } #if HAS_DLOPEN_CALLBACK extern void *$dlopen_callback(const char *lib_name); lib_handle = $dlopen_callback("$load_name"); CHECK(lib_handle, "failed to load library '$load_name' via callback '$dlopen_callback'"); #else lib_handle = dlopen("$load_name", RTLD_LAZY | RTLD_GLOBAL); CHECK(lib_handle, "failed to load library '$load_name' via dlopen: %s", dlerror()); #endif // With (non-default) IMPLIB_EXPORT_SHIMS we may call dlopen more than once // so dlclose it if we are not the first ones if (__sync_val_compare_and_swap(&dlopened, 0, 1)) { dlclose(lib_handle); } unlock(); return publish; } // Run dtor as late as possible in case library functions are // called in other global dtors // FIXME: this may crash if one thread is calling into library // while some other thread executes exit(). It's no clear // how to fix this besides simply NOT dlclosing library at all. static void __attribute__((destructor(101))) unload_lib(void) { if (dlopened) { dlclose(lib_handle); lib_handle = 0; dlopened = 0; } } #endif #if ! NO_DLOPEN && ! LAZY_LOAD static void __attribute__((constructor(101))) load_lib(void) { load_library(); } #endif // TODO: convert to single 0-separated string static const char *const sym_names[] = { $sym_names 0 }; #define SYM_COUNT (sizeof(sym_names)/sizeof(sym_names[0]) - 1) extern void *_${lib_suffix}_tramp_table[]; // Can be sped up by manually parsing library symtab... void *_${lib_suffix}_tramp_resolve(size_t i) { assert(i < SYM_COUNT); int publish = 1; void *h = 0; #if NO_DLOPEN // Library with implementations must have already been loaded. if (lib_handle) { // User has specified loaded library h = lib_handle; } else { // User hasn't provided us the loaded library so search the global namespace. # ifndef IMPLIB_EXPORT_SHIMS // If shim symbols are hidden we should search // for first available definition of symbol in library list h = RTLD_DEFAULT; # else // Otherwise look for next available definition h = RTLD_NEXT; # endif } #else publish = load_library(); h = lib_handle; CHECK(h, "failed to resolve symbol '%s', library failed to load", sym_names[i]); #endif void *addr; #if HAS_DLSYM_CALLBACK extern void *$dlsym_callback(void *handle, const char *sym_name); addr = $dlsym_callback(h, sym_names[i]); CHECK(addr, "failed to resolve symbol '%s' via callback $dlsym_callback", sym_names[i]); #else // Dlsym is thread-safe so don't need to protect it. addr = dlsym(h, sym_names[i]); CHECK(addr, "failed to resolve symbol '%s' via dlsym: %s", sym_names[i], dlerror()); #endif if (publish) { // Use atomic to please Tsan and ensure that preceeding writes // in library ctors have been delivered before publishing address (void)__sync_val_compare_and_swap(&_${lib_suffix}_tramp_table[i], 0, addr); } return addr; } // Below APIs are not thread-safe // and it's not clear how make them such // (we can not know if some other thread is // currently executing library code). // Helper for user to resolve all symbols void _${lib_suffix}_tramp_resolve_all(void) { size_t i; for(i = 0; i < SYM_COUNT; ++i) _${lib_suffix}_tramp_resolve(i); } // Allows user to specify manually loaded implementation library. void _${lib_suffix}_tramp_set_handle(void *handle) { // TODO: call unload_lib ? lib_handle = handle; dlopened = 0; } // Resets all resolved symbols. This is needed in case // client code wants to reload interposed library multiple times. void _${lib_suffix}_tramp_reset(void) { // TODO: call unload_lib ? memset(_${lib_suffix}_tramp_table, 0, SYM_COUNT * sizeof(_${lib_suffix}_tramp_table[0])); lib_handle = 0; dlopened = 0; } #ifdef __cplusplus } // extern "C" #endif