使用 nm 和 dlsym 加载共享对象符号
在 Linux 开发中,动态链接库(共享对象文件,.so)的符号加载和地址解析是一个常见需求。通过 nm 和 dlsym 的结合,我们可以高效地实现符号加载。本文将详细介绍实现原理、方法、代码示例,以及注意事项。
背景介绍
共享对象文件中的符号包含函数、变量等导出的信息。在调试或动态加载场景中,我们常需要解析符号名称和对应地址。
通常解析符号可以通过以下步骤实现:
- 使用
nm获取.so文件中的全部符号名称。 - 使用
dlsym从共享对象中解析符号地址。
这种方法避免了复杂的 ELF 文件格式解析,具有高效和稳定的优点。
实现原理
使用 nm 获取符号名称
nm -D your_shared_object.so 可以列出共享对象中所有的动态符号。我们可以通过解析其输出,筛选出感兴趣的符号,例如类型为 T 或 t(表示全局或本地函数符号)的符号。
使用 dlsym 获取符号地址
dlsym 是一个标准的 POSIX 接口,用于从共享对象文件中查找符号地址。结合符号名称列表,可以逐一解析每个符号的地址,并将其存储到 std::unordered_map 中。
实现代码
以下代码展示了从共享对象中提取符号名称并解析地址的完整实现:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include <cstdlib>
#include <dlfcn.h>
// 获取共享对象符号映射表
std::unordered_map<void*, std::string> getAddressToNameMap(const std::string& so_file_name) {
std::unordered_map<void*, std::string> addressToNameMap;
// 运行 nm 命令获取符号名称
std::string command = "nm -D " + so_file_name + " 2>/dev/null";
FILE* pipe = popen(command.c_str(), "r");
if (!pipe) {
std::cerr << "Failed to run nm command" << std::endl;
return addressToNameMap;
}
std::vector<std::string> functionNames;
char buffer[256];
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
std::string line(buffer);
std::istringstream iss(line);
std::string address, type, name;
if (iss >> address >> type >> name) {
if (type == "T" || type == "t") {
functionNames.push_back(name);
}
}
}
pclose(pipe);
// 打开共享对象文件
void* handle = dlopen(so_file_name.c_str(), RTLD_LAZY);
if (!handle) {
std::cerr << "dlopen failed: " << dlerror() << std::endl;
return addressToNameMap;
}
// 获取符号地址并存入映射表
for (const auto& funcName : functionNames) {
void* addr = dlsym(handle, funcName.c_str());
if (addr) {
addressToNameMap[addr] = funcName;
} else {
std::cerr << "dlsym failed for symbol: " << funcName << " - " << dlerror() << std::endl;
}
}
// 关闭共享对象文件
dlclose(handle);
return addressToNameMap;
}
int main() {
const std::string so_file_name = "your_shared_object.so";
// 获取符号映射表
auto addressToNameMap = getAddressToNameMap(so_file_name);
// 打印所有符号地址和名称
for (const auto& entry : addressToNameMap) {
std::cout << "Address: " << entry.first << ", Name: " << entry.second << std::endl;
}
return 0;
}
代码解析
获取符号映射表
- 通过封装
getAddressToNameMap函数,实现符号提取和地址解析的功能。 getAddressToNameMap的输入是共享对象文件路径,返回值是地址和符号名称的映射表。
使用 nm 获取符号名称
- 通过
popen执行nm -D命令,读取其输出。 - 筛选出类型为
T或t的符号,通常表示导出的函数符号。