&func 获取的是符号地址而非真实执行地址,因增量链接、PIE、LTO等机制导致其与调试器显示不一致;安全获取真实入口需禁用增量链接、避免内联、用abi::__cxa_demangle()等工具可靠解码符号。

直接取 &func 得到的数值,**不等于**调试器里看到的函数真实执行地址——尤其在 Windows 增量链接、Linux PIE 或启用 LTO 的项目中。这不是 bug,是链接器和运行时机制决定的。
为什么 printf("%p", &func) 和 GDB info address func 显示不同
常见错误现象:Release 模式下 &func 打印出一个地址,但用 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:C++17 起这是未定义行为;改用(func) printf("%p", static_cast更合规(static_cast (func))) - 验证是否真实:用
objdump -d a.out | grep -A3 "看反汇编起始地址,再与运行时打印值比对:"
Demangle C++ 符号名的可靠方式(不是所有 nm -C 都准)
错误现象:dlsym(handle, "my_func") 返回 nullptr;nm -C libxxx.so | grep my_func 找不到;或 addr2line 解析失败。
关键点:
-
nm -C仅对未 strip 的二进制有效,且依赖编译器是否生成 .comment 或 .gnu.build.attributes;生产环境常被 strip,此时nm -C无输出 - C++ name mangling 不是标准协议,Itanium ABI(GCC/Clang)和 MSVC ABI 完全不兼容;
c++filt _Z1fv只能解 Itanium 名 - 真正可靠的运行时 demangle:Linux 用
abi::__cxa_demangle(),Windows 用UnDecorateSymbolName()(Dbghelp.h) - 若你只有 mangled 名(如
_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4sizeEv),必须用对应 ABI 工具链的 demangler,不能混用
成员函数地址?别硬解,换思路
试图对 &MyClass::foo 做 reinterpret_cast 或 union 拆包,在 C++ 标准里就是未定义行为。Itanium ABI 下它可能是 16 字节结构,含 this 偏移、vtbl 索引、甚至 thunk 地址;MSVC x64 下虽常为 8 字节 {addr, offset},但一旦涉及虚继承或 LTO,立即失效。
更务实的做法:
- 静态成员函数:直接
&T::static_func,它就是普通函数指针 - 非静态函数:用捕获对象的 lambda 包装,再取其地址(仅限无捕获 lambda):
auto wrapper = [&obj]() { obj.foo(); }; auto ptr = +[](auto&& f) -> void(*)() { return reinterpret_cast(f); }(wrapper); - 必须走符号查表?自己注册:
std::map<:string void> g_func_map = {{"MyClass::foo", reinterpret_cast,其中(&MyClass::foo_bridge)}}; foo_bridge是 extern "C" 静态函数
最易被忽略的一点:**地址本身没有意义,上下文才有**。同一个 &func 值,在 fork 后子进程里可能映射失败;在 dlopen 加载的 .so 里,若没加 __attribute__((visibility("default"))),符号根本不可见;而你用 dladdr() 查到的地址,也只在当前加载基址下有效。别把“拿到地址”当成终点,它只是你后续操作(patch/hook/trace)的起点,而那个操作是否成功,取决于你是否匹配了真实的内存布局和调用约定。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











