问题背景

产品 A 中的 libtest.so 提供了一个接口 void test_feature(int num),该接口被下游产品 B 通过 dlopen 调用。现在有人编写了该接口的新实现 test_feature_v2,并打包成 libtest_v2.so。由于产品 A 和 B 均难以进行工程或代码层面的修改,因此需要一种不修改原有代码的方式,让产品 B 在调用 test_feature 时,实际执行 libtest_v2.so 中的 test_feature_v2 函数。

解决方案

利用 LD_PRELOAD 机制,我们可以预加载一个自定义的共享库,在其中重新定义 dlsym 函数,从而拦截动态链接过程。具体做法是:当检测到 dlsym(handle, "test_feature") 被调用时,自动加载 libtest_v2.so,并返回其中 test_feature_v2 的地址。

这样,产品 B 无需任何改动即可使用新的实现进行测试。

代码实现

#include <dlfcn.h>
#include <string.h>
#include <stdio.h>

static void *(*real_dlsym)(void *, const char *) = NULL;
static void *v2_handle = NULL;   // 记录 libtest_v2.so 的句柄

void *dlsym(void *handle, const char *symbol) {
    if (!real_dlsym) {
        real_dlsym = dlsym(RTLD_NEXT, "dlsym");
    }
    if (symbol && strcmp(symbol, "test_feature") == 0) {
        // 懒加载 libtest_v2.so
        if (!v2_handle) {
            v2_handle = dlopen("libtest_v2.so", RTLD_LAZY);
            if (!v2_handle) {
                // 加载失败时可记录错误信息
                return NULL;
            }
        }
        // 从 v2 库中获取 test_feature_v2 符号
        return real_dlsym(v2_handle, "test_feature_v2");
    }
    // 其他符号按原始方式处理
    return real_dlsym(handle, symbol);
}

使用方法

  • 编译拦截库:
gcc -shared -fPIC -o libdl_patched.so dl_patched.c -ldl
  • 运行产品 B,并预加载拦截库:
LD_PRELOAD=./libdl_patched.so ./product_B