当前位置: 首页 > article >正文

Linux基础 -- `dlsym` 函数的作用

dlsym 是一个用于 动态链接库(Dynamic Shared Object, .so) 解析符号(函数或变量地址)的函数,属于 POSIX 动态链接库 API,由 libdl.so 库提供。主要用于 运行时加载共享库,并在共享库中查找符号


1. dlsym 函数的作用

dlsym 主要用于从动态库(.so 文件)中查找 函数或变量的地址,并返回对应的指针。

原型

#include <dlfcn.h>

void *dlsym(void *handle, const char *symbol);

参数

  • handle:由 dlopen 返回的库句柄,用于指定在哪个共享库中查找符号。
  • symbol:要查找的 符号名称(字符串格式)。

返回值

  • 成功:返回符号地址(可以转换为函数指针或变量指针)。
  • 失败:返回 NULL,错误信息可用 dlerror() 获取。

2. dlsym 的使用流程

典型的动态库加载与函数解析步骤

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

int main() {
    void *handle;
    void (*func)();  // 声明函数指针
    char *error;

    // 1. 以 RTLD_LAZY 模式打开共享库
    handle = dlopen("libm.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "dlopen failed: %s\n", dlerror());
        return 1;
    }

    // 2. 查找符号 "cos"(cosine 函数)
    *(void **) (&func) = dlsym(handle, "cos");
    
    // 3. 检查 dlsym 是否成功
    error = dlerror();
    if (error != NULL) {
        fprintf(stderr, "dlsym failed: %s\n", error);
        dlclose(handle);
        return 1;
    }

    // 4. 调用函数
    printf("cos(0) = %f\n", ((double (*)(double))func)(0.0));

    // 5. 关闭动态库
    dlclose(handle);
    return 0;
}

编译方式

gcc -o dlsym_test dlsym_test.c -ldl

3. dlsym 的应用场景

(1)插件机制

  • 许多嵌入式和系统软件通过 dlopen + dlsym 动态加载插件。
  • 例如 gstreamer 采用动态库方式加载不同的编解码器。

(2)性能优化

  • 仅在需要时加载库,减少 内存占用启动时间
  • 适用于 可选功能,例如 GUI 和 CLI 组件分离,动态加载 GUI。

(3)替换动态库函数

  • 使用 dlsym(RTLD_NEXT, ...)获取下一个同名符号的地址,用于 拦截系统调用(如 malloc)。
  • 例如 LD_PRELOAD 技术 劫持库函数 进行性能分析、日志记录等。

4. 常见问题

(1)dlsym 找不到符号

  • 动态库未导出符号:检查是否使用了 -fvisibility=hidden
  • 符号被 C++ name mangling:C++ 代码需要 extern "C",否则符号名会被修饰:
    extern "C" void my_func() { ... }
    
  • 编译时静态链接:如果某函数被编译为 静态链接 而非动态库,就无法使用 dlsym 查找。

(2)跨架构问题

  • ARM、AArch64、MIPS 等架构上,动态库调用约定不同,可能需要手动调整 函数指针的转换方式

5. 总结

  • dlsym 作用:从动态库中解析符号地址(函数或变量)。
  • dlsym 依赖libdl.so(需 -ldl 链接)。
  • dlsym 典型应用:插件机制、动态扩展、函数拦截。
  • 使用注意
    • 需要 dlopen 先打开库,否则 dlsym 失败。
    • dlsym 可能返回 NULL,调用 dlerror() 获取具体错误信息。

6. dlsym 的底层原理

6.1 dlsym 在 ELF 共享库中的工作机制

在 Linux 系统上,动态库(.so 文件)通常采用 ELF(Executable and Linkable Format) 格式,而 dlsym 本质上是解析 ELF 动态链接库的 符号表(symbol table) 来找到指定符号的地址。

ELF 动态库结构

  • .dynsym:存储动态符号表,包含所有导出的全局函数和变量。
  • .dynstr:存储符号的字符串名称。
  • .hash.gnu.hash:加速符号查找。
  • .plt(Procedure Linkage Table):延迟绑定(Lazy Binding)机制。
  • .got(Global Offset Table):用于重定位。

dlsym 查找符号的流程

  1. 查找 handle 对应的 ELF 共享库
    • handledlopen() 返回,指向加载的 .so
  2. 遍历 ELF 的 .dynsym 符号表
    • 使用 .gnu.hash.hash 进行快速定位。
    • 找到 symbol 对应的 ELF32_Sym / ELF64_Sym 结构。
  3. 解析符号的重定位(GOT/PLT)
    • 如果是 PLT 符号,解析 GOT 获取最终地址。
    • 若符号是 STB_GLOBAL 类型,则直接返回符号地址。

注意dlsym 仅能找到 动态符号表.dynsym),而静态符号表(.symtab)是调试符号,不可用于 dlsym


7. dlsym 的用法详解

7.1 基础使用

动态加载 libm.so 并调用 cos()

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

int main() {
    void *handle;
    double (*cosine)(double);
    
    // 加载共享库
    handle = dlopen("libm.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "dlopen failed: %s\n", dlerror());
        return 1;
    }

    // 获取 cos 函数地址
    *(void **)(&cosine) = dlsym(handle, "cos");

    if (!cosine) {
        fprintf(stderr, "dlsym failed: %s\n", dlerror());
        dlclose(handle);
        return 1;
    }

    printf("cos(0) = %f\n", cosine(0.0));

    // 释放共享库
    dlclose(handle);
    return 0;
}

编译

gcc -o test_dlsym test_dlsym.c -ldl

7.2 使用 RTLD_NEXT 劫持系统函数

有时需要拦截系统调用,如 malloc,可以使用 RTLD_NEXTdlsym 获取 下一个同名符号

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

void *malloc(size_t size) {
    static void *(*real_malloc)(size_t) = NULL;

    if (!real_malloc) {
        real_malloc = dlsym(RTLD_NEXT, "malloc");
    }

    printf("malloc: %zu bytes\n", size);
    return real_malloc(size);
}

编译

gcc -shared -fPIC -o malloc_hook.so malloc_hook.c -ldl

运行

LD_PRELOAD=./malloc_hook.so ./my_program

此方法常用于 内存调试工具(如 valgrind)、性能分析工具


8. dlsym 进阶应用

8.1 动态加载插件(Plugin System)

插件机制允许 运行时扩展功能,如:

  • 音视频编解码(FFmpeg、GStreamer)
  • 数据库驱动(MySQL、SQLite)
  • Python C 扩展

示例:动态加载插件

插件 plugin.c

#include <stdio.h>

void plugin_function() {
    printf("Hello from plugin!\n");
}

编译:

gcc -shared -fPIC -o plugin.so plugin.c

宿主程序 loader.c

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

int main() {
    void *handle;
    void (*plugin_func)();

    handle = dlopen("./plugin.so", RTLD_NOW);
    if (!handle) {
        fprintf(stderr, "dlopen failed: %s\n", dlerror());
        return 1;
    }

    *(void **)(&plugin_func) = dlsym(handle, "plugin_function");
    if (!plugin_func) {
        fprintf(stderr, "dlsym failed: %s\n", dlerror());
        dlclose(handle);
        return 1;
    }

    plugin_func();
    dlclose(handle);
    return 0;
}

编译:

gcc -o loader loader.c -ldl

运行:

./loader

输出:

Hello from plugin!

9. dlsym 的性能分析

9.1 RTLD_LAZY vs RTLD_NOW

方式解析时机优势劣势
RTLD_LAZY第一次调用函数时解析延迟绑定,提高启动速度首次调用有延迟
RTLD_NOWdlopen 时立即解析首次调用无延迟dlopen 开销大

如果库较大且仅调用部分函数,RTLD_LAZY 更合适。

9.2 dlsym 在多线程中的问题

  • dlsym 不是线程安全的,多个线程同时查找符号可能导致 race condition。
  • 建议
    • 在单线程环境下初始化动态库,存储 dlsym 结果。
    • 使用 pthread_mutex_lock() 保护 dlsym 调用。

10. dlsym 的局限性

10.1 无法解析 static 符号

如果库中某函数是 static 或未导出,则 dlsym 解析失败:

static void hidden_function() { }

解决方法

  • static 改为 extern
  • 使用 __attribute__((visibility("default"))) 导出符号。

10.2 C++ 符号修饰问题

在 C++ 代码中,符号会被 Name Mangling,如:

void myFunction() {}

符号可能变为:

_Z11myFunctionv

解决方案

extern "C" void myFunction() {}

11. 深入总结

重点说明
dlsym 作用解析共享库符号(函数、变量地址)
dlsym 依赖仅能解析 .dynsym 符号表
典型应用插件系统、劫持系统调用、动态扩展
性能优化RTLD_LAZY vs RTLD_NOW
限制不能解析 static 符号,C++ 需 extern "C"

http://www.kler.cn/a/572673.html

相关文章:

  • Redis maven项目 jedis 客户端操作(二)
  • 【电控笔记z69】电机选型-机械特性
  • 数据流图(实例)
  • 搜好货平台按关键字搜索商品API接口开发实战(Python版
  • 文本处理Bert面试内容整理-BERT的应用场景有哪些?
  • CSS 中等比例缩放的演变:从传统技巧到 aspect-ratio 属性
  • 宁波福尔达智能科技-再次续订MappingSpace
  • linux服务器根据内核架构下载各种软件依赖插件(例子:Anolis服务器ARM64架构内核Nginx依赖插件下载)
  • Adam优化器
  • kubectrl 使用多k8s 配置文件
  • 后端架构模式之-BFF(Backend-For-Frontend)
  • 国科大——数据挖掘(0812课程)——考试真题
  • ASP.NET Core JWT认证与授权
  • 【玩转正则表达式】将正则表达式中的分组(group)与替换进行结合使用
  • 深入C语言:指针与数组的经典笔试题剖析
  • 大语言模型(LLM)和嵌入模型的统一调用接口
  • 谈谈常用的分布式 ID 设计方案?
  • MySQL数据库只能通过localhost访问,无法通过IP访问?两步快速定位和解决
  • stm32主从机硬件IIC实现
  • Windows10下本地搭建Manim环境