【问题标题】:Remove dead code when linking static library into dynamic library将静态库链接到动态库时删除死代码
【发布时间】:2018-11-25 15:44:25
【问题描述】:

假设我有以下文件:

libmy_static_lib.c:

#include <stdio.h>
void func1(void){
    printf("func1() called from a static library\n");
}
void unused_func1(void){
    printf("printing from the unused function1\n");
}
void unused_func2(void){
    printf("printing from unused function2\n");
}

libmy_static_lib.h:

void func(void);
void unused_func1(void);
void unused_func2(void);

my_prog.c:

#include "libmy_static_lib.h"
#include <stdio.h>
void func_in_my_prog()
{
    printf("in my prog\n");
    func1();

}

这是我链接库的方式:

# build the static library libmy_static_lib.a
gcc -fPIC -c -fdata-sections --function-sections -c libmy_static_lib.c -o libmy_static_lib.o
ar rcs libmy_static_lib.a libmy_static_lib.o

# build libmy_static_lib.a into a new shared library
gcc -fPIC -c ./my_prog.c -o ./my_prog.o
gcc -Wl,--gc-sections -shared -m64 -o libmy_shared_lib.so ./my_prog.o -L. -l:libmy_static_lib.a

libmy_static_lib.c中有2个函数没有用到,我认为来自this post

gcc fdata-sections --function-sections

应该为每个函数创建一个符号,并且

gcc -Wl,--gc-sections

链接时应删除未使用的符号

但是当我运行时

nm libmy_shared_lib.so

这表明这两个未使用的函数也被链接到共享库中。

关于如何让 gcc 自动删除未使用的函数有什么建议吗?

编辑: 如果我将静态库直接链接到可执行文件,我可以使用 gcc 的上述选项来删除未使用的函数。但如果我将静态库链接到共享库,它不会删除未使用的函数。

【问题讨论】:

  • 更便携的方法是将每个函数放入一个单独的翻译单元中。
  • 你的 .h 是错误的,它应该是 void func1(void); 而不是 void func(void);
  • 如果您的平台不支持--gc-sections,您可以尝试使用-flto。在此处查看此答案stackoverflow.com/questions/6687630/…
  • @Jean-FrançoisFabre 编译时没有警告,我修复了 .h 但未使用的函数仍然存在
  • 其实我已经测试过了,如果没有编译-shared

标签: c gcc shared-libraries static-libraries dead-code


【解决方案1】:

您可以结合-ffunction-sections--gc-sections使用版本脚本来标记入口点。

例如,考虑这个 C 文件 (example.c):

int
foo (void)
{
  return 17;
}

int
bar (void)
{
  return 251;
}

还有这个version script,叫version.script

{
  global: foo;
  local: *;
};

像这样编译和链接源代码:

gcc -Wl,--gc-sections -shared -ffunction-sections -Wl,--version-script=version.script example.c

如果您查看objdump -d --reloc a.out 的输出,您会注意到共享对象中只包含foo,而不包含bar

以这种方式删除函数时,链接器将考虑间接依赖关系。例如,如果你把foo 变成这样:

void *
foo (void)
{
  extern int bar (void);
  return bar;
}

链接器会将foobar 都放入共享对象中,因为两者都是必需的,即使只导出了bar

(显然,这不适用于所有平台,但 ELF 支持。)

【讨论】:

    【解决方案2】:

    您正在创建一个,而您的符号不是静态,因此链接器不会删除任何全局符号是正常的。

    这个-gc-sections 选项是为可执行文件设计的。链接器从入口点 (main) 开始并发现函数调用。它标记已使用的部分,并丢弃其他部分。

    库没有 1 个入口点,它的入口点与全局符号一样多,这说明它无法清理您的符号。如果有人在他的程序中使用您的.h 文件并调用“未使用”函数怎么办?

    要找出哪些函数未被“使用”,我建议您将void func_in_my_prog() 转换为int main()(或将源代码复制到包含main() 的修改过的函数中),然后使用源,并在链接时添加 -Wl,-Map=mapfile.txt 选项以创建地图文件。

    gcc -Wl,--gc-sections -Wl,--Map=mapfile.txt -fdata-sections -ffunction-sections libmy_static_lib.c my_prog.c
    

    此映射文件包含丢弃的符号:

    Discarded input sections
    
     .drectve       0x00000000       0x54 c:/gnatpro/17.1/bin/../lib/gcc/i686-pc-mingw32/6.2.1/crt2.o
     .drectve       0x00000000       0x1c c:/gnatpro/17.1/bin/../lib/gcc/i686-pc-
     ...
     .text$unused_func1
                    0x00000000       0x14 C:\Users\xx\AppData\Local\Temp\ccOOESqJ.o
     .text$unused_func2
                    0x00000000       0x14 C:\Users\xx\AppData\Local\Temp\ccOOESqJ.o
     .rdata$zzz     0x00000000       0x38 C:\Users\xx\AppData\Local\Temp\ccOOESqJ.o
      ...
    

    现在我们看到未使用的函数已被删除。它们不再出现在最终的可执行文件中。

    现有的工具可以做到这一点(使用这种技术但不需要main),例如Callcatcher。还可以轻松创建一个工具来反汇编库并检查已定义但未调用的符号(我已经在 python 中多次编写过此类工具,解析汇编比从高级代码中容易得多)

    要清理,您可以从源中手动删除未使用的函数(在使用现有/自定义程序集分析工具时,必须小心面向对象的语言和调度调用。另一方面,编译器不会删除可以使用的部分,这样是安全的)

    您还可以删除库文件中的相关部分,避免更改源代码,例如通过删除部分:

    $ objcopy --remove-section .text$unused_func1 --remove-section text$unused_func2 libmy_static_lib.a  stripped.a    
    $ nm stripped.a
    
    libmy_static_lib.o:
    00000000 b .bss
    00000000 d .data
    00000000 r .rdata
    00000000 r .rdata$zzz
    00000000 t .text
    00000000 t .text$func1
    00000000 T _func1
             U _puts
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-17
      • 2011-01-10
      • 1970-01-01
      • 1970-01-01
      • 2010-10-01
      相关资源
      最近更新 更多