Linux C++ 调用动态库函数的过程与原理
在 Linux 系统下,C++ 调用动态库(.so 文件)主要分为**静态链接(编译期链接)和动态加载(运行时加载)**两种方式。以下是详细流程和代码示例:
Linux 下 C++ 调用动态库的静态链接与动态加载方式,涵盖编译步骤、dlopen/dlsym 使用及错误处理。深入解析了动态链接器介入、符号解析、地址重定位及 PLT/GOT 协同工作机制。同时阐述了 ELF 文件中.dynamic 节的结构作用、内存布局及 ASLR 机制,并提供 ldd、readelf 等调试工具的使用指南,帮助开发者掌握底层原理以解决依赖冲突和优化性能。

在 Linux 系统下,C++ 调用动态库(.so 文件)主要分为**静态链接(编译期链接)和动态加载(运行时加载)**两种方式。以下是详细流程和代码示例:
假设需要导出一个 add 函数:
// mylib.h
#pragma once
extern "C" int add(int a, int b);
// mylib.cpp
#include "mylib.h"
int add(int a, int b) {
return a + b;
}
编译生成动态库:
g++ -fPIC -c mylib.cpp -o mylib.o
g++ -shared mylib.o -o libmylib.so
// main.cpp
#include "mylib.h"
#include <iostream>
int main() {
std::cout << "5 + 3 = " << add(5, 3) << std::endl;
return 0;
}
g++ main.cpp -L. -lmylib -o main
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
./main
// main_dynamic.cpp
#include <iostream>
#include <dlfcn.h>
#include <cstdlib>
typedef int (*add_func)(int, int);
int main() {
void* handle = dlopen("./libmylib.so", RTLD_LAZY);
if (!handle) {
std::cerr << "Error: " << dlerror() << std::endl;
return 1;
}
add_func add = (add_func)dlsym(handle, "add");
char* error = dlerror();
if (error) {
std::cerr << "Error: " << error << std::endl;
dlclose(handle);
return 1;
}
std::cout << "5 + 3 = " << add(5, 3) << std::endl;
dlclose(handle);
return 0;
}
g++ main_dynamic.cpp -ldl -o main_dynamic
./main_dynamic
PLT/GOT 机制是 Linux 动态链接的核心设计,通过**延迟绑定(Lazy Binding)和位置无关代码(PIC)**实现高效、灵活的函数调用。
.plt 节,是只读的代码段。jmp 指令和一段'解析存根'(stub)。.plt:
jmp *got_entry
pushq $symbol_index
jmp plt0
.got 和 .got.plt 节,是可写的数据段。.got.plt 存储函数地址指针,每个 PLT 条目对应一个 GOT 条目。以调用 add(5,3) 为例:
首次调用(延迟绑定触发):
call add 指令,跳转到 PLT 中 add 对应的条目。jmp *GOT[add],此时 GOT 条目仍指向 PLT 自身的解析存根。pushq $symbol_index,将符号索引压栈。jmp plt0,跳转到动态链接器入口点。libmylib.so 中的 add 函数地址。add 函数的实际内存地址,并跳转执行。后续调用(直接跳转):
call add,跳转到 PLT 条目。jmp *GOT[add],此时 GOT 条目已存储 add 函数地址。add 函数执行,无需经过动态链接器。在 Linux 动态链接机制中,.dynamic 节是 ELF 文件的核心组成部分,负责存储动态链接器运行时所需的关键信息。
Elf32_Dyn 或 Elf64_Dyn 结构数组组成,每个条目包含 d_tag 和 d_un。typedef struct {
Elf64_Xword d_tag;
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
DT_NEEDED 条目记录程序依赖的共享库。.dynsym)、字符串表(.dynstr)的位置。.rela.dyn、.rela.plt 等重定位表的地址。| d_tag 类型 | 含义 | d_un 存储内容 |
|---|---|---|
DT_NEEDED | 依赖的共享库 | d_val 指向 .dynstr 中的库名字符串偏移 |
DT_STRTAB | 字符串表地址 | d_ptr 指向 .dynstr 的起始地址 |
DT_SYMTAB | 动态符号表地址 | d_ptr 指向 .dynsym 的起始地址 |
DT_RELA/DT_REL | 重定位表地址 | d_ptr 指向 .rela.dyn 或 .rela.plt |
DT_JMPREL | PLT 重定位表地址 | d_ptr 指向 .rela.plt |
DT_INIT/DT_FINI | 初始化和清理函数地址 | d_ptr 指向库的 init 或 fini 函数 |
.dynamic 节内容。readelf -d ./main
| 组件类型 | 典型路径 | 功能说明 |
|---|---|---|
| 可执行文件 | /usr/bin, /bin | 程序入口点 |
| 动态库(.so) | /usr/lib, /lib | 共享代码模块 |
| 配置文件 | /etc, ~/.config | 定义程序行为参数 |
| 资源文件 | /usr/share, /var | 非代码数据 |
.dynamic 节中的依赖库。_start -> 调用 main()。ELF 文件由**节(Section)和段(Segment)**构成。
| 节名 | 类型 | 内容描述 |
|---|---|---|
.text | 代码节 | 编译后的机器码 |
.data | 数据节 | 已初始化的全局/静态变量 |
.bss | 数据节 | 未初始化的全局/静态变量 |
.rodata | 只读数据节 | 字符串常量、全局常量 |
.plt | 过程链接表 | 延迟绑定的跳转代码 |
.got.plt | 全局偏移表 | 存储函数地址指针 |
.dynamic | 动态信息表 | 存储动态链接元数据 |
进程启动后,ELF 文件通过 mmap 映射到内存,形成以下区域:
Linux 应用程序的结构是分层、模块化、可扩展的,通过动态链接、位置无关代码(PIC)和标准目录布局实现高效运行。理解这些机制有助于调试依赖问题、优化性能及设计插件化系统。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online