动态库符号可见性及库裁剪

2020, Nov 30    
// digit.h
#include <iostream>

int one();

int two();

int three();

int four();

int five();

// ---------

// digit.cc
#include "digit.h"

#define EXPORTED __attribute__((visibility ("default")))

EXPORTED int one() {
  std::cout << "one: 1" << std::endl;
  return 1;
}

EXPORTED int two() {
  std::cout << "two: 2" << std::endl;
  return 2;
}

int three() {
  std::cout << "three: 3" << std::endl;
  return 3;
}

int four() {
  std::cout << "four: 4" << std::endl;
  return 4;
}

int five() {
  std::cout << "five: 5" << std::endl;
  return 5;
}

// ---------

// main.cc
#include "digit.h"

int main() {
  one();
  two();
  // three();
}

编译出动态库digit.so,size为8848字节。

g++ digit.cc -fPIC -shared -o digit.so

使用nm查看so内部的符号,发现digit.h定义的所有函数都被导出。

nm -C -D digit.so
0000000000000990 T one()
00000000000009c6 T two()
0000000000000a68 T five()
0000000000000a32 T four()
00000000000009fc T three()

编译可执行文件,size为8832字节。

g++ main.cc digit.so -Wl,-rpath=. -o main

符号可见性

编译过程中使用用visibility来控制符号可见性

利用符号的可见性防止依赖冲突。digit.so的size为8848字节,编译可执行文件,size为8832字节

g++ digit.cc -fvisibility=hidden -fPIC -shared -o digit.so

使用nm再次观察so内部的符号

0000000000000920 T one()
0000000000000956 T two()

编译main文件的时候,如果uncomment three函数,会报错,找不到three

/tmp/ccslv9VF.o: In function `main':
main.cc:(.text+0xf): undefined reference to `three()'

链接过程使用version-script来控制符号可见性

该方法仅对ELF格式文件有效。digit.map文件如下。

{
  global:
    *one*;
    *two*;
  local:
    *;
};
g++ digit.cc -fPIC -shared -Wl,--version-script digit.map -o digit.so

库裁剪

符号可见性一节中介绍了导出符号的概念。在编译库的过程中,可以通过-ffunction-sections, -fdata-sections来将未使用的function或data去除,从而减少库的体积。

g++ digit.cc -fPIC -shared -fvisibility=hidden -ffunction-sections -Wl,--gc-sections -o digit.so

digit.so的size为8744字节,digit.so中的three(), four(), five()函数都被去除。

References

  • https://gcc.gnu.org/wiki/Visibility
  • https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_25.html
  • https://gcc.gnu.org/onlinedocs/gcc-8.4.0/gcc/Optimize-Options.html#Optimize-Options