abi-compliance-checker 理解

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
Scriptsjs相关脚本
Stylescss相关样式文件
*.pm内部的perl模块
RulesBin.xml二进制abi报表的规则配置文件
RulesSrc.xml源码abi报表的规则配置文件
abi-compliance-checker.plabi-compliance-checker命令/工具入口脚本
Makefile
Makefile.plMakefile中调用的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-dumperlibabigailABIcheckshlib-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还有很多其它选项,更多的功能读者可以自行解锁!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值