GCC 怎么控制 .so/.dll 中导出的符号?这个方法比 --fvisibility 更好用!

在使用 GCC / G++ / MinGW 编译动态链接库的时候,我们常常会遇到需要控制导出符号的问题。比如,有时候我们想把一大堆依赖库塞进一个 .dll 文件里,这样就不会出现 .dll 依赖 .dll 的套娃现象,但是这样一来,编译器会自动把静态库里的所有函数都导出到 dll 里,然后动态链接库里就有了一大堆我们不需要的符号。

举个实际例子,我前段时间编译了 libass 的 DLL,它依赖 fontconfig,fontconfig 又依赖 expat,然后 libass 又依赖 libpng,libpng 依赖 zlib,导致最终生成的 DLL 里有一大堆 XML、zlib、png 和 FT_* 函数:

这个时候该怎么办呢?网上的文章大多会让你使用 gcc 的 -fvisibility=hidden 来控制符号的可见性。将所有其它符号设为不可见,就可以控制要导出的符号了。但是,实际编程中发现这样做需要大量修改源代码,加上 __declspec(dllexport) 定义。有没有更简单的方法呢?有,那就是今天要介绍的 version script。

version script 是 GNU 编译工具链中的一个非常有用的功能,它允许在编译的时候,使用文本文件来控制动态链接库中需要导出的符号,除此之外还有不少高级功能。version script 完整的规范定义在了 ld 工具的手册里(ftp.gnu.org/old-gnu/Man...),这里我们只是使用它控制导出符号的功能,因此只使用它的简化版。

下面我们就以一个实际例子来说明如何使用 version script 来控制符号导出。

这里我们以 libass 为例,首先,在 libass 的源代码根目录下新建一个文件,我们将它命名为 version-script.txt,内容如下:

cpp 复制代码
{
    global:
        ass_library_init;
    
    local:
        *;
};

上述代码表示的意思是:

  1. 我们将 ass_library_init 指定为 global,所以最终导出的 dll 文件里就只会出现 ass_library_init 这一个符号;
  2. 所有其他符号都被指定为 local,也就是不会被导出。

然后,我们在编译时,加上一个参数 -Wl,--version-script=version-script.txt,这样就可以使用 version script 来控制符号的导出了。

检查一下输出的结果:

简单吧?以这种方法,我们就可以轻松地控制导出的符号,无需对源代码进行大量的修改。同时,version script 还有很多高级功能,包括给库指定不同版本等等。感兴趣的读者可以自行阅读 ld 工具手册以了解更多内容。美团也写过一篇关于优化 .so 动态链接库的文章,认为 version script 方式有一些额外的好处:

  1. version script 方式可以控制编译进 so 的静态库的符号是否导出,visibility 和 attribute 方式都无法做到这一点。
  2. visibility 结合 attribute 方式需要在源码中标明每个需要导出的符号,对于导出符号较多的项目来说是很繁杂的。version script 把需要导出的符号统一地放到了一起,能够直观方便地查看和修改,对导出符号较多的项目也非常友好。
  3. version script 支持通配符,* 代表0个或者多个字符,? 代表单个字符。比如 my*; 就代表所有以 my 开头的符号。有了通配符的支持,配置 version script 会更加方便。
  4. 还有非常特殊的一点,version script 方式可以删除 __bss_start 这样的一些符号(这是链接器默认加上的符号)。

文中所使用的 DependenciesGui 工具可以在这里下载

相关推荐
兵哥工控20 分钟前
MFC用高精度计时器实现五段时序控制器
c++·mfc·高精度计时器·时序控制器
眠りたいです1 小时前
基于脚手架微服务的视频点播系统-服务端开发部分(补充)文件子服务问题修正
c++·微服务·云原生·架构
ULTRA??1 小时前
各种排序算法时间复杂度分析和实现和优势
c++·python·算法·排序算法
博语小屋1 小时前
简单线程池实现(单例模式)
linux·开发语言·c++·单例模式
墨雪不会编程1 小时前
C++基础语法篇八 ——【类型转换、再探构造、友元】
java·开发语言·c++
yuuki2332332 小时前
【C++】内存管理
java·c++·算法
刃神太酷啦2 小时前
Linux 进程核心原理精讲:从体系结构到实战操作(含 fork / 状态 / 优先级)----《Hello Linux!》(6)
java·linux·运维·c语言·c++·算法·leetcode
一个不知名程序员www2 小时前
算法学习入门---二叉树
c++·算法
小李小李快乐不已2 小时前
数组&&矩阵理论基础
数据结构·c++·线性代数·算法·leetcode·矩阵
coderxiaohan2 小时前
【C++】用哈希表封装unordered_map和unordered_set
开发语言·c++·散列表