能拿到——dladdr返回的dli_saddr即为函数在进程中的运行时虚拟地址,是真实可解引用的地址,但需确保函数未被内联、符号在动态符号表中可见且非静态/隐式链接。

dladdr 能不能拿到函数的运行时绝对地址
不能直接拿到——dladdr 返回的是 Dl_info 结构体里的 dli_saddr,它确实是符号对应的地址,但这个地址是「当前加载基址下的有效地址」,也就是你进程视角看到的运行时虚拟地址。它不是“重定位前的原始偏移”,也不是“PLT/GOT 间接跳转入口”,而是真实可 dereference 的函数指针值(前提是符号确实被解析且未被优化掉)。
常见误解是以为 dladdr 只能查符号名、查不到地址;其实只要传入一个有效的函数指针(比如 &my_func 或 (void*)my_func),dladdr 就会把对应符号的运行时地址填进 dli_saddr 字段。
- 必须确保函数未被内联(加
__attribute__((noinline))或编译时关优化) - 不能传 NULL 或野指针,否则
dladdr返回 0 - 静态链接的函数(如
libc.a中的printf)可能查不到,因为没走动态符号表 - 如果函数在 .so 里但被
hidden或protectedvisibility 修饰,dladdr也可能失败
为什么 dladdr(&func) 有时返回空或 dli_sname 为 NULL
根本原因是:符号在动态符号表(.dynsym)中不可见。这和编译/链接时的 visibility、导出控制强相关。
典型场景包括:
- 函数定义在 .so 里,但没加
__attribute__((visibility("default"))),且编译用了-fvisibility=hidden(GCC/Clang 默认行为) - 链接时用了
--exclude-libs或-fvisibility=hidden传导到依赖库 - 函数是模板实例化或 inline 函数,未显式实例化导出
- 目标函数实际跳转到了 PLT 入口(例如被 -fPIE/-pie 影响),而 PLT stub 不算“符号本身”
验证方法:readelf -d libxxx.so | grep NEEDED 看依赖,再用 nm -D libxxx.so | c++filt | grep my_func 确认符号是否出现在动态符号表里。没输出就说明 dladdr 必然失败。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
如何安全地用 dladdr 获取函数地址并做后续操作
关键不是“调用 dladdr”,而是确保输入指针指向的是符号的真实定义位置,而非 PLT 桩、got entry 或内联副本。
- 获取地址前,先取函数地址:例如
void* ptr = reinterpret_cast<void>(real_func);</void>,不要用reinterpret_cast<void>(&real_func)</void>(取地址符对函数名是冗余的) - 调用后检查返回值:
if (dladdr(ptr, &info) == 0)表示失败,不要假设info.dli_saddr有效 -
info.dli_saddr和你传入的ptr在绝大多数情况下相等,但若函数有多个 weak 定义或版本符号(symbol versioning),dli_saddr是最终解析到的那个地址 - 若需反查符号名,
info.dli_sname是 C 字符串,可能为 NULL;info.dli_fname才是所在 so 的路径,可用于二次dlopen
示例片段:
#include <dlfcn.h>
#include <iostream><p>extern "C" void real_func() <strong>attribute</strong>((noinline));
void real_func() { }</p>
<p>int main() {
Dl_info info;
void<em> ptr = reinterpret_cast<void>>(real_func);
if (dladdr(ptr, &info)) {
std::cout </void></em></p></iostream></dlfcn.h>
dladdr 在 PIE 可执行文件或 Android 上的特殊表现
在启用 PIE(-fPIE -pie)的程序里,dladdr 依然工作,但要注意:主可执行文件的符号默认不进动态符号表(除非显式加 -rdynamic 或 --export-dynamic)。这意味着你对 main binary 里的函数调用 dladdr 很可能失败。
- Linux 下编译主程序时加
-rdynamic(等价于ld --export-dynamic)才能让全局符号进入 .dynsym - Android NDK r21+ 默认开启
-fPIE,且adb shell cat /proc/self/maps可确认加载基址随机性;但dladdr返回的仍是 ASLR 后的实际地址,无需额外重定位计算 - ARM64 上某些内联 hook 工具依赖
dladdr定位函数起始,但如果目标函数开头是adrp + add形式的 PC-relative load,dli_saddr仍指向函数第一条指令,没问题
真正容易被忽略的是:符号地址有效 ≠ 函数可安全 patch。现代 libc(如 glibc 2.34+)对关键函数(malloc, open)做了 indirect call hardening,直接改函数头可能触发 abort。查地址只是第一步,后续修改需要更底层的保护绕过意识。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










