解决C/C++依赖库不兼容的问题

本文更新于 2019.01.07

问题引入与分析

我们可能都遇到过一个问题: 在一台机器上编译出来的某个程序/库无法在另一台机器上运行.

造成这个问题的原因有:

  1. 编译机器与运行机器硬件不一致, 比如把在x86机器上编译的程序放到ARM机器上运行, 还包括字节序问题(Littile-/Big-Endian)和指令宽度问题(32/64bit)等
  2. 依赖库不存在
  3. 依赖库存在, 但不兼容

本文主要关注第3个问题的某种情况. 举个例子, 在Ubuntu16.04机器上编译出来的某个动态库(这里记为libXXX.so)无法在CentOS7.3机器上运行(硬件一致). 其原因是Ubuntu16.04默认安装的gcc版本是5.4.0, 使用它进行编译链接时依赖的C++运行时库libstdc++.so与CentOS7.3默认安装的不兼容(ABI层面). 有关libstdc++ ABI的具体描述见 [4] , 这里摘录其中一部分内容:

C++ applications often depend on specific language support routines, say for throwing exceptions, or catching exceptions, and perhaps also depend on features in the C++ Standard Library.

The C++ Standard Library has many include files, types defined in those include files, specific named functions, and other behavior. The text of these behaviors, as written in source include files, is called the Application Programing Interface, or API.

Furthermore, C++ source that is compiled into object files is transformed by the compiler: it arranges objects with specific alignment and in a particular layout, mangling names according to a well-defined algorithm, has specific arrangements for the support of virtual functions, etc. These details are defined as the compiler Application Binary Interface, or ABI. From GCC version 3 onwards the GNU C++ compiler uses an industry-standard C++ ABI, the Itanium C++ ABI.

The GNU C++ compiler, g++, has a compiler command line option to switch between various different C++ ABIs. This explicit version switch is the flag -fabi-version. In addition, some g++ command line options may change the ABI as a side-effect of use. Such flags include -fpack-struct and -fno-exceptions, but include others: see the complete list in the GCC manual under the heading Options for Code Generation Conventions.

The configure options used when building a specific libstdc++ version may also impact the resulting library ABI. The available configure options, and their impact on the library ABI, are documented here.

Putting all of these ideas together results in the C++ Standard Library ABI, which is the compilation of a given library API by a given compiler ABI. In a nutshell:

“ library API + compiler ABI = library ABI ”

The library ABI is mostly of interest for end-users who have unresolved symbols and are linking dynamically to the C++ Standard library, and who thus must be careful to compile their application with a compiler that is compatible with the available C++ Standard library binary. In this case, compatible is defined with the equation above: given an application compiled with a given compiler ABI and library API, it will work correctly with a Standard C++ Library created with the same constraints.

To use a specific version of the C++ ABI, one must use a corresponding GNU C++ toolchain (i.e., g++ and libstdc++) that implements the C++ ABI in question.

可以通过依赖库检查来验证问题原因. 首先检查出问题的动态库, 看它依赖什么动态库.

在Ubuntu16.04开发机:

$ ldd libXXX.so
    linux-vdso.so.1 =>  (0x00007fff439ac000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f4668328000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f466801f000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f4667e09000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4667a3f000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f466900a000)

在CentOS7.3目标机:

$ ldd libXXX.so
./libXXX.so: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by ./libXXX.so)
./libXXX.so: /lib64/libstdc++.so.6: version `CXXABI_1.3.8' not found (required by ./libXXX.so)
./libXXX.so: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./libXXX.so)
    linux-vdso.so.1 =>  (0x00007ffc2d9f3000)
    libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f777255d000)
    libm.so.6 => /lib64/libm.so.6 (0x00007f777225a000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f7772044000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f7771c83000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f77731be000)

从这里ldd命令的输出就可以看到, 目标机器由于依赖库libstc++.so.6与开发机ABI不兼容, 造成问题.

下面检查libstdc++.so.6, 在Ubuntu16.04开发机:

$ strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX
GLIBCXX_3.4
GLIBCXX_3.4.1
GLIBCXX_3.4.2
GLIBCXX_3.4.3
GLIBCXX_3.4.4
GLIBCXX_3.4.5
GLIBCXX_3.4.6
GLIBCXX_3.4.7
GLIBCXX_3.4.8
GLIBCXX_3.4.9
GLIBCXX_3.4.10
GLIBCXX_3.4.11
GLIBCXX_3.4.12
GLIBCXX_3.4.13
GLIBCXX_3.4.14
GLIBCXX_3.4.15
GLIBCXX_3.4.16
GLIBCXX_3.4.17
GLIBCXX_3.4.18
GLIBCXX_3.4.19
GLIBCXX_3.4.20
GLIBCXX_3.4.21
GLIBCXX_DEBUG_MESSAGE_LENGTH

在CentOS7.3目标机:

$ strings /lib64/libstdc++.so.6 | grep GLIBCXX
GLIBCXX_3.4
GLIBCXX_3.4.1
GLIBCXX_3.4.2
GLIBCXX_3.4.3
GLIBCXX_3.4.4
GLIBCXX_3.4.5
GLIBCXX_3.4.6
GLIBCXX_3.4.7
GLIBCXX_3.4.8
GLIBCXX_3.4.9
GLIBCXX_3.4.10
GLIBCXX_3.4.11
GLIBCXX_3.4.12
GLIBCXX_3.4.13
GLIBCXX_3.4.14
GLIBCXX_3.4.15
GLIBCXX_3.4.16
GLIBCXX_3.4.17
GLIBCXX_3.4.18
GLIBCXX_3.4.19
GLIBCXX_DEBUG_MESSAGE_LENGTH

可见与ldd命令之前输出的错误信息一致, CentOS7.3上的libstdc++库不支持GLIBCXX_3.4.19以后符号版本.

解决问题

解决此问题的办法是在开发机上使用与目标机(依赖libstdc++库)一致的gcc版本, 见 [4] 中的一句话:

To use a specific version of the C++ ABI, one must use a corresponding GNU C++ toolchain (i.e., g++ and libstdc++) that implements the C++ ABI in question.

stackoverflow上的 [5] 也给出了同样的答案. 所以, 我们需要在Ubuntu16.04开机上安装GCC 4.8.5, 并强制使用它来编译libXXX.so.

首先查看当前gcc版本:

$ gcc -dumpversion
5.4.0

然后需要利用Linux下面一个工具: update-alternatives, 它可以通过命令链接的方法, 切换同一个系统中多个不同版本的软件, 比如我们的gcc, 参考 [1] , 这工具在Ubuntu和CentOS下面都有.

看一下当前gcc的版本切换情况:

$ update-alternatives --display gcc
update-alternatives: error: no alternatives for gcc

安装gcc-4.8:

$ sudo apt install gcc-4.8 g++-4.8

增加命令链接:

$ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100 --slave /usr/bin/g++ g++ /usr/bin/g++-4.8
$ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 80 --slave /usr/bin/g++ g++ /usr/bin/g++-5

这里的格式是:

update-alternatives --install <link> <name> <path> <priority> [--slave <link> <name> <path>]

其中:

  • link: 不同版本程序所公用的链接文件
  • name: 程序名称
  • path: 此版本程序所在路径
  • priority: 此版本程序的优先级
  • –slave: 可选项. 前面的称为master, 当master配置改变时,它所关联的slave也随着改变.

再次查看gcc切换情况:

$ update-alternatives --display gcc
gcc - auto mode
  link best version is /usr/bin/gcc-4.8
  link currently points to /usr/bin/gcc-4.8
  link gcc is /usr/bin/gcc
  slave g++ is /usr/bin/g++
/usr/bin/gcc-4.8 - priority 100
  slave g++: /usr/bin/g++-4.8
/usr/bin/gcc-5 - priority 80
  slave g++: /usr/bin/g++-5

可见目前是”auto”模式, 此模式下update-alternatives将自动选择优先级最高的gcc版本, 目前是4.8, 验证一下当前gcc版本:

$ gcc -dumpversion
4.8
$ g++ -dumpversion
4.8

使用update-alternatives –config gcc可以手动进行gcc版本切换:

$ sudo update-alternatives --config gcc
There are 2 choices for the alternative gcc (providing /usr/bin/gcc).

  Selection    Path              Priority   Status
------------------------------------------------------------
* 0            /usr/bin/gcc-4.8   100       auto mode
  1            /usr/bin/gcc-4.8   100       manual mode
  2            /usr/bin/gcc-5     80        manual mode

Press <enter> to keep the current choice[*], or type selection number:

如果我们选择2, 将切换为系统默认安装的gcc5:

$ gcc -dumpversion
5.4.0
$ g++ -dumpversion
5.4.0

重新运行update-alternatives –config gcc并选择0, 可以返回auto模式; 也可以使用 update-alternatives –auto gcc命令:

$ sudo update-alternatives --auto gcc
update-alternatives: using /usr/bin/gcc-4.8 to provide /usr/bin/gcc (gcc) in auto mod

参考资料

[1]使用update-alternatives命令进行版本的切换 https://blog.csdn.net/JasonDing1354/article/details/50470109
[2]How to install and use GCC g++ v4.7 and C++11 on Ubuntu 12.04 http://charette.no-ip.com:81/programming/2011-12-24_GCCv47/
[3]GNU: Binary Compatibility https://gcc.gnu.org/onlinedocs/gcc/Compatibility.html
[4](1, 2) GNU: ABI Policy and Guidelines https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
[5]Forcing G++ (GCC) to a specific libstdc++ version https://stackoverflow.com/questions/24454930/forcing-g-gcc-to-a-specific-libstdc-version-glibcxx