Linux下查看进程用到了哪些动态库的函数

Linux下查看进程用到了哪些动态库的接口

在Linux下,查看进程使用的动态库及其接口,可以通过以下方法实现:


1. 查看进程加载的动态库文件

方法 1:通过 /proc/<PID>/maps 文件

1
cat /proc/<PID>/maps | grep '\.so'
  • 说明
    列出进程内存映射中所有加载的 .so 动态库文件路径。
    替换 <PID> 为实际进程ID。

方法 2:使用 pmap 命令

1
pmap -x <PID> | grep '\.so'
  • 说明
    显示进程的内存映射,过滤出动态库路径。

方法 3:使用 lsof 命令

1
lsof -p <PID> | grep '\.so'
  • 说明
    列出进程打开的文件,过滤出共享库文件(.so)。

2. 查看动态库导出的接口(符号)

方法 1:使用 nm 命令

1
nm -D /path/to/library.so
  • 说明
    -D 选项显示动态符号表(导出的接口)。
    示例输出:
    1
    2
    0000000000003a60 T malloc
    0000000000009400 T pthread_create
    T 表示全局函数(即接口)。

方法 2:使用 objdump 命令

1
objdump -T /path/to/library.so
  • 说明
    显示动态库的动态符号表(导出的函数接口)。

方法 3:使用 readelf 命令

1
readelf -s --dyn-syms /path/to/library.so
  • 说明
    --dyn-syms 选项列出动态符号(导出的接口)。

3. 实时监控进程调用的动态库接口

使用 ltrace 工具

1
ltrace -p <PID> -l /path/to/library.so
  • 说明
    • -p <PID>:附加到目标进程。
    • -l lib.so:只监控特定库的调用(可省略)。
      示例输出
      1
      2
      libc.so.6->malloc(1024) = 0x55a6d5eeb260
      libpthread.so.0->pthread_create(0x7ffd4a260d50, NULL, 0x55a6d5c8d1a0, NULL) = 0
      显示进程调用的库函数及其参数。

4. 查看进程依赖的所有动态库

使用 ldd 命令(针对可执行文件)

1
ldd /path/to/program
  • 注意
    ldd 只能查看可执行文件的依赖库(非运行时进程)。
    查看运行中进程的库依赖需结合 /proc/<PID>/maps

完整流程示例

假设进程ID为 1234,查看其使用的 libc 接口:

  1. 找到 libc 库路径
    1
    2
    cat /proc/1234/maps | grep libc.so
    # 输出示例:/usr/lib/x86_64-linux-gnu/libc.so.6
  2. 列出 libc 的导出接口
    1
    2
    3
    4
    nm -D /usr/lib/x86_64-linux-gnu/libc.so.6 | grep ' T ' | head -n 5
    # 输出示例:
    # 00000000000a7c50 T malloc
    # 00000000000a7ce0 T free
  3. 实时监控进程调用
    1
    ltrace -p 1234 -l libc.so.6

总结

目标 命令/方法
查看加载的动态库 cat /proc/<PID>/maps | grep '\.so'
列出动态库的导出接口 nm -D /path/to/lib.so
监控进程调用的库函数 ltrace -p <PID> -l lib.so
查看可执行文件的库依赖 ldd /path/to/program

注意:部分操作需 sudo 权限(如监控非当前用户的进程)。

dlopen函数用法及示例

dlopen 函数详解

dlopen 是 Linux 动态链接库的核心函数,用于在运行时动态加载共享库.so 文件)。它允许程序按需加载库,无需在编译时静态链接,常用于插件系统、模块化设计等场景。


函数原型

1
2
3
#include <dlfcn.h>

void *dlopen(const char *filename, int flags);

参数说明:

  1. filename

    • 共享库路径(如 "libexample.so"
    • 若为 NULL,返回主程序自身的句柄(用于访问全局符号)。
  2. **flags**(常用标志组合)

    标志 作用
    RTLD_LAZY 延迟绑定(推荐):在首次调用函数时解析符号(提高启动速度)。
    RTLD_NOW 立即解析所有符号:加载时检查所有符号有效性,失败则返回错误。
    RTLD_GLOBAL 使库的符号全局可见(可供后续加载的库使用)。
    RTLD_LOCAL 符号仅对本库可见(默认)。

返回值:

  • 成功:返回共享库的句柄void* 类型)
  • 失败:返回 NULL,错误信息通过 dlerror() 获取。

关联函数

函数 作用
void* dlsym(void* handle, const char* symbol) 从库中获取符号(函数/变量)地址
int dlclose(void* handle) 关闭共享库(引用计数减为0时卸载)
char* dlerror(void) 获取最后一次错误信息(无错误返回 NULL

使用步骤

  1. dlopen 打开共享库
  2. dlsym 获取符号地址
  3. 调用符号(函数或访问变量)
  4. dlclose 关闭库
  5. dlerror 检查错误

示例 1:动态加载数学库(libm.so

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <dlfcn.h> // 包含 dlopen 系列函数

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

// 1. 打开数学库(libm.so)
handle = dlopen("libm.so.6", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "Error: %s\n", dlerror());
return 1;
}

// 2. 获取 cos 函数地址
cosine = (double (*)(double)) dlsym(handle, "cos");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "dlsym error: %s\n", error);
dlclose(handle);
return 1;
}

// 3. 调用动态加载的函数
double result = cosine(3.1415926535); // cos(π) ≈ -1
printf("cos(π) = %f\n", result);

// 4. 关闭库
dlclose(handle);
return 0;
}

编译命令(需链接 dl 库):

1
gcc -o math_demo math_demo.c -ldl

示例 2:加载自定义共享库

步骤 1:创建共享库 libhello.so

1
2
3
4
5
6
// hello.c
#include <stdio.h>

void say_hello(const char *name) {
printf("Hello, %s!\n", name);
}

编译为共享库:

1
gcc -shared -fPIC -o libhello.so hello.c

步骤 2:主程序动态加载 libhello.so

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// main.c
#include <stdio.h>
#include <dlfcn.h>

int main() {
void *lib_handle;
void (*say_hello)(const char*); // 函数指针
char *error;

// 1. 打开自定义库
lib_handle = dlopen("./libhello.so", RTLD_LAZY);
if (!lib_handle) {
fprintf(stderr, "dlopen error: %s\n", dlerror());
return 1;
}

// 2. 获取 say_hello 函数地址
say_hello = (void (*)(const char*)) dlsym(lib_handle, "say_hello");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "dlsym error: %s\n", error);
dlclose(lib_handle);
return 1;
}

// 3. 调用动态加载的函数
say_hello("OpenAI");

// 4. 关闭库
dlclose(lib_handle);
return 0;
}

编译并运行:

1
2
3
gcc -o hello_demo main.c -ldl
./hello_demo
# 输出: Hello, OpenAI!

关键注意事项

  1. 路径问题

    • 若库不在标准路径(如 /usr/lib),需使用绝对路径(如 "/home/user/libhello.so")或相对路径(如 "./libhello.so")。
  2. 符号可见性

    • 共享库中的函数需声明为 extern(默认)或使用编译选项 -fvisibility=default
  3. 错误处理

    • 每次调用后检查 dlerror()(因连续调用会清除错误信息)。
  4. 资源释放

    • dlclose() 减少引用计数,计数为 0 时卸载库(避免内存泄漏)。
  5. C++ 兼容性

    • C++ 函数需用 extern "C" 避免名称修饰(Name Mangling):
      1
      2
      3
      extern "C" {
      void say_hello(const char*);
      }

常见错误场景

错误现象 可能原因 解决方案
dlopen: file not found 库路径错误 指定绝对路径或设置 LD_LIBRARY_PATH
dlsym: undefined symbol 函数名错误或未导出 nm -D lib.so 检查符号表
段错误(Segmentation Fault) 函数指针类型不匹配 确保函数指针声明正确

进阶技巧

  1. 加载主程序符号

    1
    2
    void *self_handle = dlopen(NULL, RTLD_LAZY);
    void *func = dlsym(self_handle, "local_function");
  2. 控制符号可见性(GCC)
    在库源码中声明导出符号:

    1
    __attribute__((visibility("default"))) void public_func();
  3. 调试工具

    • 使用 LD_DEBUG=libs 环境变量跟踪加载过程:
      1
      LD_DEBUG=libs ./your_program

通过 dlopen 实现动态加载,可以显著提升程序的灵活性和可扩展性。