1. 问题背景

运用ubuntu安装LLVM libc++过程时,注意到LLVM官网关于libc++的简介:

The libc++ and libc++ ABI projects provide a standard conformant and high-performance implementation of the C++ Standard Library, including full support for C++11 and C++14.

疑问libc++ ABI是什么 ?检查LLVM官网描绘libc++ ABI:

libc++abi is a new implementation of low level support for a standard C++ library.

既然libc++abi是c++的底层支撑,为什么要单独以so的形式呈现 ?为什么不直接编译到libc++.so中呢 ?

2. 分析思路

2.1 ABI是什么 ?它的效果是什么 ?

ABI的全称是Application Binary Interface,假如仅是从字面意思来看,很难直接理解它的含义与功用,可是作业学习中我们更多接触的是API(Application Programming Interface,运用程序编程接口),那么二者之间有没有什么联系呢 ?接下来将从API出发,走向ABI。

C++的头文件,扮演着API的人物,开发人员运用其间界说的办法,完结对外部库的调用,所以API是直接对开发人员可见的,开发人员能够看得见摸得着的。可是ABI却不相同,从界说上看,它涉及到Binary,既然是二进制,明显开发人员不或许肉眼直接检查二进制,以获得有价值的信息,事实也是如此,ABI对开发人员不直接可见,它由硬件,编译器,链接器等要素影响。

举一个简单的例子,C++的Name Mangling规范,假定A项目需求依赖外部库third_party.so,该库界说并完成函数B,函数B在该库中的符号名称是_B2023_0401_, 假如A项目开发人员调用函数B,运用A项目中的c++编译器生成函数B的符号是_B2023_0405_,那么,程序链接时,将呈现符号找不到的反常场景。究其原因,是由于A项目与外部库运用的编译器没有遵循相同的符号润饰规范,导致二进制不兼容。符号润饰规范仅仅是ABI的一部分,以此类推。

ABI的界说如下:

符号润饰规范,变量内存布局,函数调用方法等这些与可执行代码二进制兼容性相关的内容称为ABI

ABI与API都是运用程序接口,只是他们所描绘的接口地点的层面不相同。API往往是指源代码等级的接口,而ABI是指二进制层面的接口

综上所述,API运用正确由开发人员保证,而ABI则由编译器,链接器等保证。一个在上,一个在下。ABI的效果也清楚明了,就是保证二进制的兼容性。

2.2 C++ Standard Library ABI是什么 ?

C++ Standard Library ABI is the compilation of a given library API by a given compiler ABI.

library API + compiler ABI = library ABI

C++规范库ABI由C++规范库API和编译器ABI两部分构成,规范库API保证c++规范中目标(比如std:string, std::map等)底层完成的一致性,防止因底层完成不同导致的兼容性问题,compiler ABI则保证外部符号润饰,怎么调用虚函数等而二进制兼容性。所以,假如需求完成二进制兼容性的完整体,需求一起满意library API和compiler ABI两个条件。那么便存在一个常见的问题,运用不同的C++规范库so,是否能够一起运行在一个进程空间内呢?操作系统渠道是否对该运用场景存在约束呢 ?

2.3 LLVM的libc++ABI.so 与 C++ ABI之间的联系是什么呢 ?

C++ ABI是C++二进制兼容规范,规范的完成一部分经过编译器完结,比如怎么调用虚函数等内容,而规范的另一部分完成,比如RTTI怎么完成,则是坐落libc++ABI.so, 由于这部分内容是运行时决议的。所以LLVM的libc++ABI.so是C++ ABI完成的子集,是进程运行时的一部分。

Ubuntu检查进程的maps信息(cat /proc/进程号/maps),可验证libc++ABI.so是进程运行的一部分。

C++ Standard Library ABI 是什么 ?

假如检查进程maps信息,Andorid渠道是否也能检查到libc++abi.so呢 ?

3. 答疑

3.1 运用不同的C++规范库so,是否能够一起运行在一个进程空间内呢 ?

问题分为两种状况,一种状况是C++ABI规范相同,答案是能够运行在同一进程空间,但存在约束,约束是不能在二者之间传递C++规范库目标(比如std::map等),由于规范库不同,或许std::map的完成也不同,存在对同一地址空间的不同解释,这或许会导致兼容性问题;第二种状况则是C++ABI规范不同,此毋庸置疑,答案肯定是不行的,程序链接时或许会因符号润饰规范不同而导致失利。

3.2 操作系统渠道是否对混用不同C++规范库so的运用场景有约束呢 ?

Android渠道存在约束,假定运用Android NDK编译的so,调用Android中坐落/system/lib64/的系统so, 结果是能够调用,可是存在约束,不能传递C++规范库目标,由于程序链接时便会因无法解析的符号而导致链接失利。原因分析如下:

std::map头文件完成:

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif
_LIBCPP_BEGIN_NAMESPACE_STD
template <class _Key, class _CP, class _Compare,
          bool = is_empty<_Compare>::value && !__libcpp_is_final<_Compare>::value>
class __map_value_compare
    : private _Compare
{

_LIBCPP_BEGIN_NAMESPACE_STD宏界说了命名空间,该宏界说在文件__config中。

AOSP中源码如下:

// Inline namespaces are available in Clang/GCC/MSVC regardless of C++ dialect.
#define _LIBCPP_BEGIN_NAMESPACE_STD namespace std { inline namespace _LIBCPP_ABI_NAMESPACE {
#define _LIBCPP_END_NAMESPACE_STD  } }
#ifndef _LIBCPP_ABI_NAMESPACE
# define _LIBCPP_ABI_NAMESPACE _LIBCPP_CONCAT(__,_LIBCPP_ABI_VERSION)
#endif

Android NDK中源码如下:

// Inline namespaces are available in Clang/GCC/MSVC regardless of C++ dialect.
#define _LIBCPP_BEGIN_NAMESPACE_STD namespace std { inline namespace _LIBCPP_ABI_NAMESPACE {
#define _LIBCPP_END_NAMESPACE_STD  } }
#ifndef _LIBCPP_ABI_NAMESPACE
# define _LIBCPP_ABI_NAMESPACE _LIBCPP_CONCAT(__ndk,_LIBCPP_ABI_VERSION)
#endif

请注意观察,_LIBCPP_ABI_NAMESPACE的界说是不相同的,也就是说尽管都是std::map,可是他们的命名空间的确不相同的,所以才会导致无法解析的符号。之前问题定位时,只知道命名空间不相同的,可是不知道为什么不相同 ?命名空间不相同,是为避免二进制兼容性问题。

3.3 假如检查进程maps信息,Andorid渠道是否也能检查到libc++abi.so ?

答案是否定的,检查进程maps信息,能够看到有libc++.so,可是看不到libc++abi.so。经过检查libcxx的Adnroid.bp文件,原因一目了然,由于AOSP将libc++abi编译成静态库,集成到libc++.so。

4.参考

  1. 程序员的自我修养-链接,装载与库 章节4.4.3:C++与ABI

  2. GCC ABI Policy and Guidelines

  3. LLVM官网

  4. LLVM libcxxabi 项目

  5. AOSP源码,分支:android-13.0.0_r35

    map: external/libcxx/include/map

    __config: external/libcxx/include/__config

    Android.bp: external/libcxx/Android.bp

  6. Android NDK,版本:r25c

    __config: sources/cxx-stl/llvm-libc++/include/__config