挂钩框架(类似绕行)

我正在寻找一个C库/框架,它允许我替换内存中的函数并将它们重定向到我自己的实现,同时仍允许我的实现调用原始实现。

这似乎是Linux-y系统中相当罕见的需求,大概是因为LD_PRELOAD涵盖了运行时function替换的大多数方面。

以下方法似乎适用于我的应用程序。 我不喜欢我的机器上的专有blob,所以我不知道它是否适用于例如Steam。 不过,我很想知道。 我认为没有任何理由不应该这样做。

以下方法使用_dl_vsym() main()在执行main()之前从libdl.so正确查找版本化的dlsym()dlvsym() 。 在应用程序执行期间,插入的dlsym()dlvsym()调用它们的原始版本( 不是 _dl_vsym() ); 我认为应该避免任何特定于应用程序的困境。

如果其他动态库在此之前初始化,则使用这些函数的非常谨慎的初始版本。 他们使用_dl_vsym()来获取对libdl dlsym()dlvsym()函数的引用; 任何后续调用都将使用libdl dlsym()dlvsym() 。 这限制了库初始化期间第一次调用的脆弱时间 – 但优先级101希望首先初始化此库。

 #define _GNU_SOURCE #include  #include  #include  #include  #include  #define UNUSED __attribute__((unused)) #define LIBDL_VERSION "GLIBC_2.2.5" #define LIBDL_PATH "libdl.so" extern void *_dl_vsym(void *, const char *, const char *, void *); static const struct { const char *const symbol; const char *const version; void *const function; } interposed[] = { { "dlsym", LIBDL_VERSION, dlsym }, { "dlvsym", LIBDL_VERSION, dlvsym }, { "glXSwapBuffers", (const char *)0, glXSwapBuffers }, { "eglSwapBuffers", (const char *)0, eglSwapBuffers }, { (const char *)0, (const char *)0, (void *)0 } }; static void * initial_dlsym(void *, const char *); static void * initial_dlvsym(void *, const char *, const char *); static void initial_glXSwapBuffers(Display *, GLXDrawable); static EGLBoolean initial_eglSwapBuffers(EGLDisplay, EGLSurface); static void * (*actual_dlsym)(void *, const char *) = initial_dlsym; static void * (*actual_dlvsym)(void *, const char *, const char *) = initial_dlvsym; static void (*actual_glXSwapBuffers)(Display *, GLXDrawable) = initial_glXSwapBuffers; static EGLBoolean (*actual_eglSwapBuffers)(EGLDisplay, EGLSurface) = initial_eglSwapBuffers; static void initial_glXSwapBuffers(Display *display UNUSED, GLXDrawable drawable UNUSED) { return; } static EGLBoolean initial_eglSwapBuffers(EGLDisplay display UNUSED, EGLSurface surface UNUSED) { return 0; } static void *initial_dlsym(void *handle, const char *const symbol) { void *(*call_dlsym)(void *, const char *); if (symbol) { size_t i; for (i = 0; interposed[i].symbol; i++) if (!strcmp(symbol, interposed[i].symbol)) return interposed[i].function; } *(void **)(&call_dlsym) = __atomic_load_n((void **)(&actual_dlsym), __ATOMIC_SEQ_CST); if (!call_dlsym || call_dlsym == initial_dlsym) { const int saved_errno = errno; void *handle; handle = dlopen(LIBDL_PATH, RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND | RTLD_NODELETE); call_dlsym = _dl_vsym(handle, "dlsym", LIBDL_VERSION, dlsym); dlclose(handle); if (!call_dlsym || call_dlsym == initial_dlsym || call_dlsym == dlsym) { errno = saved_errno; return (void *)0; } __atomic_store_n((void **)(&actual_dlsym), call_dlsym, __ATOMIC_SEQ_CST); errno = saved_errno; } return call_dlsym(handle, symbol); } static void *initial_dlvsym(void *handle, const char *const symbol, const char *const version) { void *(*call_dlvsym)(void *, const char *, const char *); if (symbol) { size_t i; for (i = 0; interposed[i].symbol; i++) if (!strcmp(symbol, interposed[i].symbol)) if (!interposed[i].version || !version || !strcmp(version, interposed[i].version)) return interposed[i].function; } *(void **)(&call_dlvsym) = __atomic_load_n((void **)(&actual_dlvsym), __ATOMIC_SEQ_CST); if (!call_dlvsym || call_dlvsym == initial_dlvsym) { const int saved_errno = errno; void *handle; handle = dlopen(LIBDL_PATH, RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND | RTLD_NODELETE); call_dlvsym = _dl_vsym(handle, "dlvsym", LIBDL_VERSION, dlvsym); dlclose(handle); if (!call_dlvsym || call_dlvsym == initial_dlvsym || call_dlvsym == dlvsym) { errno = saved_errno; return (void *)0; } __atomic_store_n((void **)(&actual_dlvsym), call_dlvsym, __ATOMIC_SEQ_CST); errno = saved_errno; } return call_dlvsym(handle, symbol, version); } void *dlsym(void *handle, const char *const symbol) { if (symbol) { size_t i; for (i = 0; interposed[i].symbol; i++) if (!strcmp(symbol, interposed[i].symbol)) return interposed[i].function; } return actual_dlsym(handle, symbol); } void *dlvsym(void *handle, const char *const symbol, const char *version) { if (symbol) { size_t i; for (i = 0; interposed[i].symbol; i++) if (!strcmp(symbol, interposed[i].symbol)) if (!interposed[i].version || !version || !strcmp(version, interposed[i].version)) return interposed[i].function; } return actual_dlvsym(handle, symbol, version); } static void init(void) __attribute__((constructor (101))); static void init(void) { int saved_errno; void *handle; saved_errno = errno; handle = dlopen(LIBDL_PATH, RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND | RTLD_NODELETE); __atomic_store_n((void **)(&actual_dlsym), _dl_vsym(handle, "dlsym", LIBDL_VERSION, dlsym), __ATOMIC_SEQ_CST); __atomic_store_n((void **)(&actual_dlvsym), _dl_vsym(handle, "dlvsym", LIBDL_VERSION, dlvsym), __ATOMIC_SEQ_CST); dlclose(handle); __atomic_store_n((void **)(&actual_glXSwapBuffers), actual_dlsym(RTLD_NEXT, "glXSwapBuffers"), __ATOMIC_SEQ_CST); __atomic_store_n((void **)(&actual_eglSwapBuffers), actual_dlsym(RTLD_NEXT, "eglSwapBuffers"), __ATOMIC_SEQ_CST); errno = saved_errno; } void glXSwapBuffers(Display *dpy, GLXDrawable drawable) { /* TODO: Custom stuff before glXSwapBuffers() */ actual_glXSwapBuffers(dpy, drawable); /* TODO: Custom stuff after glXSwapBuffers() */ } EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface) { EGLBoolean result; /* TODO: Custom stuff before eglSwapBuffers() */ result = actual_eglSwapBuffers(dpy, surface); /* TODO: Custom stuff after eglSwapBuffers() */ return result; } 

如果将上面的内容保存为example.c ,则可以使用将其编译为libexample.so

 gcc -Wall -fPIC -shared `pkg-config --cflags gl egl` example.c -ldl -Wl,-soname,libexample.so `pkg-config --libs gl egl` -o libexample.so 

在某些情况下,您需要修改LIBDL_VERSION 。 使用

 find /lib* /usr/ -name 'libdl.*' | while read FILE ; do echo "$FILE:" ; readelf -s "$FILE" | sed -ne '/ dlsym@/ s|^.*@@*|\t|p' ; done 

检查libdl使用的API版本。 (我见过GLIBC_2.0GLIBC_2.2.5 ;它没有反映库的实际版本,而是dlsym()dlvsym()调用的API版本。)

interposed[]数组包含插入函数的修改结果。

我已经validation上面的例子不会崩溃我试过的任何应用程序 – 包括我写的简单的dlsym()dlvsym()压力测试 – 并且它也正确地插入了glXSwapBuffers() (在glxgearsmpv ) 。

有问题吗? 评论?