Introduction

Detecting and reviewing changes in the application binary interface (aka ABI) of ELF shared libraries has never been easier, notably since the creation of the Abigail project.  As you might already know, a tool like abidiff now allows users to analyse the cause of ABI changes and assess their impact on the forward compatibility of the shared library being considered.  It is less practical, though, to use that tool on a binary (RPM) package, because it expects individual libraries.

Thus, this article presents abipkgdiff, a new Libabigail tool aimed at detecting incompatible ABI changes between shared libraries (or binaries in general) carried by binary packages.

Getting abipkgdiff

This tool is available from the Libabigail package.  On Fedora, just type (with the proper rights, obviously):

dnf install libabigail

On RHEL 6, RHEL 7, CentOS, you need to configure the repositories for the Extra Packages for Enterprise Linux (EPEL). Then just type (again, with the proper rights):

yum install libabigail

If you are not on a distribution of the Red Hat ecosystem, worry not. Binary packages of Libabigail tools are widely available on all the major GNU/Linux distributions out there.  Otherwise, you can always get the source code and compile it.

A concrete example of ABI changes review: The Glib package in Fedora

Let's review the ABI changes of the libraries carried by the excellent Glib package in Fedora.  We'll compare the bleeding edge version of the Glib package from Rawhide (the current Fedora development version, aka Fedora 24) against its latest stable version from Fedora 23, for the ARM architecture.

Getting the packages to review

To get the Fedora 24 version of the GLib package along with its debug info, please type:

wget https://kojipkgs.fedoraproject.org//packages/glib2/2.47.5/1.fc24/armv7hl/glib2-debuginfo-2.47.5-1.fc24.armv7hl.rpm \
     https://kojipkgs.fedoraproject.org//packages/glib2/2.47.5/1.fc24/armv7hl/glib2-2.47.5-1.fc24.armv7hl.rpm

To get the Fedora 23 version of Glib package, along with its debug info, please type:

wget https://kojipkgs.fedoraproject.org//packages/glib2/2.46.2/1.fc23/armv7hl/glib2-debuginfo-2.46.2-1.fc23.armv7hl.rpm \
     https://kojipkgs.fedoraproject.org//packages/glib2/2.46.2/1.fc23/armv7hl/glib2-2.46.2-1.fc23.armv7hl.rpm

 

Running abipkgdiff

As all Libabigail tools, abipkgdiff inspects the debug info accompanying ELF binaries to get extensive information about the data types used by functions and global variables that are defined and exported.  Hence, the need for both the debug info and binary packages that we intend to review.

Thus, the command line invocation to compare the ABIs of the glib2-2.46.2-1.fc23.armv7hl.rpm and glib2-2.47.5-1.fc24.armv7hl.rpm packages looks like:

abipkgdiff --d1 glib2-debuginfo-2.46.2-1.fc23.armv7hl.rpm \
           --d2 glib2-debuginfo-2.47.5-1.fc24.armv7hl.rpm \
                glib2-2.46.2-1.fc23.armv7hl.rpm glib2-2.47.5-1.fc24.armv7hl.rpm

Note that I have artificially added '\' characters to shorten the long line and increase legibility.  Readers willing to try this command line might just do away with that '\'.

Careful readers would have also noted how the debug info packages are provided using the --d1 and --d2 options.

Understanding and reviewing the resulting ABI change report

Here is what the report emitted by abipkgdiff looks like (it's been redacted for the sake of simplicity):

================ changes of 'libgio-2.0.so.0.4600.2'===============
  Functions changes summary: 0 Removed, 1 Changed (141 filtered out), 43 Added functions
  Variables changes summary: 0 Removed, 0 Changed, 0 Added variable

  43 Added functions:
        (... a list of 43 added functions ...)
1 function with some indirect sub-type change:
( ... details about sub-type changes of the function ...)
===== end of changes of 'libgio-2.0.so.0.4600.2'=====

======= changes of 'libglib-2.0.so.0.4600.2'========
  Functions changes summary: 0 Removed, 1 Changed (3 filtered out), 2 Added functions
  Variables changes summary: 0 Removed, 0 Changed, 0 Added variable

  2 Added functions:
( ... a list of 2 added functions ...)
1 function with some indirect sub-type change:
( ... details about sub-type changes of the function ...)
======== end of changes of 'libglib-2.0.so.0.4600.2'=======

 

abipkgdiff is telling us that two shared libraries, libgio-2.0.so.0.4600.2 and libglib-2.0.so.0.4600.2, from the Glib binary package have ABI changes that are worth reviewing.

For both libraries, two kinds of ABI changes occurred:

  • New functions were defined and exported. This kind of ABI change does not make the newer version of the library be incompatible with applications linked against its former version.  However, reviewing the list of added functions (and globally exported variables) is important to ensure that library authors really intended to export those new functions.  For people only interested in ABI changes that might introduce incompatibilities, abipkgdiff does have an option which avoid showing the list of added functions and variables.  Finding that option in the manual is left as an exercise for interested readers :-)
  • Some publicly defined and exported functions had some sub-type changes. This means there is at least one function whose return type or parameter type changed in a way that impacts the ABI.  That ABI change might cause incompatibilities that are worth looking at closely.  We'll look at this kind of ABI changes a little bit deeper.

An interesting thing to note is how the tool filters out some changes, either because they are harmless from an ABI standpoint, or because they are redundant with the changes that are reported.  In the report summary showed above, those filtered-out changes are mentioned by the note:

(141 filtered out)

There is, of course, an option to show all changes without any filtering.  Another exercise for interested readers ;-)

Functions (or variables) sub-type changes

In all fairness, listing added or removed functions and variables is the easy game.  We don't need to look at debug info for that, do we?

Interesting matters start when looking into changes to types which impact the ABI.  These are the so-called changes to sub-types of functions or variables.

Here is the function sub-type change reported for the libgio-2.0.so.0.4600.2 library, in-extenso:

  1 function with some indirect sub-type change:

    [C]'function GDBusConnection* g_bus_get_finish(GAsyncResult*, GError**)' at gdbusconnection.c:7356:1 has some indirect sub-type changes:
      return type changed:
        in pointed to type 'typedef GDBusConnection' at giotypes.h:545:1:
          underlying type 'struct _GDBusConnection' at gdbusconnection.c:346:1 changed:
            1 data member change:
             type of 'GDBusWorker* _GDBusConnection::worker' changed:
               in pointed to type 'typedef GDBusWorker' at gdbusprivate.h:34:1:
                 underlying type 'struct GDBusWorker' at gdbusprivate.c:311:1 changed:
                   1 data member changes (3 filtered):
                    type of 'GSocket* GDBusWorker::socket' changed:
                      in pointed to type 'typedef GSocket' at giotypes.h:161:1:
                        underlying type 'struct _GSocket' at gsocket.h:68:1 changed:
                          1 data member change:
                           type of 'GSocketPrivate* _GSocket::priv' changed:
                             in pointed to type 'typedef GSocketPrivate' at gsocket.h:46:1:
                               underlying type 'struct _GSocketPrivate' at gsocket.c:231:1 changed:
                                 1 data member insertion:
                                   'guint _GSocketPrivate::connected_read', at offset 256 (in bits) at gsocket.c:245:1
                                 no data member change (1 filtered);

The core of this change report is that a new data member named 'connected_read', of type 'guint' has been added to the type 'struct _GSocketPrivate'.  That change was made in the source code in file 'gsocket.c' at line 231. That type is somehow part of the return type of the publicly defined and exported function g_bus_get_finish.

A cool feature of this report is that it tells us how the type that changed is related to the type of the publicly defined and exported function g_bus_get_finish which is actually an ABI entry point exported by the libgio library.

A careful read of the report suggests that 'struct _GSocketPrivate' is actually an implementation detail for the return type of the g_bus_get_finish function: 'GDBusConnection*'.  There are several facts which lead us to that conclusion.

One of these facts is that 'struct _GDBusConnection' is not defined in a header file (*.h), but rather in 'gdbusconnection.c'.  Note how the 'typedef GDBusConnection' (which is really struct _GDBusConnection) is declared in the header file 'giotypes.h'.  So in practice, users are not supposed to access the type 'struct _GDBusConnection', as the library authors carefully avoided to declare it in public headers.  It's an implementation detail.  So any of its sub-types are implementation details too.  As is 'struct _GSocketPrivate'.

Fortunately, there is a way to tell abipkgdiff to ignore some ABI changes: suppression specifications.

Suppressing change reports about implementation details

A suppression specification tells abipkgdiff to avoid reporting changes that have certain characteristics.  In this particular case, we want to avoid showing ABI changes about types that are not defined in a header file.  The suppression specification is thus going to be a file with this content:

[suppress_type]
  # Let's suppress change reports about types
  # not defined in header files.
  source_location_not_regexp = *\\\.h

The meaning of this suppression specification should be obvious.  For curious readers, details of the concepts and syntax of suppression specifications can be found in the manual.  We shall call that file 'implementation-details.abignore'.  abipkgdiff can thus be invoked with the suppression specification like this:

$ abipkgdiff --suppr implementation-details.abignore \
             --no-added-syms \
             --d1 glib2-debuginfo-2.46.2-1.fc23.armv7hl.rpm \
             --d2 glib2-debuginfo-2.47.5-1.fc24.armv7hl.rpm \
                  glib2-2.46.2-1.fc23.armv7hl.rpm glib2-2.47.5-1.fc24.armv7hl.rpm

$

There are two points worth noting here:

  • the suppression specification file has been passed to the tool using the --suppr command line option
  • the --no-added-syms command line option has been added to avoid reporting added functions or variables.

With these two additions this command now emits an empty report. This means, abipkgdiff now tells us there is no ABI change that is worth reporting, given the constraints we expressed.

In other words, with this suppression specification, we have just come up with a an ABI incompatible change detection tool, for the Glib package.

Glib packagers could even include that file in the package itself; abipkgdiff would then detect it and use (as the file name ends with the .abignore suffix) it as a suppression specification.

Script writers can use the exit code of abipkgdiff to detect when the tool reports ABI changes and even know about the kind of changes it reports.  This might be useful in setting up automatic ABI incompatible change detection systems.

Conclusion

abipkgdiff is a useful tool to review ABI changes in binary packages.  Its sophisticated filtering capabilities are tailored to help tool writers adapt to the different coding conventions used by various upstream projects.  What might be an ABI change worth reviewing for a given project might just be a false positive for another one.  By allowing each package to provide its own way of defining what a false positive is, we hope this tool empowers package maintainers by helping them increase the signal / noise ratio while ensuring the ABI compliance of ELF shared libraries they care about.

We welcome feedback! If you have any questions or comments relating to this article, please feel free to add it below or contact us.

Last updated: February 24, 2024