├── lib.c ├── lib.h ├── CMakeLists.txt ├── main.c ├── main_bad.c ├── closure.h ├── README.md ├── main.cpp ├── main_bad.cpp └── closure.c /lib.c: -------------------------------------------------------------------------------- 1 | #include "lib.h" 2 | 3 | void register_callback(callback_t callback, void* data) { 4 | callback(data); 5 | } 6 | 7 | void register_callback_bad(callback_bad_t callback) { 8 | callback(); 9 | } -------------------------------------------------------------------------------- /lib.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_H 2 | #define LIB_H 3 | 4 | typedef void (*callback_t)(void* data); 5 | 6 | typedef void (*callback_bad_t)(); 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | /* 13 | Well-designed callback API. 14 | */ 15 | void register_callback(callback_t callback, void* data); 16 | 17 | /* 18 | Bad-designed callback API. 19 | */ 20 | void register_callback_bad(callback_bad_t callback); 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | set(CMAKE_CXX_STANDARD 14) 3 | 4 | add_library(mylib SHARED lib.c) 5 | add_library(closure SHARED closure.c) 6 | 7 | add_executable(main_cpp main.cpp) 8 | add_executable(main main.c) 9 | add_executable(main_bad main_bad.c) 10 | add_executable(main_bad_cpp main_bad.cpp) 11 | 12 | target_link_libraries(main mylib) 13 | target_link_libraries(main_cpp mylib) 14 | target_link_libraries(main_bad mylib closure) 15 | target_link_libraries(main_bad_cpp mylib closure) -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "lib.h" 4 | 5 | 6 | typedef struct Context { 7 | int id; 8 | } Context; 9 | 10 | void callback(void* data) { 11 | Context* ctx = (Context*)data; 12 | 13 | printf("Context id=%d\n", ctx->id); 14 | } 15 | 16 | int main(){ 17 | Context* ctx = malloc(sizeof(Context)); 18 | ctx->id = 114514; 19 | 20 | // some operation 21 | 22 | register_callback(callback, ctx); 23 | 24 | // other operation 25 | 26 | free(ctx); 27 | } -------------------------------------------------------------------------------- /main_bad.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "lib.h" 4 | #include "closure.h" 5 | 6 | typedef struct Context { 7 | int id; 8 | } Context; 9 | 10 | void callback(void* data) { 11 | Context* ctx = (Context*)data; 12 | 13 | printf("Context id=%d\n", ctx->id); 14 | } 15 | 16 | int main(){ 17 | Context* ctx = malloc(sizeof(Context)); 18 | Closure* closure; 19 | ctx->id = 114514; 20 | 21 | // Generate a closure. 22 | tramp_table_init(); 23 | closure = closure_alloc(); 24 | closure_prepare(closure, callback, ctx); 25 | 26 | // some operation 27 | 28 | // NOTE: We are passing a closure to a raw function pointer. 29 | register_callback_bad(closure_to_pointer(closure)); 30 | 31 | // other operation 32 | 33 | closure_dealloc(closure); 34 | free(ctx); 35 | } -------------------------------------------------------------------------------- /closure.h: -------------------------------------------------------------------------------- 1 | #ifndef CLOSURE_H 2 | #define CLOSURE_H 3 | 4 | #include "lib.h" 5 | 6 | struct Closure; 7 | typedef struct Closure Closure; 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | /* 13 | Initialize the trampoline table. Must be called before other APIs. 14 | */ 15 | void tramp_table_init(); 16 | 17 | /* 18 | Allocate a free closure. 19 | */ 20 | Closure* closure_alloc(); 21 | 22 | /* 23 | Prepare for the closure. 24 | */ 25 | void closure_prepare(Closure* closure, callback_t callback, void* data); 26 | 27 | /* 28 | Deallocate a free closure. 29 | */ 30 | Closure* closure_dealloc(Closure*); 31 | 32 | /* 33 | Cast a closure to a raw pointer to function. 34 | 35 | NOTE: Since this is a demo so we support the fixed function pointer type. 36 | */ 37 | callback_bad_t closure_to_pointer(Closure*); 38 | 39 | #ifdef __cplusplus 40 | } 41 | #endif 42 | 43 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # closure2fp 2 | 3 | An example to illustrate how libffi cast a closure to a pointer to function. Note that this only works on Linux x86_64. 4 | 5 | Blog post link: 6 | 7 | - [Cast a Closure to a Function Pointer -- How libffi closure works](https://blog.lazym.io/2021/07/29/Cast-a-Closure-to-a-Function-Pointer-How-libffi-closure-works/) 8 | - [把闭包变成函数指针 ——libffi 闭包原理解析](https://blog.ihomura.cn/2021/07/30/%E6%8A%8A%E9%97%AD%E5%8C%85%E5%8F%98%E6%88%90%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E2%80%94%E2%80%94libffi-%E9%97%AD%E5%8C%85%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/) 9 | 10 | # Sources 11 | 12 | - closure.c closure.h: The closure lib to cast a closure to a raw function pointer. 13 | - lib.c lib.h: The lib that provides API to register callback. 14 | - main.c: The example that uses lib.h well-designed callback API. 15 | - main.cpp: The example that uses lib.c well-designed callback API with a cpp wrapper. 16 | - main_bad.c: The example that uses lib.h bad-designed callback API. 17 | - main_bad.cpp: The example that uses lib.c bad-designed callback API with a cpp wrapper. 18 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "lib.h" 5 | 6 | typedef std::function callback_fn; 7 | 8 | class CallbackManager { 9 | public: 10 | static CallbackManager* New(callback_fn fn) { 11 | return new CallbackManager(fn); 12 | } 13 | 14 | void operator()() { 15 | return this->_fn(); 16 | } 17 | 18 | ~CallbackManager(){ 19 | delete this; 20 | } 21 | private: 22 | callback_fn _fn; 23 | CallbackManager(callback_fn fn) : _fn(fn) {} 24 | }; 25 | 26 | void register_callback_wrapper(callback_fn callback) { 27 | CallbackManager* mgr = CallbackManager::New(callback); 28 | register_callback([](void* data){ (*(CallbackManager*)data)(); }, mgr); 29 | } 30 | 31 | typedef struct Context { 32 | int id; 33 | } Context; 34 | 35 | int main(){ 36 | std::shared_ptr ctx = std::make_shared(); 37 | ctx->id = 114514; 38 | 39 | register_callback_wrapper([ctx](){ 40 | // play with Context without any concern. 41 | printf("Context id=%d\n", ctx->id); 42 | }); 43 | 44 | // no need to free context. 45 | } 46 | -------------------------------------------------------------------------------- /main_bad.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "lib.h" 5 | #include "closure.h" 6 | 7 | typedef std::function callback_fn; 8 | 9 | class CallbackManager { 10 | public: 11 | static CallbackManager* New(callback_fn fn, Closure* closure) { 12 | return new CallbackManager(fn, closure); 13 | } 14 | 15 | void operator()() { 16 | return this->_fn(); 17 | } 18 | 19 | ~CallbackManager(){ 20 | closure_dealloc(this->_closure); 21 | delete this; 22 | } 23 | private: 24 | callback_fn _fn; 25 | Closure* _closure; 26 | CallbackManager(callback_fn fn, Closure* closure) : _fn(fn), _closure(closure) {} 27 | }; 28 | 29 | void register_callback_wrapper(callback_fn callback) { 30 | Closure* closure = closure_alloc(); 31 | CallbackManager* mgr = CallbackManager::New(callback, closure); 32 | 33 | closure_prepare(closure, [](void* data){ (*(CallbackManager*)data)();}, mgr); 34 | // NOTE: We are passing a closure to a raw function pointer. 35 | register_callback_bad(closure_to_pointer(closure)); 36 | } 37 | 38 | typedef struct Context { 39 | int id; 40 | } Context; 41 | 42 | int main(){ 43 | std::shared_ptr ctx = std::make_shared(); 44 | ctx->id = 114514; 45 | 46 | // Initialize the trampoline table. 47 | tramp_table_init(); 48 | 49 | register_callback_wrapper([ctx](){ 50 | // play with Context without any concern. 51 | printf("Context id=%d\n", ctx->id); 52 | }); 53 | 54 | // no need to free context. 55 | } 56 | -------------------------------------------------------------------------------- /closure.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 1 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "closure.h" 7 | 8 | typedef struct Closure { 9 | unsigned index; // Fast to dealloc. 10 | int is_free; // Whether in use. 11 | callback_bad_t code; // Actual code entry point. 12 | callback_t callback; // User supplied callback. 13 | void* data; // User supplied data. 14 | } Closure; 15 | 16 | // ['mov rsi, qword ptr [rip + 0xff9] size=7', # 0xff9 + 7 = 0x1000 (tramp_len) 17 | // 'mov r10, qword ptr [rip + 0xffa] size=7', # 0xffa + 7 + 7 = 0x1004 (tramp_len + 8) 18 | // 'jmp r10 size=3'] 19 | #define TRAMP_CODE "\x48\x8b\x3d\xf9\x0f\x00\x00\x4c\x8b\x15\xfa\x0f\x00\x00\x41\xff\xe2" 20 | #define TRAMP_CODE_ALIGNED_LEN ((sizeof(TRAMP_CODE) - 1 + 0x10 - 1) & ( ~(0x10 - 1) )) 21 | #define TRAMP_LEN (0x1000) 22 | #define TRAMP_COUNT (TRAMP_LEN / TRAMP_CODE_ALIGNED_LEN) 23 | static Closure closures[TRAMP_COUNT]; 24 | 25 | static void internal_closure_callback(Closure* closure) { 26 | closure->callback(closure->data); 27 | } 28 | 29 | void tramp_table_init() { 30 | void* addr; 31 | uint64_t tramp_addr; 32 | 33 | // No error handling for simplicity. 34 | 35 | // Map TRAMP_LEN * 2 memory, a half for code and the other half for data. 36 | addr = mmap(NULL, TRAMP_LEN * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 ,0); 37 | // Replace the half of the tramp memory with our code. 38 | addr = mmap(addr, TRAMP_LEN, PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); 39 | 40 | tramp_addr = (uint64_t)addr; 41 | for (int i = 0;i < TRAMP_COUNT; i++) { 42 | // Initialize closures. 43 | closures[i].is_free = 1; 44 | closures[i].code = (void*)tramp_addr; 45 | closures[i].index = i; 46 | closures[i].callback = NULL; 47 | closures[i].data = NULL; 48 | 49 | // Setup tramp code. 50 | memcpy((void*)tramp_addr, TRAMP_CODE, sizeof(TRAMP_CODE) - 1); 51 | 52 | // The parameter of the internal_closure_callback. 53 | *(Closure**)(tramp_addr + TRAMP_LEN) = &closures[i]; 54 | // The target of jmp r10. 55 | *(uint64_t*)(tramp_addr + TRAMP_LEN + 8) = (uint64_t)internal_closure_callback; 56 | 57 | // Next closure. 58 | tramp_addr = tramp_addr + TRAMP_CODE_ALIGNED_LEN; 59 | } 60 | 61 | } 62 | 63 | Closure* closure_alloc() { 64 | // Find the first free closure. 65 | for (int i = 0;i < TRAMP_COUNT; i++) { 66 | if (closures[i].is_free) { 67 | closures[i].is_free = 0; 68 | return &closures[i]; 69 | } 70 | } 71 | 72 | return NULL; 73 | } 74 | 75 | void closure_prepare(Closure* closure, callback_t callback, void* data) { 76 | closure->callback = callback; 77 | closure->data = data; 78 | } 79 | 80 | Closure* closure_dealloc(Closure* closure) { 81 | closure->callback = NULL; 82 | closure->data = NULL; 83 | closures[closure->index].is_free = 1; 84 | } 85 | 86 | callback_bad_t closure_to_pointer(Closure* closure) { 87 | return closure->code; 88 | } 89 | --------------------------------------------------------------------------------