美文网首页C++
控制目标文件符号可见性

控制目标文件符号可见性

作者: 呆呆的张先生 | 来源:发表于2018-10-12 23:36 被阅读14次

    控制符号可见性

    实验环境

    系统: 16.04.1-Ubuntu
    编译器:gnu 5.4.0

    参考

    第 1 部分 - 符号可见性简介

    nm工具使用

    使用NM查看目标文件的符号列表

    // file : symtest.hpp
    class SymTest
    {
        SymTest();
        SymTest(int x);
        ~SymTest();
        void foo();
    };
    
    // file : symtest.cc
    #include "symtest.hpp"
    SymTest::SymTest()       {}
    SymTest::SymTest( int x) {}
    SymTest::~SymTest()      {}
    void SymTest::foo()      {}
    
    > g++ -g -shared -o libsymtest.so symtest.cc // 编译动态库
    > g++ -g -c symtest.cc -o libsymtest.o; ar rvs libsymtest.a libsymtest.o // 编译静态库
    
    • so 编译时,加上 -g 编译信息
    • -g 仅显示外部符号 -C 显示用户级名字
    $ nm -g libsymtest.a 
    
    libsymtest.o:
    0000000000000026 T _ZN7SymTest3fooEv
    000000000000000c T _ZN7SymTestC1Ei
    0000000000000000 T _ZN7SymTestC1Ev
    000000000000000c T _ZN7SymTestC2Ei
    0000000000000000 T _ZN7SymTestC2Ev
    000000000000001a T _ZN7SymTestD1Ev
    000000000000001a T _ZN7SymTestD2Ev
    $ nm -C libsymtest.a 
    
    libsymtest.o:
    0000000000000026 T SymTest::foo()
    000000000000000c T SymTest::SymTest(int)
    0000000000000000 T SymTest::SymTest()
    000000000000000c T SymTest::SymTest(int)
    0000000000000000 T SymTest::SymTest()
    000000000000001a T SymTest::~SymTest()
    000000000000001a T SymTest::~SymTest()
    

    ps. 构造函数和系够函数会出现两次,见使用NM查看目标文件的符号列表

    • -A 符号前显示二进制文件名称
    $ nm -C -A libsymtest.a
    
    libsymtest.a:libsymtest.o:0000000000000026 T SymTest::foo()
    libsymtest.a:libsymtest.o:000000000000000c T SymTest::SymTest(int)
    libsymtest.a:libsymtest.o:0000000000000000 T SymTest::SymTest()
    libsymtest.a:libsymtest.o:000000000000000c T SymTest::SymTest(int)
    libsymtest.a:libsymtest.o:0000000000000000 T SymTest::SymTest()
    libsymtest.a:libsymtest.o:000000000000001a T SymTest::~SymTest()
    libsymtest.a:libsymtest.o:000000000000001a T SymTest::~SymTest()
    
    • -l 显示行号,需要配合 -g 调试选项使用
    $ nm -C -A -l libsymtest.a
     
    libsymtest.a:libsymtest.o:0000000000000026 T SymTest::foo()
    libsymtest.a:libsymtest.o:000000000000000c T SymTest::SymTest(int)  /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:4
    libsymtest.a:libsymtest.o:0000000000000000 T SymTest::SymTest() /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:3
    libsymtest.a:libsymtest.o:000000000000000c T SymTest::SymTest(int)
    libsymtest.a:libsymtest.o:0000000000000000 T SymTest::SymTest()
    libsymtest.a:libsymtest.o:000000000000001a T SymTest::~SymTest()    /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:5
    libsymtest.a:libsymtest.o:000000000000001a T SymTest::~SymTest()
    

    符号类型

    符号类型一列有两个字母时,小写字母代表局部符号,大写则为全局/外部符号。

    ‘A’ - 符号的值为连接期间不能改变
    ‘B b’ - BSS 段, 存放未初始化的数据
    ‘C’ - comm symbols ?
    'D d' - 初始化的数据段
    ‘G g’ - 存放小对象的初始化数据段,某些目标文件格式支持小对象的快速访问,比如全局的 int 类型可以存放在此处,而大型全局数组不能存放在这里
    'i' - 看不懂 ?
    "N" - 调试相关的符号
    "p" - 该符号在堆栈展开部分
    "R r" - 常量,只读数据段
    "S s" - 存放小对象的未初始化数据段
    "T t" - text段,代码段
    "U" - 未定义符号
    "u" - 独占的全局符号,GNU对ELF标准的拓展,链接过程需要保证该符号是独占的
    "V v" - 看不懂?
    "W w" - 看不懂?
    "-" - a.out 文件中的调试信息相关
    "?" - 看不懂?

    符号及符号可见性是什么?

    符号概念与对象文件(.o)、链接等概念相关,对于 C/C++ 语言,用户定义的变量、函数名称、及命名空间、类/结构/名称等,都会在对象文件中生成符号,这些符号对于链接器(linker)确定不同模块(对象文件、动态共享库、可执行文件)是否会共享相同的数据或代码很有用。 ps. 水平有限,此处只关注函数的符号可见性

    举例

    nm命令中符号类型详解

    // file : demo.cc
    // description : demo symbol of elements in C
    
    int a1;
    int a2 = 1;
    const int a3 = 1;
    static int sa = 1;
    static int funA() {return 1;}
    int funB() {return 2;}
    
    
    $ g++ -g -c demo.cc 
    $ nm -C -A -l demo.o 
    demo.o:0000000000000000 B global BBS段-未初始化变量   a1   /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:1
    demo.o:0000000000000000 D global 数据段-初始化变量    a2    /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:5
    demo.o:000000000000000b T global 代码段             funB() /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:6
    demo.o:0000000000000000 r local  常量               a3    /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:5
    demo.o:0000000000000004 d local  数据段-初始化变量    sa    /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:5
    demo.o:0000000000000000 t local  代码段             funA() /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:5
    
    
    > readelf -s demo.o
    Symbol table '.symtab' contains 20 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         6: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 _ZL2a3
         7: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    2 _ZL2sa
         8: 0000000000000000    11 FUNC    LOCAL  DEFAULT    1 _ZL4funAv
        17: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 a1
        18: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    2 a2
        19: 000000000000000b    11 FUNC    GLOBAL DEFAULT    1 _Z4funBv
    

    为什么控制符号可见性?

    编译器默认导出所有的符号,存在很高的风险,链接时可能会导致符号冲突,只要有人链接一个跟您的库具有相同符号名称的库,当进行链接器解析时,该库就可能会意外地覆盖您自己的符号。

    导出所有的符号,会增加动态库的加载和链接时间。

    举例

    > g++ -c demo.cc                          // 生成目标文件 demo.o
    > cp demo.cc demo1.cc                     // 此时 demo1.cc 同样具有符号 a、sa、funA 和 funB
    > g++ -c demo1.cc                         // 生成目标文件 demo1.o
    > g++ -shared -o demo.so demo.o demo1.o   // 链接生成动态库 demo.so, 产生符号冲突
    demo1.o:(.data+0x0): `a'被多次定义
    demo.o:(.data+0x0):第一次在此定义
    demo1.o:在函数‘funB()’中:
    demo1.cc:(.text+0xb): `funB()'被多次定义
    demo.o:demo.cc:(.text+0xb):第一次在此定义
    collect2: error: ld returned 1 exit status
    

    题外话 - 头文件中放置函数的定义引起符号冲突

    // file: demo1.hpp
    #ifndef __demo1_hpp_
    #define __demo1_hpp_
    int test1() { return 1; }
    #endif//#ifndef __demo1_hpp_
    
    // file: demo2.cc
    #include "demo1.hpp"
    int test2()
    { return test1(); }
    
    // file: demo3.cc
    #include "demo1.hpp"
    int test3()
    { return test1(); }
    
    > g++ -fPIC -shared -o demo.so demo2.cc demo3.cc
    /tmp/cchKMCYz.o:在函数‘test1()’中:
    demo3.cc:(.text+0x0): `test1()'被多次定义
    /tmp/ccLp8ynr.o:demo2.cc:(.text+0x0):第一次在此定义
    collect2: error: ld returned 1 exit status
    
    // 1, demo1.hpp 中的 `#ifndef ... #define ...#endif` 不能阻止此类符号冲突
    // 2, 将 demo1.hpp 中的函数 test1 改为 inline  可以修复该编译错误
    // 3, 也可以将 demo1.hpp 中的函数 test1 改为 static [inline], 会生成两个版本的 test1  
    

    控制符号可见性的方式

    1, static 关键字

    如上所示 demo.cc 中, static 改变可见性

    2, (仅针对gnu)visibility属性

    // file : demo.cc
    // description : demo visibility change the visiable of symbol
    
    #if defined(__GNUC__) && ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3))
    #define NP_VISIBILITY_DEFAULT __attribute__((visibility("default")))
    #else
    #define NP_VISIBILITY_DEFAULT
    #endif
    
    #define NP_EXPORT(__type) NP_VISIBILITY_DEFAULT __type
    
    int a1; 
    int a2 = 1;
    const int a3 = 1;
    int sa = 1;
    int funA() {return 1;} 
    NP_EXPORT(int) funB() {return 2;} 
    
    $ g++ -g -shared -o libdemo.so -fvisibility=hidden demo.cc 
    $ nm -C -a -l libdemo.so 
    
    000000000020102c b a1   /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:9
    0000000000201020 d a2   /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:10
    0000000000201024 d sa   /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:12
    0000000000000600 t funA()   /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:13
    000000000000060b T funB()   /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/demo.cc:14
    0000000000000624 r a3
    

    3, 使用导出列表

    // file : demo.cc
    // description : demo version-script change the visiable of symbol
    
    int a = 1;
    int sa = 1;
    int funA() {return 1;}
    int funB() {return 2;}
    
    /*
    // file : exportmap
    // description : define which is global or local
    {
    global:
    a;         // var a is global, D
    _Z4funAv;  // funA is global, T
    local: *;  // default is local
    };
    */
    
    > g++ -shared -o demo.so  demo.cc -fPIC -Wl,--version-script=exportmap 
    > nm demo.so
    0000000000201020 D a
    0000000000201024 d sa
    0000000000000570 T _Z4funAv
    000000000000057b t _Z4funBv
    

    cpp 文件

    // file : symtest.hpp
    #if defined(__GNUC__) && ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3))
    #define NP_EXPORT __attribute__((visibility("default")))
    #else
    #define NP_EXPORT
    #endif
    
    class SymTest
    {
        SymTest() ;
        NP_EXPORT SymTest(int x); 
        ~SymTest();
        void foo();
    };
    
    // file : symtest.cc
    #include "symtest.hpp"
    SymTest::SymTest()       {}
    SymTest::SymTest( int x) {}
    SymTest::~SymTest()      {}
    void SymTest::foo()      {}
    
    $ g++ -g -fvisibility=hidden -shared -o libsymtest.so symtest.cc
    $ nm -C -A -l libsymtest.so 
    
    libsymtest.so:0000000000000656 t SymTest::foo() /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:6
    libsymtest.so:000000000000063c T SymTest::SymTest(int)  /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:4
    libsymtest.so:0000000000000630 t SymTest::SymTest() /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:3
    libsymtest.so:000000000000063c T SymTest::SymTest(int)
    libsymtest.so:0000000000000630 t SymTest::SymTest()
    libsymtest.so:000000000000064a t SymTest::~SymTest()    /home/zhanghl/001_cpp/005_snipts/0004_gnu_symbol_visiable/with_cpp/symtest.cc:5
    libsymtest.so:000000000000064a t SymTest::~SymTest()
    

    相关文章

      网友评论

        本文标题:控制目标文件符号可见性

        本文链接:https://www.haomeiwen.com/subject/ydunaftx.html