zoukankan      html  css  js  c++  java
  • GCC编译之如何控制共享文件导出符号

    背景

    前不久在调试一个与导出符号相关的bug,问题大概如此:

    模块A.so在堆上构造了一个对象即 CTest *one = new CTest; , CTestA.so定义,后来使用one->AMemFunc(),即调用一个成员函数时崩溃。原来在另一个模块B.so(A.so先加载)中,也有一个同名的CTest定义,但是却没有一个叫AMemFunc的成员函数,因此崩溃。

    那为什么A.soCTest会被解析到B.so呢?

    全局符号介入

    这种一个共享对象里面的全局符号被另一个共享对象的同名全局符号覆盖的现象又被称为共享对象全局符号介入(Global Symbol Interpose)。对于全局符号介入这个问题,linux下的动态连接器是这样处理的:它定义了一个规则,那就是当一个符号需要被加入全局符号表时,如果相同的符号名已经存在,则后加入的符号被忽略。

    其实,在上述的情形里,B.soCTest是不需要导出的,GCC编译的so默认是导出所有符号,这与MS VS刚好相反。如何控制so文件的导出符号呢?

    控制共享文件的导出符号

    常用方法有两种:

    1.使用GCC C++ visibility 支持。

    把__attribute__ ((visibility ("default"))) 放置在你希望导出的struct,class,function的声明处,然后修改你的GCC构建参数,使用-fvisibility=hidden参数编译每一个源代码文件。GCC编译源代码文件的visibility默认属性是public,所以默认所有符号都导出来了,设置为hidden后然后在需要导出的地方加__attribute__ ((visibility ("default"))),以达到控制导出符号的目的。在跨平台代码的编写中,常使用下面类似的宏定义来简化上述过程。

    #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

     

    extern "C" DLL_PUBLIC void function(int a);

    class DLL_PUBLIC SomeClass

    {

       int c;

       DLL_LOCAL void privateMethod();  // Only for use within this DSO

    public:

       Person(int _c) : c(_c) { }

       static void foo(int a);

    };

    2.使用链接参数 --retain-symbols-file 控制静态符号表--version-script 控制动态符号表,后面都是接含有导出符号的文件的名字。这两个参数在移植windows下的动态库很有用,windows下的DEF文件能控制导出符号,我们可以在linux下的构建脚本中解析DEF生成一个导出符号文件,然后作为retain-symbols-fileversion-script的参数。示例如下:

    这是a1.c文件

    #include <stdio.h>
    #include <stdlib.h>
    
    void func_1()
    {
    	printf("a1 :: func_1\n");
    }
    
    void func_2()
    {
    	printf("a1 :: func_2\n");
    }
    
    void func_3()
    {
    	printf("a1 :: func_3\n");
    }

    这是a1.sym文件,控制静态导出符号

    func_1
    func_3
    

    这是a1.map文件,控制动态导出符号

    global表示要导出的符号,local表示不导出的,*表示都不导出

    {
    global:
      func_1;
      func_2;
    local: *;
    };
    

    生成共享库

    gcc a1.c -shared -o liba1.so -Wl,--retain-symbols-file=a1.sym -Wl,--version-script=a1.map
    

    查看符号表

    readelf -s liba1.so
    Symbol table '.dynsym' contains 7 entries:
       Num:    Value  Size Type    Bind   Vis      Ndx Name
         0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 00000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.1.3 (2)
         2: 00000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.0 (3)
         3: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
         4: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
         5: 00000400    20 FUNC    GLOBAL DEFAULT   11 func_2
         6: 000003ec    20 FUNC    GLOBAL DEFAULT   11 func_1
    
    
    Symbol table '.symtab' contains 27 entries:
       Num:    Value  Size Type    Bind   Vis      Ndx Name
         0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 00000114     0 SECTION LOCAL  DEFAULT    1 
         2: 00000138     0 SECTION LOCAL  DEFAULT    2 
         3: 0000015c     0 SECTION LOCAL  DEFAULT    3 
         4: 000001cc     0 SECTION LOCAL  DEFAULT    4 
         5: 00000232     0 SECTION LOCAL  DEFAULT    5 
         6: 00000240     0 SECTION LOCAL  DEFAULT    6 
         7: 00000270     0 SECTION LOCAL  DEFAULT    7 
         8: 000002c0     0 SECTION LOCAL  DEFAULT    8 
         9: 000002d0     0 SECTION LOCAL  DEFAULT    9 
        10: 00000300     0 SECTION LOCAL  DEFAULT   10 
        11: 00000330     0 SECTION LOCAL  DEFAULT   11 
        12: 00000468     0 SECTION LOCAL  DEFAULT   12 
        13: 00000482     0 SECTION LOCAL  DEFAULT   13 
        14: 000004ac     0 SECTION LOCAL  DEFAULT   14 
        15: 000004d8     0 SECTION LOCAL  DEFAULT   15 
        16: 00001f0c     0 SECTION LOCAL  DEFAULT   16 
        17: 00001f14     0 SECTION LOCAL  DEFAULT   17 
        18: 00001f1c     0 SECTION LOCAL  DEFAULT   18 
        19: 00001f20     0 SECTION LOCAL  DEFAULT   19 
        20: 00001fe8     0 SECTION LOCAL  DEFAULT   20 
        21: 00001ff4     0 SECTION LOCAL  DEFAULT   21 
        22: 00002008     0 SECTION LOCAL  DEFAULT   22 
        23: 0000200c     0 SECTION LOCAL  DEFAULT   23 
        24: 00000000     0 SECTION LOCAL  DEFAULT   24 
        25: 00000414    20 FUNC    LOCAL  DEFAULT   11 func_3
        26: 000003ec    20 FUNC    GLOBAL DEFAULT   11 func_1
    

    显而易见:动态符号表中是函数func_2,func_1,静态符号表中是func_3,func_1

    参考

    1.程序员的自我修养---连接,装载

    2.http://gcc.gnu.org/wiki/Visibility 

    3.version-script

  • 相关阅读:
    线程(中)
    线程
    生产者消费者模型
    进程的常用方法
    HTML中head与body标签
    HTTP协议
    mysql:视图,触发器,事务,存储过程,函数。
    关于MySQL中pymysql安装的问题。
    MySQL多表查询,pymysql模块。
    MySQL之单表查询
  • 原文地址:https://www.cnblogs.com/persistentsnail/p/3294855.html
Copyright © 2011-2022 走看看