代码之家  ›  专栏  ›  技术社区  ›  NoobInside

当共享库引用静态库中未使用的对象时,为什么静态库中未使用的对象包含在最终二进制文件中?

  •  7
  • NoobInside  · 技术社区  · 7 年前

    总结:

    静态库和共享库之间交叉使用的函数导致静态库的所有对象(甚至未使用!)将包含在最终二进制文件中!

    我想你不明白我的意思吧? :-p

    坐下来阅读下面的全部故事! 为了保护无辜,名字被改了。示例的目标是简单性和再现性。

    挑逗者:有一个 SSCCE公司 可获得的(简短、自包含、正确(可编译),示例: http://www.sscce.org/ )

    一开始,我有:

    • 二进制文件( main )调用函数( fun1a() )存储在静态库中( libsub.a ). 主要的 还具有内部功能( mainsub() ).

    • 静态库( libsub。一 )包含 几个 对象,每个对象具有其他源使用的多个函数。

    编译 主要的 导致二进制具有 仅限 包含引用函数的对象(静态库)的副本。 在下面的示例中, 主要的 将仅包含对象的函数 shared1.o (因为main正在调用 func1a() )以及 不是 的功能 shared2.o (因为没有参考资料)。

    好啊

      main.c                 libsub.a    
    +-------------+        +------------+
    | main        |        | shared1.o  |
    |  func1a()   | <----> |   func1a() |
    |  mainsub()  |        |   func1b() |
    +-------------+        |    ----    |
                           | shared2.o  |
                           |   func2a() |
                           |   func2b() |
                           +------------+
    

    作为一项改进,我希望允许“外部”人员能够覆盖调用的函数 主要的 通过 他们自己的代码 ,无需重新编译 我的 二进制的

    我没有提供源代码,也没有提供静态库。

    为此,我打算提供一个“随时可用”的函数框架源代码。(这叫做用户出口?!) 使用共享/动态库可以做到这一点。 可以覆盖的函数是main的内部函数( mainsub() )或共享功能( func1a() ...) 并将存储在共享库(.so)中,以便在链接期间添加/引用。

    创建了以“c”为前缀的新源,其中将包含“标准”函数的“客户端”版本。 使用(或不使用)覆盖功能的切换超出范围。就这样吧,如果 UE 为true,则进行覆盖。

    cmain.c 是包含以下内容的新源 Client_mainsub() 可以称之为“替代” mainsub()

    cshared1.c 是包含以下内容的新源 Client_func1a() 可以称之为“替代” func1a() . 实际上,所有功能 shared1.c 可能会有他们的替代者 c共享1.c

    cshared2.c 是包含以下内容的新源 Client_func2a() 可以称之为“替代” func2a()

    概述如下:

         main.c                          libsub.a                       clibsub.so
       +-----------------------+     +------------------------+     +--------------------+
       | main                  |     | shared1.o              |     | cshared1.o         |
       |  func1a() {}          |     |   func1a()             |     |   Client_func1a()  |
       |  mainsub()            | <-> |   { if UE              | <-> |    {do ur stuff }  |
       |  { if UE              |     |     Client_func1a()    |     |                    |
       |     Client_mainsub()  |     |     return           } |     | cshared2.o         |
       |     return           }|     |   func1b()             |     |   Client_func2a()  |
       +-----------------------+     |        -------         |    >|    {do ur stuff }  |
                    ^                | shared2.o              |   / +--------------------+
        cmain.c     v                |   func2a()             |  /
       +--------------------+        |   { if UE              | /
       | cmain              |        |     Client_func2a()    |<
       |   Client_mainsub() |        |     return           } |
       |    {do ur stuff }  |        |   func2b()             |
       +--------------------+        +------------------------+
    

    这里再次,作为 主要的 请勿致电 func2a() 也没有 func2b() ,共享的(静态)对象2。二进制文件中不包含o,也没有引用(共享) Client\u func2a() 存在其中之一。 好啊


    最后,仅仅覆盖函数是不够的(或者太多了!)。 我希望外部人员能够调用我的函数(或不调用)。。。但是 产科加强生命支持 允许他们做一些正确的事情 之前 和/或右侧 之后 我的职能。

    因此 func2a() 愚蠢地被替换为 Client\u func2a() ,我们大致可以得到伪代码:

           shared2.c              |          cshared2.c
                        (assume UE=true)
                                  |
    func2a()  {                   |Client_func2a() {
        if UE {}                  |
            Client_func2a()      ==>    do (or not) some stuf PRE call
                                  |
                                  |     if (DOIT)  {            // activate or not standard call
                                  |         UE=false 
                                  |         func2a()            // do standard stuff
                                  |         UE=true
                                  |     } else  
                                  |     {   do ur bespoke stuff }
                                  |     
                                  |     do (or not) some stuf POST call
                                  | }
                                 <==
        } else
          { do standard stuff }
    }
    

    记住这一点 c共享2.c 提供给其他可以(或不可以)在提供的骨架上完成自己工作的人。

    (注:设置 UE公司 在中设置为false并返回为true Client\u func2a() 避免无限循环 func2a() 呼叫!;-))

    现在我的问题来了。

    在这种情况下,结果二进制现在包括 共享2.o 对象尽管 调用的任何函数 shared2.c 也没有 c共享2.c !!!!!

    搜索后,这似乎是因为交叉调用/引用:

    shared2.o contains func2a() that may call Client_func2a()
    cshared2.o contains Client_func2a() that may call func2a()
    

    那为什么 主要的 二进制文件包含shared2。o?

    >dump -Tv main
    
    main:
    
                            ***Loader Section***
    
                            ***Loader Symbol Table Information***
    [Index]      Value      Scn     IMEX Sclass   Type           IMPid Name
    
    [0]     0x00000000    undef      IMP     RW EXTref libc.a(shr_64.o) errno
    [1]     0x00000000    undef      IMP     DS EXTref libc.a(shr_64.o) __mod_init
    [2]     0x00000000    undef      IMP     DS EXTref libc.a(shr_64.o) exit
    [3]     0x00000000    undef      IMP     DS EXTref libc.a(shr_64.o) printf
    [4]     0x00000000    undef      IMP     RW EXTref libc.a(shr_64.o) __n_pthreads
    [5]     0x00000000    undef      IMP     RW EXTref libc.a(shr_64.o) __crt0v
    [6]     0x00000000    undef      IMP     RW EXTref libc.a(shr_64.o) __malloc_user_defined_name
    [7]     0x00000000    undef      IMP     DS EXTref     libcmain.so Client_mainsub1
    [8]     0x00000000    undef      IMP     DS EXTref   libcshared.so Client_func1b
    [9]     0x00000000    undef      IMP     DS EXTref   libcshared.so Client_func1a
    [10]    0x00000000    undef      IMP     DS EXTref   libcshared.so Client_func2b          <<< but why ??? ok bcoz func2b() is referenced ...
    [11]    0x00000000    undef      IMP     DS EXTref   libcshared.so Client_func2a          <<< but why ??? ok bcoz func2a() is referenced ...
    [12]    0x110000b50    .data    ENTpt     DS SECdef        [noIMid] __start
    [13]    0x110000b78    .data      EXP     DS SECdef        [noIMid] func1a
    [14]    0x110000b90    .data      EXP     DS SECdef        [noIMid] func1b
    [15]    0x110000ba8    .data      EXP     DS SECdef        [noIMid] func2b                <<< but why this ? Not a single call is made in main ???
    [16]    0x110000bc0    .data      EXP     DS SECdef        [noIMid] func2a                <<< but why this ? Not a single call is made in main ???
    

    请注意,只需输入注释 func2a() (和 func2b() )解决了链接问题(断开十字架)。。。但这是不可能的,因为我想保留一个共享库!?

    这种行为发生在使用IBM XL C/C++12.1的AIX 7.1上,但在Linux上看起来是一样的(Red Hat 5+GCC 5.4,编译参数有一些小的变化)

    IBM XL C/C++ for AIX, V12.1 (5765-J02, 5725-C72)
    Version: 12.01.0000.0000
    Driver Version: 12.01(C/C++) Level: 120315
    C Front End Version: 12.01(C/C++) Level: 120322
    High-Level Optimizer Version: 12.01(C/C++) and 14.01(Fortran) Level: 120315
    Low-Level Optimizer Version: 12.01(C/C++) and 14.01(Fortran) Level: 120321
    

    所以我想这肯定是一个误会。有人能解释一下吗?


    正如所承诺的,这里是SSCCE。 您可以通过重新创建/下载以下小文件并运行go来重播我的问题。sh(参见脚本中的注释)

    编辑1 :在问题中添加了代码,而不是在建议的外部站点上

    主要的c

    #include <stdio.h>
    #include "inc.h"
    
    extern void func1a (), func1b ();
    
    int UEXIT(char* file, char* func)
    {
        printf("      UEXIT file=<%s>   func=<%s>\n",file,func);
        return 1;   /* always true for testing */
    }
    
    
    main (){
        printf(">>> main\n");
        func1a ();
        mainsub ();
        printf("<<< main\n");
    }
    
    mainsub () {
        printf(">>> mainsub\n");
    
        if(UEXIT("main","mainsub")) {
            Client_mainsub1();
            return;
        }
        printf("<<< mainsub\n");
    }
    

    cmain公司。c

    #include <stdio.h>
    #include "inc.h"
    
    void Client_mainsub1 () {
        printf(">>>>>> Client_mainsub1\n");
        printf("<<<<<< Client_mainsub1\n");
    return;
    }
    

    股份有限公司h

    extern int UEXIT(char * fileName, char * functionName);
    

    共享1.c

    #include <stdio.h>
    #include "inc.h"
    
    void func1a (){
        printf(">>>>> func1a\n");
        if(UEXIT("main","func1a")) {
            Client_func1a();
            return;
        }
        printf("<<<<< func1a\n");
    }
    
    void func1b (){
        printf(">>>>> func1b\n");
        if(UEXIT("main","func1b")){
            Client_func1b();
            return;
        }
        printf("<<<<< func1b\n");
    }
    

    共享2.c

    #include <stdio.h>
    #include "inc.h"
    
    void func2a (){
        printf(">>>>> func2a\n");
        if(UEXIT("main","func2a")) {
            Client_func2a();
            return;
        }
        printf("<<<<< func2a\n");
    }
    
    void func2b (){
        printf(">>>>> func2b\n");
        if(UEXIT("main","func2b")){
            Client_func2b();
            return;
        }
        printf("<<<<< func2b\n");
    }
    

    c共享1.c

    #include <stdio.h>
    #include "inc.h"
    
    void Client_func1a () {
        int standardFunctionCall = 0;
        printf("\t>>>> Client_func1a\n");
        if (standardFunctionCall) {
            func1a();
        }
        printf("\t<<< Client_func1a\n");
        return;
    }
    
    
    void Client_func1b () {
        int standardFunctionCall = 0;
        printf("\t>>>> Client_func1b\n");
        if (standardFunctionCall) {
            func1b();
        }
        printf("\t<<< Client_func1b\n");
        return;
    }
    

    c共享2.c

    #include <stdio.h>
    #include "inc.h"
    
    void Client_func2a () {
        int standardFunctionCall = 0;
        printf("\t>>>> Client_func2a\n");
        if (standardFunctionCall) {
            func2a();           /* !!!!!! comment this to avoid crossed link with shared2.c !!!!! */
        }
        printf("\t<<< Client_func2a\n");
        return;
    }
    
    
    void Client_func2b () {
        int standardFunctionCall = 0;
        printf("\t>>>> Client_func2b\n");
        if (standardFunctionCall) {
            func2b();           /* !!!!!! ALSO comment this to avoid crossed link with shared2.c !!!!! */
        }
        printf("\t<<< Client_func2b\n");
        return;
    }
    

    去上海

    #!/bin/bash
    
    ## usage :
    ## . ./go.sh
    ## so that the redefinition of LIBPATH is propagated to calling ENV ...
    ##    otherwise :  "Dependent module libcshared.so could not be loaded."
    
    
    # default OBJECT_MODE to 64 bit (avoid explicitely setting -X64 options...)
    export OBJECT_MODE=64
    export LIBPATH=.:$LIBPATH
    
    # Compile client functions for target binary
    cc -q64 -c -o cmain.o cmain.c
    
    # (1) Shared lib for internal function
    cc -G -q64 -o libcmain.so cmain.o
    
    
    # Compile common functions
    cc -c shared2.c shared1.c
    
    # Compile client common functions overwrite
    cc -c cshared2.c cshared1.c
    
    
    # (2) Built libsub.a for common functions (STATIC)
    ar -rv libsub.a  shared1.o shared2.o
    
    # (3) Built libcshared.so for client common functions overwrite (SHARED)
    cc -G -q64 -o libcshared.so cshared1.o cshared2.o
    
    
    # Finally built binary using above (1) (2) (3)
    # main only call func1a() , so should only include objects shared1
    # But pragmatically shared2 is also included if cshared2 reference a possible call to func2() in shared2 !!!!????
    #   Check this with "nm main |grep shared2" or "nm main |grep func2" or "dump -Tv main |grep func2"
    cc -q64 -o main main.c -bstatic libsub.a -bshared libcmain.so  libcshared.so
    
    # result is the same without specifying -bstatic or -bshared
    #cc -q64 -o main2 main.c libsub.a libcmain.so  libcshared.so
    
    
    #If I split libcshared.so into libcshared1.so and libcshared2.so it is also the same :
    #cc -G -q64 -o libcshared1.so cshared1.o
    #cc -G -q64 -o libcshared2.so cshared2.o
    #cc -q64 -o main4 main.c -bstatic libsub.a -bshared libcmain.so  libcshared1.so libcshared2.so
    
    #If I do not inlcude libcshared2.so, binary is of course well working, without reference to cshared2 nor shared2 . 
    # So why linker chooses to add STATIC shared2.o only if libcshared2.so is listed ?
    # Is there a way to avoid this add of unused code ?
    #cc -q64 -o main4 main.c -bstatic libsub.a -bshared libcmain.so  libcshared1.so
    

    编辑2 :添加了go的RedHat版本。按要求编写sh脚本

    被刺伤。上海

    ## usage :
    ## . ./gored.sh
    ## so that the redefinition of LD_LIBRARY_PATH is propagated to calling ENV ...
    ##    otherwise :  "Dependent module libcshared.so could not be loaded."
    export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
    
    # Compile client functions for target binary
    gcc -fPIC -c cmain.c
    
    # (1) Shared lib for internal function
    gcc -shared -o libcmain.so cmain.o
    
    
    # Compile common functions
    gcc -c shared2.c shared1.c
    
    # Compile client common functions overwrite
    gcc -fPIC -c cshared2.c cshared1.c
    
    
    # (2) Built libsub.a for common functions (STATIC)
    ar -rv libsub.a  shared1.o shared2.o
    
    # (3) Built libcshared.so for client common functions overwrite (SHARED)
    gcc -shared -o libcshared.so cshared1.o cshared2.o
    
    
    # Finally built binary using above (1) (2) (3)
    # main only call func1a() , so should only include objects shared1
    # But pragmatically shared2 is also included if cshared2 reference a possible call to func2() in shared2 !!!!????
    #   Check this with "nm main |grep shared2" or "nm main |grep func2" or "dump -Tv main |grep func2"
    gcc -o main main.c libcmain.so  libcshared.so libsub.a
    
    #If I split libcshared.so into libcshared1.so and libcshared2.so it is also the same :
    gcc -shared -o libcshared1.so cshared1.o
    gcc -shared -o libcshared2.so cshared2.o
    cc -o main2 main.c libcmain.so  libcshared1.so libcshared2.so libsub.a
    
    #If I do not inlcude libcshared2.so, binary is of course well working, without reference to cshared2 nor shared2 . 
    # So why linker chooses to add STATIC shared2.o only if libcshared2.so is listed ?
    # Is there a way to avoid this add of unused code ?
    cc -o main3 main.c libcmain.so  libcshared1.so libsub.a
    

    或者在这里完整的上述文件(没有gored.sh)在一个单一的。焦油bz2(6KB)。

    https://pastebin.com/KsaqacAu

    只需复制/粘贴一个新文件(例如 poc.uue ). 然后键入

    uudecode poc.uue
    

    你应该 poc。焦油bz2型

    解压、解压进入poc文件夹并运行

    . ./go.sh
    

    然后

    dump -Tv main 
    

    或者如果在红帽下

    nm main
    

    之后的结果示例 gored.sh :

    poc>nm main |grep func2
    *                 U Client_func2a
                     U Client_func2b
    0000000000400924 T func2a
    000000000040095d T func2b
    poc>nm main2 |grep func2
                     U Client_func2a
                     U Client_func2b
    0000000000400934 T func2a
    000000000040096d T func2b
    poc>nm main3 |grep func2
    poc>
    

    编辑3:ASCII艺术!:-)
    下面是未使用对象/引用的“视觉”最终状态,我认为链接器包含这些对象/引用是错误的。或者至少不够聪明,无法检测为未使用。 也许这是正常的,或者有一个选项可以避免在最终二进制中使用未使用的静态代码。这看起来不像周围标记的“未使用!?”那样复杂代码没有链接?不是吗?

         main.c                          libsub.a                                clibsub.so                          
       +-----------------------+       +-------------------------+           +-----------------------------+         
       | main                  |       | +---------------------+ |           | +-------------------------+ |         
       |  func1a();  <-------------\   | |shared1.o            | |           | | cshared1.o              | |         
       |  mainsub()            |    \------>func1a() { <-------------+     /-----> Client_func1a() {     | |         
       |  { if UE {            |       | |   if UE {           | |   |    /  | |      PRE-stuff          | |         
       |     Client_mainsub()  |       | |     Client_func1a() <-----C---/   | |      if (DOIT) {        | |         
       |     return  ^         |       | |     return          | |   |       | |        UE=false         | |         
       |    }        |         |       | |   } else {          | |   +----------------> func1a()         | |         
       |  }          |         |       | |     do std stuff    | |           | |        UE=true          | |         
       +-------------|---------+       | |   }                 | |           | |      } else {           | |         
                     |                 | |                     | |           | |        do bespoke stuff | |         
                     |                 | |  func1b() {         | |           | |      }                  | |         
                     |                 | |     same as above   | |           | |      POST-stuff         | |         
                     |                 | |  }                  | |           | |   }                     | |         
                     |                 | +---------------------+ |           | |   Client_func1b() {}    | |         
                     |                 |                         |           | +-------------------------+ |         
                     |              ***|*******U*N*U*S*E*D**?!***|*****U*N*U*S*E*D**?!*******U*N*U*S*E*D**?!****     
                     |              *  | +---------------------+ |           | +-------------------------+ |   *     
                     |              U  | |shared2.o            | |           | | cshared2.o              | |   U     
                     |              *  | |  func2a() { <-------------+     /-----> Client_func2a() {     | |   *     
                     |              N  | |   if UE {           | |   |    /  | |      PRE-stuff          | |   N     
        cmain.so     |              *  | |     Client_func2a())<-----C---/   | |      if (DOIT) {        | |   *     
       +-------------|------+       U  | |     return          | |   |       | |        UE=false         | |   U     
       | cmain.o     v      |       *  | |   } else {          | |   +----------------> func2a()         | |   *     
       |   Client_mainsub() |       S  | |     do std stuff    | |           | |        UE=true          | |   S     
       |    {do ur stuff }  |       *  | |   }                 | |           | |      } else {           | |   *     
       +--------------------+       E  | |                     | |           | |        do bespoke stuff | |   E     
                                    *  | |  func2b() {         | |           | |      }                  | |   *     
                                    D  | |     same as above   | |           | |      POST-stuff         | |   D     
                                    *  | |  }                  | |           | |   Client_func2b() {}    | |   *     
                                    *  | +---------------------+ |           | +-------------------------+ |   *     
                                    ?  +-------------------------+           +---------------------------+ |   ?     
                                    !                                                                          !     
                                    *********U*N*U*S*E*D**?!*************U*N*U*S*E*D**?!******U*N*U*S*E*D**?!***     
    

    欢迎任何有建设性的回答,让我走上正确的道路。

    谢谢

    2 回复  |  直到 7 年前
        1
  •  5
  •   Mike Kinghan Luchian Grigore    7 年前

    下面是一个非常简单的链接器行为说明,它令人费解 您:

    主要的c

    extern void foo(void);
    
    int main(void)
    {
        foo();
        return 0;
    }
    

    富。c

    #include <stdio.h>
    
    void foo(void)
    {
        puts(__func__);
    }
    

    酒吧c

    #include <stdio.h>
    
    extern void do_bar(void);
    
    void bar(void)
    {
        do_bar();
    }
    

    do\u栏。c

    #include <stdio.h>
    
    void do_bar(void)
    {
        puts(__func__);
    }
    

    让我们将所有这些源文件编译为目标文件:

    $ gcc -Wall -c main.c foo.c bar.c do_bar.c
    

    现在,我们将尝试链接一个程序,如下所示:

    $ gcc -o prog main.o foo.o bar.o
    bar.o: In function `bar':
    bar.c:(.text+0x5): undefined reference to `do_bar'
    

    未定义的函数 do_bar 仅在定义中引用 属于 bar 酒吧 中未引用 完全是程序。那么为什么联动装置会失效呢?

    很简单,这个链接失败是因为 我们让链接器链接 bar.o 进入程序 ; 的确如此;和 酒吧o 包含的定义 酒吧 , 哪些参考文献 do\u栏 ,该链接中未定义。 酒吧 不是 已引用,但 do\u栏 -根据 酒吧 ,它在程序中链接。

    默认情况下,链接器要求链接中引用的任何符号 链接中定义了程序的。如果我们强迫它把定义联系起来 属于 酒吧 ,则需要定义 do\u栏 ,因为没有 定义 do\u栏 实际上没有 得到了 定义 酒吧 . 如果链接 定义 酒吧 ,这并不怀疑我们 需要 要链接它, 然后允许未定义的引用 do\u栏 如果答案是否定的。

    悬挂机构故障当然可以通过以下方式修复:

    $ gcc -o prog main.o foo.o bar.o do_bar.o
    $ ./prog
    foo
    

    现在在本图中,链接 酒吧o 在这个节目中完全是免费的。我们 也可以通过以下方式成功链接 告诉链接器链接 酒吧o .

    gcc -o prog main.o foo.o
    $ ./prog
    foo
    

    酒吧o do_bar.o 都是多余的 正在执行 main ,但该程序只能与两者链接,或两者都不能链接

    但是假设 foo 酒吧 是否在同一文件中定义?

    它们可能在同一个对象文件中定义, foobar.o :

    ld -r -o foobar.o foo.o bar.o
    

    然后:

    $ gcc -o prog main.o foobar.o
    foobar.o: In function `bar':
    (.text+0x18): undefined reference to `do_bar'
    collect2: error: ld returned 1 exit status
    

    现在,链接器无法链接 foo公司 不同时链接 定义 酒吧 . 所以再一次,我们必须将 do\u栏 :

    $ gcc -o prog main.o foobar.o do_bar.o
    $ ./prog
    foo
    

    像这样连在一起, prog 包含的定义 foo公司 , 酒吧 do\u栏 :

    $ nm prog | grep -e foo -e bar
    000000000000065d T bar
    0000000000000669 T do_bar
    000000000000064a T foo
    

    ( T =定义的函数符号)。

    同样地, foo公司 酒吧 可能在同一共享库中定义:

    $ gcc -Wall -fPIC -c foo.c bar.c
    $ gcc -shared -o libfoobar.so foo.o bar.o
    

    然后这个链接:

    $ gcc -o prog main.o -L. -lfoobar -Wl,-rpath=$(pwd)
    ./libfoobar.so: undefined reference to `do_bar'
    collect2: error: ld returned 1 exit status
    

    与之前一样失败,并且可以通过相同的方式修复:

    $ gcc -o prog main.o do_bar.o -L. -lfoobar -Wl,-rpath=$(pwd)
    $ ./prog
    foo
    

    当我们链接共享库时 libfoobar.so 而不是对象 文件 福巴。o 我们的 掠夺 具有不同的符号表:

    $ nm prog | grep -e foo -e bar
    00000000000007aa T do_bar
                 U foo
    

    这次, 掠夺 不包含以下各项的定义 foo公司 酒吧 . 它 包含未定义的引用( U )至 foo公司 ,因为它调用 foo公司 ,和 现在,在运行时,该引用将通过中的定义得到满足 libfoobar。所以 . 甚至没有未定义的引用 酒吧 ,也不应该有,因为该计划 从不打电话 酒吧 .

    但尽管如此, 掠夺 包含 定义 属于 do\u栏 ,现在未被引用 来自符号表中的所有函数。

    这与您自己的SSCCE相呼应,但不太复杂。在您的情况下:

    • 对象文件 libsub.a(shared2.o) 是 链接到程序中,以提供 func2a func2b .

    • 必须找到并链接这些定义,因为它们分别在的定义中引用 Client_func2a Client_func2b ,定义于 libcshared.so .

    • libc共享。所以 必须链接以提供 Client_func1a .

    • 定义 客户端功能1a 必须找到并链接,因为 引用自的定义 func1a .

    • 功能1A 由调用 主要的 .

    这就是为什么我们看到:

    $ nm main | grep func2
                     U Client_func2a
                     U Client_func2b
    00000000004009f7 T func2a
    0000000000400a30 T func2b
    

    在程序的符号表中。

    将定义链接到 它不调用的函数。它通常以我们所看到的方式发生:联系, 递归解析以开头的符号引用 主要的 ,发现它需要定义 属于 f ,它只能通过链接某些对象文件来获取 file.o ,和 文件o 它还链接了函数的定义 g ,从未调用。

    什么 很奇怪的是,最终会有一个像 主要的 就像我上一个版本的 掠夺 , 其中包含未调用函数的定义(例如 do\u栏 )链接到解决的 来自另一个未调用函数定义的引用(例如。 酒吧 )那就是 在程序中定义。 即使有冗余的函数定义,通常我们也可以将它们链接回一个或多个 链接中的对象文件 第一 冗余定义与 一些必要的定义。

    这种奇怪的现象是由以下情况引起的:

    gcc -o prog main.o do_bar.o -L. -lfoobar -Wl,-rpath=$(pwd)
    

    因为必须链接的第一个冗余函数定义( 酒吧 )是的 通过链接提供 共享库 , libfoobar。所以 ,而 do\u栏 这是 酒吧 在该共享库或任何其他共享库中, 但是在 对象文件 .

    定义 酒吧 由提供 libfoobar。所以 当 程序与该共享库链接。它不会与 程序这就是动态链接的本质。但是 链接-是否是独立的对象文件,如 do\u栏。o 或一个 链接器从存档中提取的 libsub。a(共享2.o) -只能是 物理链接到程序中。所以多余的 do\u栏 出现在 符号表 掠夺 . 但是多余的 酒吧 ,这就解释了为什么 do\u栏 有, 不是的 那里在的符号表中 libfoobar。所以 .

    当您在程序中发现死代码时,您可能希望链接器更智能。 通常 可以 更聪明一些,但要付出一些额外的努力。你需要让它 垃圾收集区 , 在此之前,您需要要求编译器通过生成 数据部分 功能部分 在对象文件中。看见 How to remove unused C/C++ symbols with GCC and ld? the answer

    但这种修剪死代码的方法在异常情况下不起作用 死代码在程序中链接,以满足来自 共享库 悬挂机构所需。链接器只能从中递归垃圾收集未使用的节 那些 输出 输入到程序中,它只输出输入的部分 来自对象文件,而不是来自要动态链接的共享库。

    避免您的 主要的 还有我的 掠夺 不要做那种奇怪的联系 在哪儿 共享库将包含程序不调用但必须调用的未定义引用 通过将死目标代码链接到 程序 .

    相反,在构建共享库时,要么不要在其中留下任何未定义的引用, 或者只留下未定义的引用,这些引用应通过其自身的动态依赖性来满足。

    所以,建立我的 libfoobar。所以 是:

    $ gcc -shared -o libfoobar.so foo.o bar.o do_bar.o
    

    这为我提供了一个共享库,其API为:

    void foo(void);
    void bar(void);
    

    对于想要其中一个或两个的人,没有未定义的引用。然后 我构建的程序是 foo公司 :

    $ gcc -o prog main.o -L. -lfoobar -Wl,-rpath=$(pwd)
    $ ./prog
    foo
    

    而且它不包含死代码:

    $ nm prog | grep -e foo -e bar
                     U foo
    

    同样,如果您构建 libshared.so 没有未定义的引用,例如:

    $ gcc -c -fPIC shared2.c shared1.c
    $ ar -crs libsub.a  shared1.o shared2.o
    $ gcc -shared -o libcshared.so cshared1.o cshared2.o -L. -lsub
    

    然后链接您的程序:

    $ gcc -o main main.c libcmain.so  libcshared.so
    

    它也不会有死代码:

    $ nm main | grep func
                     U func1a
    

    如果你不喜欢 libsub.a(shared1.o) libsub。a(共享2.o) 物理链接到 libc共享。所以 通过此解决方案,然后 另外 链接共享库的传统方法:保留所有 func* 中未定义的函数 libc共享。所以 :制造 libsub 而且 一个共享库,它是 libc共享。所以 .

        2
  •  1
  •   Nicole Trudeau    6 年前

    如果您只是想摆脱未使用的函数,那么可能不需要使用共享库。对于GCC,请尝试 this . 对于XL,更换 -fdata-sections -ffunction-sections 具有 -qfuncsect . 一个重要的相关主题是导出/导入列表和可见性选项的使用。这些控制链接到库中的额外符号是否导出到库之外。看见 here 了解更多信息。