&func 获取的地址不等于真实执行地址,因增量链接、PIE、LTO等机制引入跳转桩或重定位;应使用 dladdr(Linux)或禁用增量链接(Windows)获取真实入口。

直接取 &func 得到的地址,不等于调试器里看到的真实执行地址——尤其在 Windows 增量链接、Linux PIE 或启用 LTO 的项目中。这不是 bug,是链接器和运行时机制决定的。
为什么 printf("%p", &func) 和 GDB info address func 显示不同
常见错误现象:Release 模式下打印出一个地址,但 GDB info address func 查到的是另一个;或者 hook 时 patch 错位置,程序崩溃。
- MSVC 默认开启
/INCREMENTAL:函数名解析指向跳转桩(thunk),真实代码在别处;禁用后两者才一致 - Linux 下启用
-fPIE或构建为共享库时,&func是重定位后的运行时地址,而objdump -t显示的是链接视图偏移(RVA) - GDB / LLDB 通过符号表 + DWARF/PE 调试信息反查,能绕过 thunk、修正 ASLR 偏移;而裸指针不会自动做这些
如何安全获取可执行的真实入口地址(非虚、非模板、非内联)
适用场景:写 inline hook、JIT 注入、热补丁,需要精确到第一条指令字节。
- 确保函数未被内联:
[[gnu::noinline]]或__attribute__((noinline))(GCC/Clang);MSVC 用__declspec(noinline) - 禁用增量链接(MSVC)或确认未用
-fPIE(GCC)——若必须用 PIE,则需在运行时用getauxval(AT_PHDR)算基址 - 避免
reinterpret_cast<void>(func)</void>:C++17 起这是未定义行为;改用printf("%p", static_cast<void>(static_cast<void>(func)))</void></void>更合规 - 验证是否真实:用
objdump -d a.out | grep -A3 "<func>:"</func>看反汇编起始地址,再与运行时打印值比对
Linux 下最稳的运行时组合:dladdr + abi::__cxa_demangle
比起猜 &func 地址或硬解析符号表,dladdr 是 Linux 生产环境定位函数真实入口的首选方法。
- 先确保函数未被内联:
[[gnu::noinline]] - 调用
dladdr(reinterpret_cast<void>(&my_func), &info)</void>,其中info.dli_sname给出 mangled 名(如_Z7my_funcv),info.dli_saddr是实际加载地址(即你真正要的入口) - 把
info.dli_sname传给abi::__cxa_demangle,注意检查返回的int*status:0=成功,-1=非法 mangled 名,-2=内存不足 -
abi::__cxa_demangle返回的是堆分配的char*,必须手动free(),否则泄漏 - 若
dli_sname == nullptr,说明符号被-fvisibility=hidden、strip掉、或在无符号的静态库中
成员函数指针不能直接 reinterpret_cast 成 void*
这是最常踩的坑:试图用 reinterpret_cast<void>(&MyClass::func)</void> 获取“真实地址”,结果得到的是未定义行为,甚至编译失败。
- C++ 标准禁止将成员函数指针转为对象指针类型——因为它在 Itanium/MSVC ABI 中不是单个地址,而是含
this偏移、vtbl 索引等的多字段结构(8–16 字节) - 非虚成员函数若想提取真实入口,可用 union 拆解(仅限 x86_64 Linux + GCC/Clang):
union { void (MyClass::*mf)(); struct { uint64_t addr; int64_t offset; }; } u{&MyClass::foo}; uintptr_t entry_addr = u.addr; - 虚函数地址必须从对象实例的 vtable 中读取:
void** vtable = *reinterpret_cast<void>(&obj); uintptr_t vfunc_addr = reinterpret_cast<uintptr_t>(vtable[1]);</uintptr_t></void>,但该地址仍可能指向 thunk,需反汇编验证
复杂点在于:同一个函数,在不同构建配置下,&func 可能是跳转桩、可能是重定位后地址、也可能根本不存在(LTO 删除)。不要假设它“就是那个地址”,每次都要结合 dladdr 或 objdump 验证。最容易被忽略的是 free() abi::__cxa_demangle 返回的内存,以及 dli_sname 为空时的 fallback 处理。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











