abi-compliance-checker 理解

本文介绍ABIComplianceChecker(ABI合规性检测器),一种用于检查C/C++软件库的向后二进制和源代码级兼容性的工具。文章详细阐述了其原理、使用方法及工作流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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还有很多其它选项,更多的功能读者可以自行解锁!

### DeepSeek ABI Integration and Compatibility #### Understanding ABI Compatibility in the Context of DeepSeek In software development, ensuring Application Binary Interface (ABI) compatibility is crucial when integrating libraries or tools into larger systems. For a platform like DeepSeek that may involve extensive use of external C/C++ libraries, maintaining ABI stability ensures seamless upgrades without breaking existing applications[^1]. DeepSeek can leverage an ABI compliance checker tool to monitor changes within its core components as well as third-party dependencies. This approach helps identify potential issues early during development cycles rather than encountering them after deployment. For instance, suppose there exists a hypothetical scenario where DeepSeek uses `libtinyalsa` for audio processing capabilities. If updates are made to this library leading to extending changes in its ABI, developers would need to address these modifications promptly using scripts provided by Android build environments: ```bash $ANDROID_BUILD_TOP/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libtinyalsa ``` This command regenerates reference dumps necessary for validating future ABI consistency checks against new versions of `libtinyalsa`. #### Implementing ABI Checks Within Development Pipelines To integrate ABI verification processes effectively within Continuous Integration/Continuous Deployment pipelines used by projects such as DeepSeek, consider automating tasks related to running ABICC on every commit or pull request targeting critical branches. Automating these steps reduces human errors while promoting best practices around dependency management. Additionally, incorporating static analysis tools capable of detecting common pitfalls associated with violating established conventions regarding function signatures, data structures alignment requirements, etc., further strengthens overall system robustness over time. #### Best Practices for Maintaining ABI Stability When developing modules intended for inclusion inside platforms similar to DeepSeek, adhering strictly to guidelines concerning public interfaces exposed through headers files becomes paramount. Developers should avoid making unnecessary alterations outside documented APIs unless absolutely required due to functional enhancements or security patches. Furthermore, employing versioned symbols allows consumers of shared objects (.so files under Linux/macOS; .dlls on Windows) to coexist peacefully alongside multiple iterations simultaneously installed across diverse operating systems configurations encountered throughout target user bases. --related questions-- 1. How does one set up automated testing frameworks specifically tailored towards catching ABI regressions? 2. What strategies exist for minimizing disruptions caused by mandatory API deprecations affecting widely adopted open-source packages? 3. Can you provide examples illustrating how symbol versioning works internally within ELF binaries produced via GCC/Clang compilers? 4. Are there any notable differences between handling ABI concerns among various programming languages beyond just C/C++ ecosystems?
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

canpool

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值