• GCC的符号可见性——解决多个库同名符号冲突问题


    引用自:https://github.com/wwbmmm/blog/wiki/gcc_visibility

    问题

    最近项目遇到一些问题,场景如下

    主程序依赖了两个库libA的funcA函数和libB的funcB函数。示意的代码(main.cpp)如下:

    #include <cstdio>
    
    int funcA(int, int);
    int funcB(int, int);
    
    int main() {
        printf("%d,", funcA(2, 1));
        printf("%d
    ", funcB(2, 1));
        return 0;
    }

    libA示意实现(libA.cpp)如下:

    int subfunc(int a, int b) {
        return a + b;
    }
    
    int funcA(int a, int b) {
        return subfunc(a, b);
    }

    libB示意实现(libB.cpp)如下:

    int subfunc(int a, int b) {
        return a - b;
    }
    
    int funcB(int a, int b) {
        return subfunc(a, b);
    }

    可见funcA调用了libA中的内部函数subfunc,funcB调用了libB中的内部函数subfunc,这两个subfunc实现不同,但不幸的是名字不小心起得一样了

    这时我们尝试编译并运行:

    g++ -fPIC libA.cpp -shared -o libA.so
    g++ -fPIC libB.cpp -shared -o libB.so
    
    g++ main.cpp libA.so libB.so -o main
    
    export LD_LIBRARY_PATH=.
    ./main

    我们期望的结果是3,1(funcA和funcB各自调用不同的subfunc实现),

    实际得到的结果是3,3(funcA和funcB都调用了libA中的subfunc实现)

    原因

    我们通过readelf来查看符号:

    $ readelf -a libA.so | grep subfunc
    000000200a60  000200000007 R_X86_64_JUMP_SLO 0000000000000708 _Z7subfuncii + 0
         2: 0000000000000708    20 FUNC    GLOBAL DEFAULT   10 _Z7subfuncii
        45: 0000000000000708    20 FUNC    GLOBAL DEFAULT   10 _Z7subfuncii
    
    $ readelf -a libB.so | grep subfunc 
    000000200a60  000200000007 R_X86_64_JUMP_SLO 0000000000000708 _Z7subfuncii + 0
         2: 0000000000000708    22 FUNC    GLOBAL DEFAULT   10 _Z7subfuncii
        45: 0000000000000708    22 FUNC    GLOBAL DEFAULT   10 _Z7subfuncii

    可见libA和libB里面都有subfunc符号,名字完全一样,而且都是GLOBAL的

    GLOBAL的符号即全局的符号,同名的全局符号会被认为是同一个符号,由于main先加载了libA,得到了libA中的subfunc符号,再加载libB时,就把libB中的subfunc忽略了。

    解决方案

    这其实是符号的可见性(Symbol Visibility)问题,既然有GLOBAL符号,那自然会有LOCAL符号,LOCAL的符号只在当前lib可见,全局不可见。

    如何将符号变成LOCAL的呢,最直接的就是加上visibility为hidden的标志,修改后的libA.cpp:

    __attribute__ ((visibility ("hidden"))) int subfunc(int a, int b) {
        return a + b;
    }
    
    int funcA(int a, int b) {
        return subfunc(a, b);
    }

    再重新编译执行,可以得到结果为3,1,成功!这里再查看一下libA的符号:

    $ readelf -a libA.so | grep subfunc
        40: 00000000000006a8    20 FUNC    LOCAL  DEFAULT   10 _Z7subfuncii

    可见subfunc符号已经变成了LOCAL

    默认LOCAL

    上面的方法可以解决问题,但是,实际情况往往是,libA里面有很多的内部函数,而暴露给外部的只有少数,能不能指定少数符号为GLOBAL,其它的都是LOCAL呢?答案是肯定的,修改libA.cpp如下:

    int subfunc(int a, int b) {
        return a + b;
    }
    
    __attribute__ ((visibility ("default"))) int funcA(int a, int b) {
        return subfunc(a, b);
    }

    这时,libA的编译参数需要加上-fvisibility=hidden

    g++ -fPIC libA.cpp -shared -fvisibility=hidden -o libA.so

    同样可以解决问题。

    跨平台兼容性

    windows平台对于符号的行为是不一样的,windows默认动态库里符号是LOCAL的,通过__declspec(dllexport)来声明GLOBAL符号,所以可以用下面的方式来兼容:

    #if defined _WIN32 || defined __CYGWIN__
      #ifdef BUILDING_DLL
        #ifdef __GNUC__
          #define DLL_PUBLIC __attribute__ ((dllexport))
        #else
          #define DLL_PUBLIC __declspec(dllexport) // Note: actually gcc seems to also supports this syntax.
        #endif
      #else
        #ifdef __GNUC__
          #define DLL_PUBLIC __attribute__ ((dllimport))
        #else
          #define DLL_PUBLIC __declspec(dllimport) // Note: actually gcc seems to also supports this syntax.
        #endif
      #endif
      #define DLL_LOCAL
    #else
      #if __GNUC__ >= 4
        #define DLL_PUBLIC __attribute__ ((visibility ("default")))
        #define DLL_LOCAL  __attribute__ ((visibility ("hidden")))
      #else
        #define DLL_PUBLIC
        #define DLL_LOCAL
      #endif
    #endif

    隐藏外部依赖的符号

    我遇到的实际情况比上面更复杂一些,subfunc并不是在libA中实现的,而是在另一个外部库libsubfunc.a中实现的。libA通过包含头文件来获取到这个函数:

    #include "subfunc.h"
    
    int funcA(int a, int b) {
        return subfunc(a, b);
    }

    上面的-fvisibility仅对实现生效,不能对声明生效。但libsubfunc.a是第三方库,我们不能去改它的代码,也不能改它的头文件,对于这种情况,gcc提供了下面方式来支持:

    #pragma GCC visibility push(hidden)
    #include "subfunc.h"
    #pragma GCC visibility pop
    
    int funcA(int a, int b) {
        return subfunc(a, b);
    }

    这种方式更方便灵活。

    参考文档

  • 相关阅读:
    写了10000条Airtest截图脚本总结出来的截图经验,赶紧收藏!
    自动化测试实操案例详解 | iOS应用篇
    Photoshop 2020特别版,内置多款实用插件,功能强大
    vue click.stop阻止点击事件继续传播
    CSS图标与文字对齐的两种方法
    为什么像王者荣耀这样的游戏Server不愿意使用微服务?
    13 张图解 Java 中的内存模型
    记住没:永远不要在 MySQL 中使用 UTF-8
    牛x!一个比传统数据库快 100-1000 倍的数据库!
    为什么我不建议你用去 “ ! = null " 做判空?
  • 原文地址:https://www.cnblogs.com/langqi250/p/7516230.html
Copyright © 2020-2023  润新知