文章目录
1. 简介
ABI Compliance Checker (ABI合规性检测器,ABICC) 是一种用于检查 C/C++ 软件库的向后二进制和源代码级兼容性的工具。
该工具分析 API/ABI(ABI = API + 编译器ABI)中可能破坏二进制兼容性和/或源代码兼容性的更改:调用堆栈的更改、v-table 更改、删除的符号、重命名的字段等。
该工具可以创建和比较库的头文件和共享对象的 ABI 转储。 如果共享对象包含 debug-info,库的 ABI 转储也可以由 ABI Dumper 工具 (https://github.com/lvc/abi-dumper) 创建。
该工具适用于对确保向后兼容性感兴趣的软件库开发人员和 Linux 维护人员,即允许旧应用程序运行或使用较新的库版本重新编译。
该工具是 ABI Tracker 和 Upstream Tracker 项目的核心:https://abi-laboratory.pro/tracker/
2. 社区
官方仓库:https://github.com/lvc/abi-compliance-checker
最新版本:2.3(发布时间2018-5-8)
许可协议:LGPL-2.1
开发语言:perl
主作者:Andrey Ponomarenko
更新时间:2020-3-30
官方文档:https://lvc.github.io/abi-compliance-checker/
3. 依赖
- Perl 5
该软件采用perl 5脚本语言开发 - GCC C++ (3.0 or newer)
提供gcc/g++编译命令 - GNU Binutils
提供c++filt、readelf、objdump命令 - Ctags
提供ctags命令 - ABI Dumper (1.1 or newer)
提供abi-dumper命令
4. 使用
abicc提供了三种用法,如下:
4.1. 用法一:借用abi-dumper命令
USAGE #1 (WITH ABI DUMPER):
1. Library should be compiled with "-g -Og" GCC options
to contain DWARF debug info
2. Create ABI dumps for both library versions
using the ABI Dumper (https://github.com/lvc/abi-dumper) tool:
abi-dumper OLD.so -o ABI-0.dump -lver 0
abi-dumper NEW.so -o ABI-1.dump -lver 1
3. You can filter public ABI with the help of
additional -public-headers option of the ABI Dumper tool.
4. Compare ABI dumps to create report:
abi-compliance-checker -l NAME -old ABI-0.dump -new ABI-1.dump
先借用abi-dumper命令对so/ko(库中需要包含debug info信息)生成dump文件, 再通过abicc基于dump文件生成abi变更报表。
缺点:需要依赖abi-dumper工具,同时还要求so/ko携带debug info信息
4.2. 用法二:原始
USAGE #2 (ORIGINAL):
1. Create XML-descriptors for two versions
of a library (OLD.xml and NEW.xml):
<version>
1.0
</version>
<headers>
/path/to/headers/
</headers>
<libs>
/path/to/libraries/
</libs>
2. Compare Xml-descriptors to create report:
abi-compliance-checker -lib NAME -old OLD.xml -new NEW.xml
先配置xml描述文件(包含版本、头文件路径、库文件路径等),再通过abicc基于xml文件生成abi报表。
4.3. 用法三:创建ABI DUMPS
USAGE #3 (CREATE ABI DUMPS):
1. Create XML-descriptors for two versions
of a library (OLD.xml and NEW.xml):
<version>
1.0
</version>
<headers>
/path/to/headers/
</headers>
<libs>
/path/to/libraries/
</libs>
2. Create ABI dumps:
abi-compliance-checker -lib NAME -dump OLD.xml -dump-path ./ABI-0.dump
abi-compliance-checker -lib NAME -dump NEW.xml -dump-path ./ABI-1.dump
3. Compare ABI dumps to create report:
abi-compliance-checker -l NAME -old ABI-0.dump -new ABI-1.dump
先配置xml描述文件(包含版本、头文件路径、库文件路径等),再通过abicc基于xml文件生成dump文件,然后通过abicc基于dump文件生成abi报表。
缺点:相比较用法二,多出生成dump文件这一步
4.4. 测试
本次采用方法二进行测试,另外两种方法读者可以自行尝试。
4.4.1. hello1
1)hello.h
#ifndef __HELLO_H__
#define __HELLO_H__
void hello(char *what);
#endif
2)hello.c
#include <stdio.h>
#include "hello.h"
void hello(char *what)
{
if (what == NULL) {
return;
}
printf("hello %s\n", what);
}
3)h1.xml
<version>
1
</version>
<headers>
/mnt/hgfs/projects/linux/abi-test/h1
</headers>
<libs>
/mnt/hgfs/projects/linux/abi-test/h1
</libs>
4.4.2. hello2
1)hello.h
#ifndef __HELLO_H__
#define __HELLO_H__
int hello(const char *what);
#endif
2)hello.c
#include <stdio.h>
#include "hello.h"
int hello(const char *what)
{
if (what == NULL) {
return -1;
}
printf("hello %s\n", what);
return 0;
}
3)h2.xml
<version>
2
</version>
<headers>
/mnt/hgfs/projects/linux/abi-test/h2
</headers>
<libs>
/mnt/hgfs/projects/linux/abi-test/h2
</libs>
4.4.3. 生成so库文件
在h1和h2目录执行如下命令,生成so:
gcc --share -fPIC -o libhello.so hello.c
4.4.4. 生成abi报表
执行如下命令,生成abi报表:
maminjie@fedora /m/h/p/l/abi-test> abi-compliance-checker -l hello -old h1.xml -new h2.xml -report-path compat_report.html
Preparing, please wait ...
Using GCC 11 (x86_64-redhat-linux, target: x86_64)
WARNING: May not work properly with GCC 4.8.[0-2], 6.* and higher due to bug #78040 in GCC. Please try other GCC versions with the help of --gcc-path=PATH option or create ABI dumps by ABI Dumper tool instead to avoid using GCC. Test selected GCC version first by -test and -gcc-path options.
Checking header(s) 1 ...
Checking header(s) 2 ...
Comparing ABIs ...
Comparing APIs ...
Creating compatibility report ...
Binary compatibility: 100%
Source compatibility: 100%
Total binary compatibility problems: 0, warnings: 2
Total source compatibility problems: 0, warnings: 0
Report: compat_report.html
maminjie@fedora /m/h/p/l/abi-test>
报表如下:
abi-compliance-checker命令在生成abi报表时加上-debug
选项,可以在当前目录下生成debug目录:
libs目录下是不同库导出的符号表文件,translation-unit-dump.txt中是头文件编译后的tu内容,preprocessor.txt是头文件预处理内容。
4.5. 小结
上面例子只测试了函数的返回类型和参数类型的差异,实际上abicc检测的可不止这些,下图展示abicc可以检测二进制兼容性的一些问题:
可以检测二进制兼容性的问题包括:
- 数据类型的问题
- 结构体和类
- 添加/删除字段(更改内部布局)
- 大小变化
- 字段顺序变化
- 字段类型变化
- 字段的变化(递归分析)
- 类
- 添加/删除虚函数(更改v-table布局)
- 虚函数位置变化
- 虚函数重写
- 添加/删除基类
- 基类的变化(递归分析)
- 联合体
- 添加/删除字段
- 大小变化
- 字段类型的变化
- 字段的变化(递归分析)
- 枚举
- 成员值变化
- 删除/重命名成员
- 结构体和类
- 符号问题
- 删除的符号(函数或全局数据)
- 添加/删除参数
- 参数/返回值类型变化
- 默认参数值变化
- 重命名参数
- 不正确的版本变化
- 属性变化(const、volatile、static等)
- 常量问题
- 值变化
查看更多的软件abi变更报表请访问:https://abi-laboratory.pro/index.php?view=tracker
5. 原理
5.1. 目录结构
主要目录和文件如下表所示:
一级目录 | 二级目录 | 三级目录 | 说明 |
---|---|---|---|
modules | 模块目录 | ||
Internals | |||
Scripts | js相关脚本 | ||
Styles | css相关样式文件 | ||
*.pm | 内部的perl模块 | ||
RulesBin.xml | 二进制abi报表的规则配置文件 | ||
RulesSrc.xml | 源码abi报表的规则配置文件 | ||
abi-compliance-checker.pl | abi-compliance-checker命令/工具入口脚本 | ||
Makefile | |||
Makefile.pl | Makefile中调用的perl脚本 |
5.1.1. Makefile
prefix ?= /usr
.PHONY: all
all:
echo "Nothing to build."
install:
perl Makefile.pl -install -prefix "$(prefix)"
uninstall:
perl Makefile.pl -remove -prefix "$(prefix)"
clean:
echo "Nothing to clean up."
该Makefile支持安装和卸载abicc,默认安装目录在/usr中,可以通过prefix=xxx指定个人安装目录,下面演示将abicc安装到个人目录的过程:
- abi-comliance-checker 命令被安装到指定目录下的bin目录下
- abicc的modules被拷贝到指定目录下的share/abi-compliance-checker目录下
5.1.2. RulesBin.xml和RulesSrc.xml
这两个xml配置文件中定义的规则分别对应abi变更报表的二进制兼容性和源码兼容性,如下图所示:
下面通过Binary Compatibility中符号问题的信息分析和RulesBin.xml配置文件中的规则的关联:
参数类型,参数类别的规则:
返回值类型,符号类别的规则:
5.1.3. pm
主要的perl模块文件如下表所示:
文件 | 说明 |
---|---|
ABIDump.pm | 该模块从 AST(Abstract syntax tree 抽象语法树)创建 ABI 转储,依赖ElfTools、TUDump、GccAst三个模块,包括 createABIDump、readSymbols等函数,请不要与abi-dumper命令搞混了 |
Basic.pm | 基础模块,包含一些简单的处理函数 |
CallConv.pm | 该模块用于创建调用约定的模型 |
Descriptor.pm | 该模块用于处理 XML 描述符 |
ElfTools.pm | 该模块用于读取 ELF 二进制文件,其中 readline_ELF 函数用于读取“readelf”输出行中的符号信息 |
Filter.pm | 该模块用于过滤符号信息 |
GccAst.pm | 该模块用于解析 GCC AST(抽象语法树) |
Input.pm | 该模块用于处理输入数据,包括输入选项options,输入数据的描述,ABI信息等 |
Logging.pm | 日志模块 |
Mangling.pm | 该模块用于处理 C++ 符号 |
Path.pm | 该模块包含一些处理path的函数 |
RegTests.pm | 带有回归测试套件的模块 |
SysCheck.pm | 该模块用于比较操作系统, 处理一些操作系统相关的信息 |
SysFiles.pm | 该模块用于查找系统文件并自动生成包含路径 |
TUDump.pm | 该模块用于创建 AST 转储,包括 createTUDump、checkCTags等函数 |
TypeAttr.pm | 该模块用于处理一些类型属性 |
Utils.pm | 该模块包含一些基础/通用的函数 |
XmlDump.pm | 该模块用于以 XML 格式创建和读取 ABI 转储,包括 createXmlDump、readXmlDump等函数 |
5.2. 编译头文件
“方法二”中生成abi报表需要先定义xml文件,其中headers标签中定义的头文件路径,那abicc是如何处理这些头文件路径中的头文件的呢?
实际上abicc通过gcc将这些头文件编译生成tu文件,具体的过程,可以从log.txt文件中发现:
生成临时头文件dumpX.h,在此头文件中包含xml文件中指定头文件路径下的头文件,然后通过如下命令生成tu文件:
gcc -fdump-lang-raw -fkeep-inline-functions -c -x c++ -fpermissive -w "/tmp/EPWA8Ad7Ru/dump1.h"
下面通过上述gcc命令编译hello.h头文件,观察tu文件信息:
这里并没有生成tu文件,而是raw文件,实际上二者是同种东西,主要是因为gcc的版本大于8,用-fdump-lang-raw替换了-fdump-translation-unit,如下所示:
下面对采用-fdump-transiation-unit选项对h2中的hello.h进行编译,效果如下:
raw文件和tu文件对比如下,格式上是一致的:
其实代码上也表明raw和tu是一回事,下面是createTUDump函数生成tu文件,然后返回给调用者:
createTUDump和createABIDump关系如下:
5.2.1. gcc参数说明
命令:
gcc -fdump-lang-raw -fkeep-inline-functions -c -x c++ -fpermissive -w "xxx.h"
- -fdump-lang-raw
转储原始的内部树数据。 此选项仅适用于 C++。 - -fdump-translation-unit(C++ only)
-fdump-translation-unit-options(C++ only)
将整个翻译单元的树结构表示转储到文件中。 文件名是通过在源文件名后附加“.tu”来制作的,并且该文件与输出文件在同一目录中创建。 如果使用“-options”形式,则选项控制转储的详细信息,如“-fdump-tree”选项所述。 - -fkeep-inline-functions
在 C 中,发出内联声明到目标文件中的静态函数,即使该函数已内联到其所有调用者中。 此开关不影响在 GNU C90 中使用 extern 内联扩展的函数。 在 C++ 中,将任何和所有内联函数发出到目标文件中。 - -c
编译或汇编源文件,但不链接。 链接阶段根本没有完成。 最终的输出是以每个源文件的目标文件的形式。 默认情况下,源文件的目标文件名是通过将后缀“.c”、“.i”、“.s”等替换为“.o”。 不需要编译或汇编的无法识别的输入文件将被忽略。 - -x language
为以下输入文件明确指定语言(而不是让编译器根据文件名后缀选择默认语言)。此选项适用于所有后续输入文件,直到下一个“-x”选项。 - -fpermissive
将一些关于不符合代码的诊断从错误降级为警告。 因此,使用‘-fpermissive’允许一些不合格的代码编译。 - -w
禁止所有警告消息。
更多gcc的参数,可以参考gcc手册:https://gcc.gnu.org/onlinedocs/
5.2.2. tu文件说明
h1中的hello.h.003l.raw文件的部分内容如下:
图中的这种格式其实是GCC编译生成的抽象语法树(Abstract syntax tree,AST),生成的抽象语法树文本规模庞大,就上面简单的几行代码,生成的文本有一万多行……
GCC编译器以源程序的过程为单位生成AST,而且包含整个编译单元的完整表示。AST是GCC编译器前端的中心数据结构,比较直观地表示出源程序的语法结构,并含有源程序结构显示所需的全部静态信息。
GCC的AST文件存储方式是首先对此AST上的每一个结点编号,然后每一个结点对应一条记录项,每个记录项由三大部分组成:结点编号、结点标识符、结点标记序列。
- 结点编号
以“@”开始,@后是该节点的索引,是AST上区分结点的唯一标志。 - 结点标识符
结点标识符表示结点类型,代表了结点的含义,主要包括:
1)标识符结点(identifiers)
2)类型节点(types)
3)声明结点(declarations)
4)函数结点(functions)
5)范围结点(scope)
6)语句结点(statements)
7)表达式结点(expressions) - 结点标记列表
结点标记的列表记录了该结点连接到其他结点的所有分支,每个结点标记对应一个分支。结点标记由标记标签和标记值(可以为空)组成,形如:name: @2。
标记标签主要有:名字(name)、类型(type)、所属/范围(scpe)、来源位置(srcp)等。
参考:https://www.wenmi.com/article/py922d00v1zp.html
下面让我们看看头文件中声明的void hello(char *what);
函数在AST是如何表示的:
分析过程如下:
5.3. 读动态库文件
上节createABIDump函数调用readLibs函数,readLibs函数如下:
通过上面可知,通过readelf -Ws xxx.so
读取动态库的符号,然后通过readline_ELF函数逐行读取符号信息,存储到全局散列表$In::ABI(定义在input.pm模块)中:
5.3.1. 符号表说明
h1中的libhello.so导出的符号表如下:
符号表内容分为两部分:
- 动态符号表(.dynsym):用来保存与动态库链接相关的导入导出符号,不包括模块内部的符号。
- 符号表(.symtab):保存所有符号,包括.dynsym中的符号。
符号表的各列含义如下:
- Num
符号表的索引值,序号从0开始 - Value
符号相对应的值。这个值跟符号有关,可能是一个绝对值,也可能是一个地址等。 - Size
符号大小。对于包含数据的符号,该值是该数据。对于类型大小,比如一个double型的符号,该值是类型占用的字节数。如果该值为0,则表示改符号大小为0或未知。 - Type
符号类型。包括:
1)NOTYPE:未知类型
2)OBJECT:数据对象,比如变量、数组等
3)FUNC:函数或其它可执行代码
4)SECTION:段,Bind必须是LOCAL
5)FILE:文件名,一般都是该目标文件所对应的源文件名,Bind一定是LOCAL,Ndx一定是ABS。 - Bind
符号绑定。包括:
1)LOCAL:局部符号,对于目标文件的外部不可见。
2)GLOBAL:全局符号,外部可见。
3)WEAK:弱引用,类似于全局符号,但可以被覆盖。 - Vis
符号可以是默认的(default)、受保护的(protected)、隐藏的(hidden)或内部的(internal)。 - Ndx
符号所在段。如果符号定义在本目标文件中,那么这个成员表示符号所在的段在段表中的下标;如果符号不是定义在本目标文件中,或者对于有些特殊符号,Ndx的值如下所示:
1)ABS:表示该符号包含了一个绝对的值,比如表示文件名的符号。
2)COMMON:表示该符号是一个“COMMON块”类型的符号,比如未初始化的全局符号。具体请参照"深入静态链接"一章中的"COMMON块"。
3)UNDEF:表示该符号未定义,表示该符号在本目标文件中被引用,但是定义在其它目标文件中。 - Name
符号名。
abi-compliance-checker中根据符号表的上述特征,提取了有下述特征的符号:
- Bind 中包含
WEAK、GLOBAL
的符号 - Type 中包含
FUNC、IFUNC、OBJECT、COMMON
的符号 - Vis 中包含
DEFAULT、PROTECTE
的符号
5.4. 工作流程
现在,让我们从头来看看abicc的工作流程,如下图所示:
6. 后语
ABI涵盖了各种细节:如数据类型、大小和对齐;调用约定(控制着函数的参数如何传送以及如何接受返回值);系统调用的编码和一个应用如何向操作系统进行系统调用;以及在一个完整的操作系统ABI中,目标文件的二进制格式、程序库等等。一个完整的ABI,像Intel二进制兼容标准 (iBCS)[1] ,允许支持它的操作系统上的程序不经修改在其他支持此ABI的操作体统上运行。
ABI兼容不同于应用程序接口(API)兼容,API定义了源代码和库之间的接口,因此同样的代码可以在支持这个API的任何系统中构建,然而ABI允许构建好的目标代码在使用兼容ABI的系统中无需改动就能运行。
ABI兼容可以减少销售商、用户将程序移植其它/不同版本系统时所需的的工作。一些标准化工作组(比如Linux)正在做这方面的努力,同时也涌现出了一些检测ABI变更的工具,用于发现ABI不兼容的潜在问题。
除了本文讲的abi-compliance-checker,还有一些abi检测工具,比如:abi-dumper,libabigail,ABIcheck,shlib-compat等。
本文只介绍了abi-compliance-checker
的"方法二"来生成abi报表:
USAGE #2 (ORIGINAL):
1. Create XML-descriptors for two versions
of a library (OLD.xml and NEW.xml):
<version>
1.0
</version>
<headers>
/path/to/headers/
</headers>
<libs>
/path/to/libraries/
</libs>
2. Compare Xml-descriptors to create report:
abi-compliance-checker -lib NAME -old OLD.xml -new NEW.xml
除此之外,abi-compliance-checker
还有很多其它选项,更多的功能读者可以自行解锁!