You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何查询C++共享库(.so)中程序退出时调用的静态析构函数列表

问题解决方案

实现思路

C++ 全局/静态类实例的析构函数确实会注册到共享库独立的 .fini_array 段中,该段存储了程序/库退出时需要顺序执行的函数指针数组,不会和其他库的同段内容混淆,基于此的定位逻辑完全成立:

  • 静态读取思路:直接解析目标 .so 文件的段结构,提取 .fini_array 段的所有指针值,对应到库内的符号偏移,解析出析构函数名
  • 动态读取思路:进程运行时读取目标库的加载基址,加上 .fini_array 段的文件偏移,拿到内存中该段的实际地址,遍历所有函数指针结合进程符号表解析函数名

可用工具与操作步骤

静态排查无需运行程序

使用系统自带的 readelf + nm 组合即可完成解析:

  1. 读取 .fini_array 段的偏移与内容:
    readelf -x .fini_array 目标库.so
    
    64位系统下输出的每8字节十六进制值就是析构函数在库内的偏移地址
  2. 解析偏移对应的符号名,-C 用于还原C++符号的可读性:
    nm -C 目标库.so | grep 上一步拿到的偏移值(去掉前缀0x)
    

运行时动态验证

  • gdb 直接导出段内容:
    程序加载完全部共享库后,执行 info sharedlibrary 拿到目标库的加载基址,结合 readelf 拿到的 .fini_array 段在文件内的偏移,计算出内存中该段的起止地址,执行:
    dump memory fini_array.bin 段起始内存地址 段结束内存地址
    
    导出的二进制文件按8字节遍历即可拿到所有运行时要执行的析构函数地址
  • ltrace 直接跟踪退出阶段的析构调用:
    执行以下命令运行程序,退出时会打印所有库的析构调用,直接过滤重复的类析构即可:
    ltrace -e "~*" -f 你的程序路径
    
    其中 ~* 匹配所有析构函数,-f 跟踪所有库的调用

根治与快速排查补充方案

  • 临时快速定位重复销毁:给可疑类的析构函数增加调试打印,输出实例地址与调用方所属库:
    #include <dlfcn.h>
    #include <iostream>
    #include <typeinfo>
    
    // 析构函数内添加以下代码
    Dl_info dlinfo;
    if (dladdr(__builtin_return_address(0), &dlinfo)) {
        std::cout << "析构实例:" << typeid(*this).name() 
                  << " 内存地址:" << this 
                  << " 调用来源库:" << dlinfo.dli_fname << std::endl;
    }
    
    编译链接时加 -ldl 即可,同一个地址出现两次析构打印就能直接定位两个调用方所属的库
  • 从根源避免问题:所有库编译时加 -fvisibility=hidden 参数,默认隐藏所有符号,仅需要对外暴露的接口显式标注 __attribute__((visibility("default"))),彻底避免不同库意外导出同名类符号导致的实例共享问题。

内容的提问来源于stack exchange,提问作者Serge

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.17 05:45:06