LIBABIGAIL(7)
๐ฆ LIBABIGAIL(7) ๐ฆ โ Library to analyze and compare ELF ABIs
โจ NAME โจ
libabigail โ bestie, itโs literally the Library to analyze and compare ELF ABIs. ๐
๐ OVERVIEW OF THE ABIGAIL FRAMEWORK ๐
okay so ABIGAIL stands for Application Binary Interface Generic Analysis and Instrumentation Library. iconic name, no cap. โจ
Itโs a framework that aims at helping developers and software distributors spot some ABI-related issues like interface incompatibility in ELF shared libraries by performing a static analysis of the ELF binaries at hand. ๐ซถ
The type of interface incompatibilities that Abigail focuses on is related to changes on the exported ELF functions and variables symbols, as well as layout and size changes of data types of the functions and variables exported by shared libraries. ๐
In other words, if the return type of a function exported by a shared library changes in an incompatible way from one version of a given shared library to another, we want Abigail to help people catch that. periodt. ๐
In more concrete terms, the Abigail framwork provides a shared library named libabigail which exposes an API to parse a shared library in ELF format (accompanied with its associated debug information in DWARF format), build an internal representation of all the functions and variables it exports, along with their types. Libabigail also builds an internal representation of the ELF symbols of these functions and variables. That information about these exported functions and variables is roughly what we consider as being the ABI of the shared library, at least, in the scope of Libabigail. ๐ธ
Aside of this internal representation, libabigail provides facilities to perform deep comparisons of two ABIs. That is, it can compare the types of two sets of functions or variables and represents the result in a way that allows it to emit textual reports about the differences. ๐ฅ
This allows us to write tools like abidiff that can compare the ABI of two shared libraries and represent the result in a meaningful enough way to help us spot ABI incompatibilities. There are several other tools that are built using the Abigail framwork. ๐
๐ CONCEPTS ๐
๐ฆ ABI artifacts ๐ฆ
An ABI artifact is a relevant part of the ABI of a shared library or program. Examples of ABI artifacts are exported types, variables, functions, or ELF symbols exported by a shared library. ๐
The set of ABI artifacts for a binary is called an ABI Corpus. โญ
Scalar types have a name, a size and other scalar properties. โจ
An aggregate type is generally constituted of sub-types. ๐ธ
For instance, class or union types are a kind of aggregate type. They have a name, a size and members. Each member can be either:
- a data member with a type representing a sub-type of the aggregate type, an offset and a name
- or a function with a type representing another sub-type of the aggregate type and a name. Some (virtual) member functions might have an offset as well.
Another example of aggregate type is a function type, which is the union of the return type and parameter types of the function. ๐
Functions and variables are declarations that have a name and a type. They are both associated with an ELF symbol. ๐ซถ
๐ ABI changes ๐
Libabigail represents and analyzes changes of types, functions, variables and ELF symbols. ๐ฅ
Those different kinds of ABI artifact changes are categorized depending on the impact they have on ABI compatibility. ๐
ABI artifact changes are reported by tools like abidiff and abipkgdiff in textual diff reports organized around the different kinds of changes listed below. ๐
Please note that users can instruct the tools to avoid emitting reports about certain changes. This is done via suppression specifications. ๐
๐ธ Harmless changes ๐ธ
A change in the diff report is considered harmless if it does not cause any ABI compatibility issue. That is, it does not prevent an application dynamically linked against a given version of a library to keep working with the changed subsequent versions of the same library. ๐
By default, abidiff_label and abipkgdiff_label filters harmless changes from the diff report. โจ
๐ฅ Harmful changes ๐ฅ
An ABI change is considered harmful if it might cause ABI compatibility issues. That is, it might prevent an application dynamically linked against a given version of a library to keep working with the changed subsequent versions of the same library. ๐ญ
Said otherwise, a harmful change is a change that is not harmless. ๐
A harmful change absolutely needs to be reviewed by a human being to know if it actually causes an ABI compatibility issue. ๐
By default, abidiff_label shows harmful changes for functions and global variables. โญ
If the tool emits at least one harmful change then the bit ABIDIFF_ABI_CHANGE of the bit-field returned in the exit code is set to 1. ๐
๐ Incompatible changes ๐
An incompatible change is a harmful change that causes ABI incompatibilities issues. In the colloquial language of programmers, such a change is said to break ABI. ๐ญ
If the abidiff_label or abipkgdiff_label tool emits at least one incompatible change then the bits ABIDIFF_ABI_INCOMPATIBLE_CHANGE and ABIDIFF_ABI_CHANGE of the bit-field returned in the exit code are set to 1. ๐ฅ
Changes recognized as ABI incompatible are the following:
- SONAME change of shared library
- change of the architecture of a binary
- remove of exported function or global variable ELF symbols (not necessarily described by debug information)
- removal of exported functions or global variables that are described by debug information.
- any incompatible change to the layout of the type which is used as a type of a global variable or a return or parameter type of a function. That incompatible layout type change might be:
- a modification to the size of the type
- or the removal of a data member
- or a modification of a data member offset
- or an incompatible change to a sub-type of the type
Please note that if a type is used neither as a type of a global variable nor as a return or parameter type of a function, then abidiff_label (or abipkgdiff_label) will not consider an incompatible change of this type as an incompatible ABI change. That change will just be considered as harmful. The ABIDIFF_ABI_CHANGE bit of the bit-field returned in the exit code is set to 1, meaning that the change requires a human review. This is a current limitation of the libabigail analysis framework that might be addressed in future refinements. ๐
๐ Suppression specifications ๐
โจ Definition โจ
A suppression specification file is a way for a user to instruct abidiff, abipkgdiff or any other relevant libabigail tool to avoid emitting reports for changes involving certain ABI artifacts. ๐ซถ
It contains directives (or specifications) that describe the set of ABI artifacts to avoid emitting change reports about. ๐ธ
๐ Introductory examples ๐
Its syntax is based on a simplified and customized form of Ini File Syntax. For instance, to specify that change reports on a type named FooPrivateType should be suppressed, one could write this suppression specification:
[suppress_type]
name = FooPrivateType
If we want to ensure that only change reports about structures named FooPrivateType should be suppressed, we could write:
[suppress_type]
type_kind = struct
name = FooPrivateType
But we could also want to suppress change reports avoid typedefs named FooPrivateType. In that case we would write:
[suppress_type]
type_kind = typedef
name = FooPrivateType
Or, we could want to suppress change reports about all struct which names end with the string โPrivateTypeโ:
[suppress_type]
type_kind = struct
name_regexp = ^.*PrivateType
Letโs now look at the generic syntax of suppression specification files. ๐
๐ Syntax ๐
๐ Properties ๐
More generally, the format of suppression lists is organized around the concept of property. Every property has a name and a value, delimited by the = sign. E.g:
name = value
Leading and trailing white spaces are ignored around property names and values. โจ
๐ฆ Regular expressions ๐ฆ
The value of some properties might be a regular expression. In that case, they must comply with the syntax of extended POSIX regular expressions. Note that Libabigail uses the regular expression engine of the GNU C Library. ๐ธ
๐ Escaping a character in a regular expression ๐
When trying to match a string that contains a * character, like in the pointer type int*, one must be careful to notice that the character * is a special character in the extended POSIX regular expression syntax. And that character must be escaped for the regular expression engine. Thus the regular expression that would match the string int* in a suppression file should be:
int\\*
Wait; but then why the two \ characters? Well, because the \ character is a special character in the Ini File Syntax used for specifying suppressions. So it must be escaped as well, so that the Ini File parser leaves a \ character intact in the data stream that is handed to the regular expression engine. Hence the \\ targeted at the Ini File parser. ๐ญ
So, in short, to escape a character in a regular expression, always prefix the character with the \\ sequence. ๐ฅ
๐ Modus operandi ๐
Suppression specifications can be applied at two different points of the processing pipeline of libabigail. ๐ซถ
In the default operating mode called โlate suppression modeโ, suppression specifications are applied to the result of comparing the in-memory internal representations of two ABIs. In this mode, if an ABI artifact matches a suppression specification, its changes are not mentioned in the ABI change report. The internal representation of the โsuppressedโ changed ABI artifact is still present in memory; it is just not mentioned in the ABI change report. The change report can still mention statistics about the number of changed ABI artifacts that were suppressed. ๐
This is the default mode of interpretation of suppression specifications for most tools including abidiff and abidw. โญ
Okay so bestie, thereโs another operating mode called โearly suppression modeโ, and in this mode suppression specifications are applied during the construction of the in-memory internal representation of a given ABI. In that mode, if an ABI artifact matches a suppression specification, no in-memory internal representation is built for it. The result? No change about the matched ABI artifact is going to be mentioned in the ABI change report, and no statistic about the number of suppressed ABI changes is available. Also, because suppressed ABI artifacts are removed from the in-memory internal representation in this mode, the amount of memory used by the internal representation is potentially smaller than the memory consumption in the late suppression mode.
Please note that this mode suppresses data from the internal representation constructed by libabigail and thus can lead to unintended and surprising consequences. Consequently this mode has to be set explicitly by users either by using thedropproperty in a suppression specification or by using a special tool command line option likeabidw โforce-early-suppression.
โจ Sections โจ
Properties are then grouped into arbitrarily named sections that shall not be nested. The name of the section is on a line by itself and is surrounded by square brackets, i.e:
[section_name]
property1_name = property1_value
property2_name = property2_value
A section might or might not have properties. Sections that expect to have properties and which are found nonetheless empty are just ignored. Properties that are not recognized by the reader are ignored as well. Lowkey very chill of them.
๐ Section names ๐
Each different section can be thought of as being a directive to suppress ABI change reports for a particular kind of ABI artifact. Itโs giving โone section, one job,โ fr fr.
๐ธ [suppress_file] ๐ธ
This directive prevents a given tool from loading a file (binary or abixml file) if its file name or other properties match certain properties. Thus, if the tool is meant to compare the ABIs of two files, and if the directive prevents it from loading either one of the files, then no comparison is performed.
Note that for the [suppress_file] directive to work, at least one of the following properties must be provided: file_name_regexp, file_name_not_regexp, soname_regexp, soname_not_regexp.
If none of the above properties are provided, then the [suppress_file] directive is simply ignored.
The potential properties of this section are listed below:
file_name_not_regexpโ Prevents the system from loading the file which name does not match the regular expression specified as value of this property. Usage:file_name_not_regexp = <regular-expression>file_name_regexpโ Prevents the system from loading the file which name matches the regular expression specified as value of this property. Usage:file_name_regexp = <regular-expression>labelโ Define a label for the section. A label is just an informative string that might be used by the tool to refer to a type suppression in error messages. There can also be some special label names that make the suppression system behave a certain way. Usage:label = <some-value>soname_regexpโ Prevents the system from loading the file which contains a SONAME property that matches the regular expression of this property. Note that this property also works on an abixml file if it contains a SONAME property. Usage:soname_regexp = <regular-expression>soname_not_regexpโ Prevents the system from loading the file which contains a SONAME property that does NOT match the regular expression of this property. Note that this property also works on an abixml file if it contains a SONAME property. Usage:soname_not_regexp = <regular-expression>
๐ฆ [suppress_type] ๐ฆ
This directive suppresses report messages about a type change.
Note that for the [suppress_type] directive to work, at least one of the following properties must be provided: file_name_regexp, file_name_not_regexp, soname_regexp, soname_not_regexp, name, name_regexp, name_not_regexp, source_location_not_in, source_location_not_regexp, type_kind.
If none of the above properties are provided, then the [suppress_type] directive is simply ignored.
The potential properties of this section are listed below:
accessed_throughโ Suppress change reports involving a type which is referred to either directly or through a pointer or a reference. The potential values of this property are the predefined keywords below: Usage:accessed_through = <some-predefined-values>directโ So if the[suppress_type]contains the property descriptionaccessed_through = direct, then changes about a type that is referred-to directly (i.e, not through a pointer or a reference) are going to be suppressed.pointerโ If theaccessed_throughproperty is set to the valuepointerthen changes about a type that is referred-to through a pointer are going to be suppressed.referenceโ If theaccessed_throughproperty is set to the valuereferencethen changes about a type that is referred-to through a reference are going to be suppressed.reference-or-pointerโ If theaccessed_throughproperty is set to the valuereference-or-pointerthen changes about a type that is referred-to through either a reference or a pointer are going to be suppressed.
For an extensive example of how to use this property, please check out the example below about suppressing change reports about types accessed either directly or through pointers.
changed_enumeratorsโ Suppresses change reports involving changes in the value of enumerators of a given enum type. This property is applied if thetype_kindproperty is set to the valueenum, at least. The value of thechanged_enumeratorsis a comma-separated list of the enumerators that the user expects to change. For instance: Usage:changed_enumerators = <list-of-enumerators>
changed_enumerators = LAST_ENUMERATORS0, LAST_ENUMERATOR1
changed_enumerators_regexpโ Suppresses change reports involving changes in the value of enumerators of a given enum type. This property is applied if thetype_kindproperty is set to the valueenum, at least. The value of thechanged_enumerators_regexpproperty is a comma-separated list of regular expressions that should match the names of the enumerators that the user expects to change. For instance: Usage:changed_enumerators_regexp = <list-of-enumerator-regular-expressions>
changed_enumerators_regexp = .*_MAX$, .*_LAST$, .*_NUM$, .*_NBITS$
In the example above, change reports to any enumerator which name ends with _MAX, _LAST, _NUM or _NBITS will be suppressed.
Note that for this property to be applied to changes to an enum type, the size of the enum type must NOT have changed.
dropโ If a type is matched by a suppression specification which contains the โdropโ property set to โyesโ (or to โtrueโ) then the type is not even going to be represented in the internal representation of the ABI being analyzed. This property makes its enclosing suppression specification to be applied in the early suppression specification mode. The net effect is that it potentially reduces the amount of memory used to represent the ABI being analyzed. Usage:drop = yes | no
Please note that for struct, class or enum types matching a suppression specification with this property having a value set to โyesโ (or to โtrueโ), if the specification also has a label property with a value set to libabigail::OPAQUE_TYPE_LABEL, then the type is transformed into an opaque type, rather than being just dropped on the floor. That also reduces the amount of memory used to represent the ABI being analyzed, but with potentially less disruption in the resulting ABI representation.
Please note that for this property to be effective, the enclosing suppression specification must have at least one of the following properties specified: name_regexp, name, name_regexp, source_location_not_in or source_location_not_regexp.
file_name_not_regexpโ Suppresses change reports about ABI artifacts that are defined in a binary file which name does not match the regular expression specified as value of this property. Usage:file_name_not_regexp = <regular-expression>file_name_regexpโ Suppresses change reports about ABI artifacts that are defined in a binary file which name matches the regular expression specified as value of this property. Usage:file_name_regexp = <regular-expression>has_data_memberโ Suppresses change reports involving a type which contains data members whose names are provided in the list value of this property. Usage:has_data_member = <list-of-data-member-names>
A usage examples of this property would be:
has_data_member = {private_data_member0, private_data_member1}
The property above would match any type which contains at least two data members whose names are private_data_member0 and private_data_member1.
Another usage examples of this property would be:
has_data_member = another_private_data_member
The property above would match any type which contains a data member which name is another_private_data_member0.
has_data_member_regexpโ Suppresses change reports involving a type which contains data members whose names match the regular expression provided as the value of this property. Usage:has_data_member_regexp = <a-regular-expression>
A usage examples of this property would be:
has_data_member_regexp = ^private_data_member
The property above would match any type which contains data members whose names match the regular expression ^private_data_member. In other words, it would match any type which contains data members whose names start with the string โprivate_data_memberโ.
has_data_member_inserted_atโ Usage:has_data_member_inserted_at = <offset-in-bit>
...and yeah, if the type's size changed too? This property will NOT suppress that change report. Like, not at all. Unless has_size_change is present and set to yes โ then it's chill. ๐
The offset-in-bit value is either:
- an integer value, expressed in bits, which denotes the offset of the insertion point of the data member, starting from the beginning of the relevant structure or class.
- data member offset selector expressions, such as:
- the keyword
endwhich is a named constant which value equals the offset of the end of the structure or class. - the keyword
offset_of_flexible_array_data_memberwhich is a named constant that evaluates to the offset of the flexible array data member contained in the relevant structure. - the function call expression
offset_of(data-member-name)where data-member-name is the name of a given data member of the relevant structure or class. The value of this function call expression is an integer that represents the offset of the data member denoted by data-member-name. - the function call expression
offset_after(data-member-name)where data-member-name is the name of a given data member of the relevant structure or class. The value of this function call expression is an integer that represents the offset of the point that comes right after the region occupied by the data member denoted by data-member-name. - the function call expression
offset_of_first_data_member_regexp(data-member-name-regexp)where data-member-name-regexp is a regular expression matching a data member. The value of this function call expression is an integer that represents the offset of the first data member which name matches the regular expression argument. If no data member of a given class type matches the regular expression, then the class type won't match the current directive. - the function call expression
offset_of_last_data_member_regexp(data-member-name-regexp)where data-member-name-regexp is a regular expression matching a data member. The value of this function call expression is an integer that represents the offset of the last data member which name matches the regular expression argument. If no data member of a given class type matches the regular expression, then the class type won't match the current directive.
- the keyword
has_data_member_inserted_betweenโ suppresses change reports involving a type which has at least one data member inserted at an offset that is comprised in the range between range-begin and range-end. Usage:
has_data_member_inserted_between = {<range-begin>, <range-end>}
Ok so bestie, heads up on two things. One: each of the values range-begin and range-end can be of the same form as the has_data_member_inserted_at property above. Two: if the size of the type changed, then the type change will NOT be suppressed by the evaluation of this property, unless the has_size_change property is present and set to yes. Also โ and this is kinda iconic โ data member deletions happening in the range between range-begin and range-end won't prevent the type change from being suppressed by the evaluation of this property if the size of the type doesn't change, or if the has_size_change property is present and set to yes. ๐ซถ
Usage examples of this properties are:
has_data_member_inserted_between = {8, 64}
or:
has_data_member_inserted_between = {16, end}
or:
has_data_member_inserted_between = {offset_after(member1), end}
has_data_members_inserted_betweenโ suppresses change reports involving a type which has multiple data member inserted in various offset ranges. A usage example of this property is, for instance:
has_data_members_inserted_between = {{8, 31}, {72, 95}}
This usage example suppresses change reports involving a type which has data members inserted in bit offset ranges [8 31] and [72 95]. The length of the sequence of ranges or this has_data_members_inserted_between is not bounded; it can be as long as the system can cope with. The values of the boundaries of the ranges are of the same kind as for the has_data_member_inserted_at property above. Please also note that if the size of the type changed, then the type will NOT be suppressed by the evaluation of this property, unless the has_size_change property is present and set to yes. Note that data member deletions happening in the defined ranges won't prevent the type change from being suppressed by the evaluation of this property if the size of the type doesn't change or if the has_size_change property is present and set to yes.
Another usage example of this property is thus:
has_data_members_inserted_between =
{
{offset_after(member0), offset_of(member1)},
{72, end}
}
has_strict_flexible_array_data_member_conversionโ suppresses change reports involving a type which has a "fake" flexible array member at the end of the struct which is converted to a real flexible array member. This would be a member likedata[1]being converted todata[]. Usage:
has_strict_flexible_array_data_member_conversion = yes | no
Please note that if the size of the type changed, then the type change will NOT be suppressed by the evaluation of this property, unless the has_size_change property is present and set to yes.
has_size_changeโ usage:
has_size_change = yes | no
This property is to be used in conjunction with the properties has_data_member_inserted_between, has_data_members_inserted_between, and has_strict_flexible_array_data_member_conversion. Those properties will not match a type change if the size of the type changes, unless the has_size_changes property is set to yes.
labelโ define a label for the section. Usage:
label = <some-value>
In general, a label is just an informative string that might be used by a tool to refer to a type suppression in error messages.
Note however that there are some special label values that can trigger a special kind of behavior. Below are those special label values:
libabigail::OPAQUE_TYPE_LABEL: A struct, class or enum type that matches a[suppress_type]section with this label property value is going to be replaced by an opaque type of the same kind. Note that an opaque type is essentially a declaration-only type, thus with no member. Also note that for a[suppress_type]section with this label to trigger the transformation of a type into an opaque type, the section must have thedropproperty value set toyes|true.
nameโ suppresses change reports involving types whose name equals the value of this property. Usage:
name = <a-value>
name_not_regexpโ suppresses change reports involving types whose name does NOT match the regular expression specified as value of this property. Said otherwise, this property specifies which types to keep, rather than types to suppress from reports. Usage:
name_not_regexp = <regular-expression>
name_regexpโ suppresses change reports involving types whose name matches the regular expression specified as value of this property. Usage:
name_regexp = <regular-expression>
soname_not_regexpโ suppresses change reports about ABI artifacts that are defined in a shared library which SONAME property does not match the regular expression specified as value of this property. Usage:
soname_not_regexp = <regular-expression>
soname_regexpโ suppresses change reports about ABI artifacts that are defined in a shared library which SONAME property matches the regular expression specified as value of this property. Usage:
soname_regexp = <regular-expression>
source_location_not_inโ suppresses change reports involving a type which is defined in a file which path is NOT listed in the value list-of-file-paths. Note that the value is a comma-separated list of file paths e.g, this property
source_location_not_in = libabigail/abg-ir.h, libabigail/abg-dwarf-reader.h
suppresses change reports about all the types that are NOT defined in header files whose path end up with the strings libabigail/abg-ir.h or libabigail/abg-dwarf-reader.h. Usage:
source_location_not_in = <list-of-file-paths>
source_location_not_regexpโ suppresses change reports involving a type which is defined in a file which path does NOT match the regular expression provided as value of the property. E.g, this property
source_location_not_regexp = libabigail/abg-.*\\.h
suppresses change reports involving all the types that are NOT defined in header files whose path match the regular expression provided a value of the property. Usage:
source_location_not_regexp = <regular-expression>
type_kindโ suppresses change reports involving a certain kind of type. The kind of type to suppress change reports for is specified by the possible values listed above. Usage:
type_kind = class | struct | union | enum |
array | typedef | builtin
class: suppress change reports for class types. Note that even if class types don't exist for C, this value still triggers the suppression of change reports for struct types, in C. In C++ however, it should do what it suggests.struct: suppress change reports for struct types in C or C++. Note that the valueclassabove is a super-set of this one.union: suppress change reports for union types.
enumโ same deal but for enum types: their change reports get muted. ๐ซถarrayโ mutes change reports for array types. โจtypedefโ mutes change reports for typedef types. ๐builtinโ mutes change reports for built-in (a.k.a. native) types. Thinkchar,int,unsigned int, all those OG squad members. ๐
๐ธ the [suppress_function] directive
Okay so this one silences report messages about changes across a whole set of functions. Like a group-chat mute but for diff reports. ๐
โ ๏ธ Real talk: for [suppress_function] to actually do anything, you gotta give it at least one of these properties. No properties = no vibes, no effect:
label, file_name_regexp, file_name_not_regexp, soname_regexp,
soname_not_regexp, name, name_regexp, name_not_regexp, parameter,
return_type_name, return_type_regexp, symbol_name,
symbol_name_regexp, symbol_name_not_regexp, symbol_version,
symbol_version_regexp.
If you provide none of the above, the [suppress_function] directive is simply ignored. Nothing happens. It's giving ghosting. ๐ญ
The potential properties of this section are: ๐ฆ
change_kindโ specifies the kind of changes this suppression spec applies to. Usage:
change_kind = <predefined-possible-values>
The possible values of this property, plus what they mean, are listed below:
function-subtype-changeโ applies to functions that have at least one sub-type that has changed.added-functionโ applies to functions that have been added to the binary. New bestie arrives. ๐deleted-functionโ applies to functions that have been removed from the binary. Gone, and honestly rude. ๐allโ applies to functions with all of the changes above. Note that not providing thechange_kindproperty at all is exactly equivalent to setting it toall. So the default is "everything, babe." ๐
dropโ usage:
drop = yes | no
If a function matches a suppression spec that has the drop property set to yes (or to true), then that function isn't even going to be represented in the internal representation of the ABI being analyzed. Like, it doesn't even make the cast list. ๐ฌ
This property is what pushes its enclosing suppression spec into the early suppression specification mode, and the net effect is that it potentially reduces the memory used to represent the ABI being analyzed. Efficiency slay. โก
๐ Heads up: for this property to be effective, the enclosing suppression spec must have at least one of these specified:name_regexp,name,name_regexp,source_location_not_inorsource_location_not_regexp.
file_name_not_regexpโ usage:
file_name_not_regexp = <regular-expression>
Suppresses change reports about ABI artifacts that are defined in a binary file whose name does not match the regular expression you hand it. ๐
file_name_regexpโ usage:
file_name_regexp = <regular-expression>
Suppresses change reports about ABI artifacts defined in a binary file whose name matches that regular expression. ๐
labelโ usage:
label = <some-value>
This property is the same as the label property defined above. Yes bestie, same one, it's a rerun (and it ate). โจ
nameโ usage:
name = <some-value>
Suppresses change reports involving functions whose name equals the value of this property. Exact match energy only. ๐
name_not_regexpโ usage:
name_not_regexp = <regular-expression>
Suppresses change reports involving functions whose names don't match the regular expression you passed. ๐
The rules for functions that have several symbol names are the same rules as for the name_regexp property above. Keep reading, it's right there. ๐
name_regexpโ usage:
name_regexp = <regular-expression>
Suppresses change reports involving functions whose name matches the regex you passed as this property's value.
Now let's talk about the messy case: functions that have several symbol names. This happens when the underlying symbol for the function has aliases, and each symbol name is actually one alias name. ๐ซ
In that case, if the regular expression matches the name of at least one of the alias names, then it must match the names of all of the aliases of the function for the directive to actually suppress the diff reports for said function. All-or-nothing, no partial credit. ๐ฅ
parameterโ usage:
parameter = <function-parameter-specification>
Suppresses change reports involving functions whose parameters match the parameter specification you give it. ๐ง
The format of the function parameter specification is:
' <parameter-index> <space> <type-name-or-regular-expression>
That is: an apostrophe, then a number which is the index of the parameter, then one of several spaces, then either the name of the type of the parameter or a regular expression describing a family of parameter type names. ๐ธ
If the parameter type name is designated by a regular expression, then that regular expression must be enclosed between two slashes, like /some-regular-expression/. ๐
The index of the first parameter of the function is zero. And for member functions (methods of classes), the first parameter is the one that comes right after the implicit this pointer parameter. ๐
Examples of function parameter specifications are:
'0 int
which means: the parameter at index 0, whose type name is int. ๐
'4 unsigned char*
which means: the parameter at index 4, whose type name is unsigned char*. โญ
'2 /^foo.*&/
which means: the parameter at index 2, whose type name starts with the string "foo" and ends with an &. In other words, this is the third parameter, and it's a reference on a type that starts with the string "foo". Literally iconic regex flex. ๐
return_type_nameโ usage:
return_type_name = <some-value>
Suppresses change reports involving functions whose return type name equals the value of this property. ๐
return_type_regexpโ usage:
return_type_regexp = <regular-expression>
Suppresses change reports involving functions whose return type name matches the regex you passed. โจ
soname_regexpโ usage:
soname_regexp = <regular-expression>
Suppresses change reports about ABI artifacts that are defined in a shared library whose SONAME property matches the regular expression given as this property's value. ๐ฆ
soname_not_regexpโ usage:
soname_not_regexp = <regular-expression>
Suppresses change reports about ABI artifacts that are defined in a shared library whose SONAME property does not match the regex given as this property's value. ๐
symbol_nameโ usage:
symbol_name = <some-value>
Suppresses change reports involving functions whose symbol name equals the value of this property. ๐ฏ
symbol_name_regexpโ usage:
symbol_name_regexp = <regular-expression>
Suppresses change reports involving functions whose symbol name matches the regex you passed.
Same alias situation as before, so let's go again: functions can have several symbol names, which happens when the underlying symbol for the function has aliases, and each symbol name is actually one alias name. In that case, the regular expression must match the names of all of the aliases of the function for the directive to actually suppress the diff reports for said function. ๐ฅ
symbol_name_not_regexpโ usage:
symbol_name_not_regexp = <regular-expression>
Suppresses change reports involving functions whose symbol name does not match the regex you passed. ๐
symbol_versionโ usage:
symbol_version = <some-value>
Suppresses change reports involving functions whose symbol version equals the value of this property. ๐ท๏ธ
symbol_version_regexpโ usage:
symbol_version_regexp = <regular-expression>
Suppresses change reports involving functions whose symbol version matches the regex you passed. ๐
๐ the [suppress_variable] directive
And this one? It suppresses report messages about changes on a set of variables. Same energy, different cast member. ๐ซถ
โ ๏ธ For [suppress_variable] to work, at least one of these properties must be provided:
label, file_name_regexp, file_name_not_regexp, soname_regexp,
soname_not_regexp, name, name_regexp, name_not_regexp, symbol_name,
symbol_name_regexp, symbol_name_not_regexp, symbol_version,
symbol_version_regexp, type_name, type_name_regexp.
And if none of the above are provided? The [suppress_variable] directive is simply ignored. Bestie said nothing, did nothing. ๐ญ
The potential properties of this section are:
labelโ usage:
label = <some-value>
This property is the same as the label property defined above. Throwback, but make it consistent. โจ
file_name_regexpโ usage:
file_name_regexp = <regular-expression>
Suppresses change reports about ABI artifacts that are defined in a binary file whose name matches the regular expression you passed. ๐
file_name_not_regexpโ usage:
file_name_not_regexp = <regular-expression>
Suppresses change reports about ABI artifacts that are defined in a binary file whose name does not match the regex you passed. ๐
soname_regexpโ usage:
soname_regexp = <regular-expression>
Suppresses change reports about ABI artifacts that are defined in a shared library whose SONAME property matches the regex you passed. ๐ธ
soname_not_regexpโ usage:
soname_not_regexp = <regular-expression>
Suppresses change reports about ABI artifacts that are defined in a shared library whose SONAME property does not match the regex you passed. ๐ฆ
nameโ usage:
name = <some-value>
Suppresses change reports involving variables whose name equals the value of this property. ๐ฏ
name_regexpโ usage:
name_regexp = <regular-expression>
Suppresses change reports involving variables whose name matches the regular expression specified as the value of this property. ๐
change_kindโ tells the suppressor which kind of change we're talking about. Usage goes like:
change_kind = <predefined-possible-values>
The possible values (and what each one means) are literally the same as when you use this property in the [suppress_function] section โ same vibes, different section, bestie. โจ
symbol_nameโ silences change reports for variables whose symbol name is exactly equal to this property's value. Usage:
symbol_name = <some-value>
symbol_name_regexpโ same energy, but matchy: silences change reports for variables whose symbol name matches the regular expression you put here. Usage:
symbol_name_regexp = <regular-expression>
symbol_name_not_regexpโ the hater version ๐ : silences change reports for variables whose symbol name does not match the regular expression you put here. Usage:
symbol_name_not_regexp = <regular-expression>
symbol_versionโ silences change reports for variables whose symbol version equals this value. Usage:
symbol_version = <some-value>
symbol_version_regexpโ silences change reports for variables whose symbol version matches this regular expression. Usage:
symbol_version_regexp = <regular-expression>
type_nameโ silences change reports for variables whose type name equals this value. Usage:
type_name = <some-value>
type_name_regexpโ silences change reports for variables whose type name matches this regular expression. Usage:
type_name_regexp = <regular-expression>
๐ฌ Comments
If a line starts with a ; or a # ASCII character, congrats, that's a comment ๐. Comment lines are simply ignored โ they're giving background character energy and nobody's reading them.
๐งช Code examples
1. Suppressing change reports about types.
Okay so picture this: we have a library named libtest1-v0.so and it contains this extremely useful code:
$ cat -n test1-v0.cc
1 // A forward declaration for a type considered to be opaque to
2 // function foo() below.
3 struct opaque_type;
4
5 // This function cannot touch any member of opaque_type. Hence,
6 // changes to members of opaque_type should not impact foo, as far as
7 // ABI is concerned.
8 void
9 foo(opaque_type*)
10 {
11 }
12
13 struct opaque_type
14 {
15 int member0;
16 char member1;
17 };
$
Now let's be messy and change the layout of struct opaque_type by inserting a data member around line 15, which gives us a whole new version of the library. We shall name it libtest1-v1.so (iconic naming, no notes):
$ cat -n test1-v1.cc
1 // A forward declaration for a type considered to be opaque to
2 // function foo() below.
3 struct opaque_type;
4
5 // This function cannot touch any member of opaque_type; Hence,
6 // changes to members of opaque_type should not impact foo, as far as
7 // ABI is concerned.
8 void
9 foo(opaque_type*)
10 {
11 }
12
13 struct opaque_type
14 {
15 char added_member; // <-- a new member got added here now.
16 int member0;
17 char member1;
18 };
$
Let's compile both of them โ and do not forget to turn debug info generation on, bestie, that part matters:
$ g++ -shared -g -Wall -o libtest1-v0.so test1-v0.cc
$ g++ -shared -g -Wall -o libtest1-v1.so test1-v1.cc
Now let's ask abidiff which ABI differences it's seeing between libtest1-v0.so and libtest1-v1.so:
$ abidiff libtest1-v0.so libtest1-v1.so
Functions changes summary: 0 Removed, 1 Changed, 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function void foo(opaque_type*)' has some indirect sub-type changes:
parameter 0 of type 'opaque_type*' has sub-type changes:
in pointed to type 'struct opaque_type':
size changed from 64 to 96 bits
1 data member insertion:
'char opaque_type::added_member', at offset 0 (in bits)
2 data member changes:
'int opaque_type::member0' offset changed from 0 to 32
'char opaque_type::member1' offset changed from 32 to 64
So abidiff is telling us that opaque_type's layout changed in a way that's, like, significant as far as ABI implications are concerned โ in theory. Because after all, a sub-type (struct opaque_type) of an exported function (foo()) saw its layout change, and that might have non negligible ABI implications. ๐ซ
But in practice here, the programmer of the litest1-v1.so library knows that the "soft" contract between the function foo() and the type struct opaque_type is to stay away from the data members of the type. So layout changes of struct opaque_type should not impact foo().
So to teach abidiff about this soft contract and have it avoid emitting what amounts to false positives in this case, we write the suppression specification file below:
$ cat test1.suppr
[suppress_type]
type_kind = struct
name = opaque_type
Translated in plain English, this suppression specification would read: "Do not emit change reports about a struct which name is opaque_type". Slay, honestly.
Let's now invoke abidiff on the two versions of the library again, but this time with the suppression specification:
$ abidiff --suppressions test1.suppr libtest1-v0.so libtest1-v1.so
Functions changes summary: 0 Removed, 0 Changed (1 filtered out), 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
See that? abidiff doesn't report the change anymore; instead it tells us it was filtered out. Literally the "1 filtered out" is doing main character work. ๐
Suppressing change reports about types with data member insertions
Suppose the first version of a library named libtest3-v0.so has this source code:
/* Compile this with:
gcc -g -Wall -shared -o libtest3-v0.so test3-v0.c
*/
struct S
{
char member0;
int member1; /*
between member1 and member2, there is some padding,
at least on some popular platforms. On
these platforms, adding a small enough data
member into that padding shouldn't change
the offset of member1. Right?
*/
};
int
foo(struct S* s)
{
return s->member0 + s->member1;
}
Now, suppose the second version of the library named libtest3-v1.so has this source code in which a data member has been added in the padding space of struct S and another data member has been added at its end:
/* Compile this with:
gcc -g -Wall -shared -o libtest3-v1.so test3-v1.c
*/
struct S
{
char member0;
char inserted1; /* <---- A data member has been added here... */
int member1;
char inserted2; /* <---- ... and another one has been added here. */
};
int
foo(struct S* s)
{
return s->member0 + s->member1;
}
In libtest3-v1.so, adding char data members S::inserted1 and S::inserted2 can be considered harmless (from an ABI compatibility perspective), at least on the x86 platform, because that doesn't change the offsets of the data members S::member0 and S::member1. But then running abidiff on these two versions of the library yields:
$ abidiff libtest3-v0.so libtest3-v1.so
Functions changes summary: 0 Removed, 1 Changed, 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function int foo(S*)' has some indirect sub-type changes:
parameter 0 of type 'S*' has sub-type changes:
in pointed to type 'struct S':
type size changed from 64 to 96 bits
2 data member insertions:
'char S::inserted1', at offset 8 (in bits)
'char S::inserted2', at offset 64 (in bits)
$
That is, abidiff shows us the two changes, even though we (the developers of that very involved library) know these changes are harmless in this particular context. ๐
Luckily, we can devise a suppression specification that essentially tells abidiff to filter out change reports about adding a data member between S::member0 and S::member1, and adding a data member at the end of struct S. We have written such a suppression specification in a file called test3-1.suppr and it unsurprisingly looks like:
[suppress_type]
name = S
has_data_member_inserted_between = {offset_after(member0), offset_of(member1)}
has_data_member_inserted_at = end
Now running abidiff with this suppression specification yields:
$ ../build/tools/abidiff --suppressions test3-1.suppr libtest3-v0.so libtest3-v1.so
Functions changes summary: 0 Removed, 0 Changed (1 filtered out), 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
$
Hooora! \o/ (I guess) ๐
Suppressing change reports about types accessed either directly or through pointers
Suppose we have a first version of an object file which source code is the file widget-v0.cc below:
// Compile with: g++ -g -c widget-v0.cc
struct widget
{
int x;
int y;
widget()
:x(), y()
{}
};
void
fun0(widget*)
{
// .. do stuff here.
}
void
fun1(widget&)
{
// .. do stuff here ..
}
void
fun2(widget w)
{
// ... do other stuff here ...
}
Now suppose in the second version of that file, named widget-v1.cc, we have added some data members at the end of the type struct widget; here is what the content of that file would look like:
// Compile with: g++ -g -c widget-v1.cc
struct widget
{
int x;
int y;
int w; // We have added these two new data members here ..
int h; // ... and here.
widget()
: x(), y(), w(), h()
{}
};
void
fun0(widget*)
{
// .. do stuff here.
}
void
fun1(widget&)
{
// .. do stuff here ..
}
void
fun2(widget w)
{
// ... do other stuff here ...
}
okay so a lil explainer is totally due here ๐
abidiff detects that two data member got added at the end of struct widget. it also tells us that the type change impacts the exported function fun0() which uses the type struct widget through a pointer, in its signature.
Careful readers will notice that the change to struct widget also impacts the exported function fun1(), that uses type struct widget through a reference. But then abidiff doesnโt tell us about the impact on that function fun1() because it has evaluated that change as being redundant with the change it reported on fun0(). It has thus filtered it out, to avoid cluttering the output with noise โจ.
Redundancy detection and filtering is fine and helpful to avoid burying the important information in a sea of noise. However, it must be treated with care, by fear of mistakenly filtering out relevant and important information ๐ญ.
That is why abidiff tells us about the impact that the change to struct widget has on function fun2(). In this case, that function uses the type struct widget directly (in its signature). It does not use it via a pointer or a reference. In this case, the direct use of this type causes fun2() to be exposed to a potentially harmful ABI change. Hence, the report about fun2() is not filtered out, even though itโs about that same change on struct widget ๐ฅ.
To go further in suppressing reports about changes that are harmless and keeping only those that we know are harmful, we would like to go tell abidiff to suppress reports about this particular struct widget change when it impacts uses of struct widget through a pointer or reference. In other words, suppress the change reports about fun0() and fun1(). We would then write this suppression specification, in file widget.suppr:
[suppress_type]
name = widget
type_kind = struct
has_data_member_inserted_at = end
accessed_through = reference-or-pointer
# So this suppression specification says to suppress reports about
# the type 'struct widget', if this type was added some data member
# at its end, and if the change impacts uses of the type through a
# reference or a pointer.
Invoking abidiff on widget-v0.o and widget-v1.o with this suppression specification yields:
$ abidiff --suppressions widget.suppr widget-v0.o widget-v1.o
Functions changes summary: 0 Removed, 1 Changed (2 filtered out), 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function void fun2(widget)' has some indirect sub-type changes:
parameter 1 of type 'struct widget' has sub-type changes:
type size changed from 64 to 128 bits
2 data member insertions:
'int widget::w', at offset 64 (in bits)
'int widget::h', at offset 96 (in bits)
$
As expected, I guess ๐โโ๏ธ.
Suppressing change reports about functions.
Suppose we have a first version a library named libtest2-v0.so whose source code is:
$ cat -n test2-v0.cc
1 struct S1
2 {
3 int m0;
4
5 S1()
6 : m0()
7 {}
8 };
9
10 struct S2
11 {
12 int m0;
13
14 S2()
15 : m0()
16 {}
17 };
18
19 struct S3
20 {
21 int m0;
22
23 S3()
24 : m0()
25 {}
26 };
27
28 int
29 func(S1&)
30 {
31 // suppose the code does something with the argument.
32 return 0;
33
34 }
35
36 char
37 func(S2*)
38 {
39 // suppose the code does something with the argument.
40 return 0;
41 }
42
43 unsigned
44 func(S3)
45 {
46 // suppose the code does something with the argument.
47 return 0;
48 }
$
And then we come up with a second version libtest2-v1.so of that library; the source code is modified by making the structures S1, S2, S3 inherit another struct:
$ cat -n test2-v1.cc
1 struct base_type
2 {
3 int m_inserted;
4 };
5
6 struct S1 : public base_type // <--- S1 now has base_type as its base
7 // type.
8 {
9 int m0;
10
11 S1()
12 : m0()
13 {}
14 };
15
16 struct S2 : public base_type // <--- S2 now has base_type as its base
17 // type.
18 {
19 int m0;
20
21 S2()
22 : m0()
23 {}
24 };
25
26 struct S3 : public base_type // <--- S3 now has base_type as its base
27 // type.
28 {
29 int m0;
30
31 S3()
32 : m0()
33 {}
34 };
35
36 int
37 func(S1&)
38 {
39 // suppose the code does something with the argument.
40 return 0;
41
42 }
43
44 char
45 func(S2*)
46 {
47 // suppose the code does something with the argument.
48 return 0;
49 }
50
51 unsigned
52 func(S3)
53 {
54 // suppose the code does something with the argument.
55 return 0;
56 }
$
Now letโs build the two libraries:
g++ -Wall -g -shared -o libtest2-v0.so test2-v0.cc
g++ -Wall -g -shared -o libtest2-v0.so test2-v0.cc
Letโs look at the output of abidiff:
$ abidiff libtest2-v0.so libtest2-v1.so
Functions changes summary: 0 Removed, 3 Changed, 0 Added functions
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
3 functions with some indirect sub-type change:
[C]'function unsigned int func(S3)' has some indirect sub-type changes:
parameter 0 of type 'struct S3' has sub-type changes:
size changed from 32 to 64 bits
1 base class insertion:
struct base_type
1 data member change:
'int S3::m0' offset changed from 0 to 32
[C]'function char func(S2*)' has some indirect sub-type changes:
parameter 0 of type 'S2*' has sub-type changes:
in pointed to type 'struct S2':
size changed from 32 to 64 bits
1 base class insertion:
struct base_type
1 data member change:
'int S2::m0' offset changed from 0 to 32
[C]'function int func(S1&)' has some indirect sub-type changes:
parameter 0 of type 'S1&' has sub-type changes:
in referenced type 'struct S1':
size changed from 32 to 64 bits
1 base class insertion:
struct base_type
1 data member change:
'int S1::m0' offset changed from 0 to 32
$
Letโs tell abidiff to avoid showing us the differences on the overloads of func that takes either a pointer or a reference. For that, we author this simple suppression specification:
$ cat -n libtest2.suppr
1 [suppress_function]
2 name = func
3 parameter = '0 S1&
4
5 [suppress_function]
6 name = func
7 parameter = '0 S2*
$
And then letโs invoke abidiff with the suppression specification:
$ ../build/tools/abidiff --suppressions libtest2.suppr libtest2-v0.so libtest2-v1.so
Functions changes summary: 0 Removed, 1 Changed (2 filtered out), 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function unsigned int func(S3)' has some indirect sub-type changes:
parameter 0 of type 'struct S3' has sub-type changes:
size changed from 32 to 64 bits
1 base class insertion:
struct base_type
1 data member change:
'int S3::m0' offset changed from 0 to 32
The suppression specification could be reduced using regular expressions:
$ cat -n libtest2-1.suppr
1 [suppress_function]
2 name = func
3 parameter = '0 /^S.(&|\\*)/
$
$ ../build/tools/abidiff --suppressions libtest2-1.suppr libtest2-v0.so libtest2-v1.so
Functions changes summary: 0 Removed, 1 Changed (2 filtered out), 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function unsigned int func(S3)' has some indirect sub-type changes:
parameter 0 of type 'struct S3' has sub-type changes:
size changed from 32 to 64 bits
1 base class insertion:
struct base_type
1 data member change:
'int S3::m0' offset changed from 0 to 32
$
๐ TOOLS ๐
โจ Overview โจ
Okay so, the upstream code repository of Libabigail contains several tools written using the library. They're maintained and released as part of the project, and โ bestie, this is the good part โ all tools come with a bash-completion script. All of them. Every single one. We love a consistent UX ๐.
๐ Tools manuals ๐
๐ abidiff ๐
abidiff compares the Application Binary Interfaces (ABI) of two shared libraries in ELF format. It emits a meaningful report describing the differences between the two ABIs. It's giving receipts ๐งพโจ.
This tool can also compare the textual representations of the ABI of two ELF binaries (as emitted by abidw) or an ELF binary against a textual representation of another ELF binary. Mix and match, iykyk ๐
.
For a comprehensive ABI change report between two input shared libraries that includes changes about function and variable sub-types, abidiff uses by default debug information in DWARF format, if present, otherwise it compares interfaces using debug information in CTF or BTF formats, if present. Finally, if no debug info in these formats is found, it only considers ELF symbols and report about their addition or removal. (So it's DWARF first, then CTF or BTF, and if all of that's missing it lowkey just vibes with the ELF symbols ๐.)
This tool uses the libabigail library to analyze the binary as well as its associated debug information. Here is its general mode of operation:
When instructed to do so, a binary and its associated debug information is read and analyzed. To that effect, libabigail analyzes by default the descriptions of the types reachable by the interfaces (functions and variables) that are visible outside of their translation unit. Once that analysis is done, an Application Binary Interface Corpus is constructed by only considering the subset of types reachable from interfaces associated to ELF symbols that are defined and exported by the binary. It's that final ABI corpus which libabigail considers as representing the ABI of the analyzed binary. Main character energy, honestly ๐.
Libabigail then has capabilities to generate textual representations of ABI Corpora, compare them, analyze their changes and report about them. Iconic behavior, no notes ๐ฅ.
๐ Invocation ๐
abidiff [options] <first-shared-library> <second-shared-library>
๐ธ Environment ๐ธ
abidiff loads two default suppression specifications files, merges their content and use it to filter out ABI change reports that might be considered as false positives to users. Like, it's literally muting the noise so the real tea can shine โโจ.
- Default system-wide suppression specification file
It's located by the optional environment variable LIBABIGAIL_DEFAULT_SYSTEM_SUPPRESSION_FILE. If that environment variable is not set, then abidiff tries to load the suppression file $libdir/libabigail/libabigail-default.abignore. If that file is not present, then no default system-wide suppression specification file is loaded.
- Default user suppression specification file
It's located by the optional environment LIBABIGAIL_DEFAULT_USER_SUPPRESSION_FILE. If that environment variable is not set, then abidiff tries to load the suppression file $HOME/.abignore. If that file is not present, then no default user suppression specification is loaded.
- Parallel processing
The environment variable ABIGAIL_THREAD_POOL_SIZE can be set to either an integer, or a percentage value, e.g, 10%. The integer value defines the number of threads used by Libabigail to perform parallel processing when applicable. The percentage defines the percentage of the total available cores to use by Libabigail to perform the parallel processing when applicable.
By default, Libabigail uses all the available cores to perform its parallel processing. It said what it said ๐.
๐ Options ๐
--add-binaries1 <bin1,bin2,bin3,..>โ For each of the comma-separated binaries given in argument to this option, if the binary is found in the directory specified by the--added-binaries-dir1option, thenabidiffloads the ABI corpus of the binary and adds it to a set of corpora (called an ABI Corpus Group) that includes the first argument ofabidiff. That ABI corpus group is then compared against the second corpus group given in argument toabidiff.--add-binaries2 <bin1,bin2,bin3,..>โ For each of the comma-separated binaries given in argument to this option, if the binary is found in the directory specified by the--added-binaries-dir2option, thenabidiffloads the ABI corpus of the binary and adds it to a set of corpora (called an ABI Corpus Group) that includes the second argument ofabidiff. That ABI corpus group is then compared against the first corpus group given in argument toabidiff.--added-binaries-dir1|--abd1<added-binaries-directory-1> โ This option is to be used in conjunction with the--add-binaries1,--follow-dependenciesand--list-dependenciesoptions. Binaries referred to by these options, if found in the directory added-binaries-directory-1, are loaded as ABI corpus and are added to the first ABI corpus group that is to be used in the comparison.--added-binaries-dir2|--abd2<added-binaries-directory-2> โ This option is to be used in conjunction with the--add-binaries2,--follow-dependenciesand--list-dependenciesoptions. Binaries referred to by these options, if found in the directory added-binaries-directory-2, are loaded as ABI corpus and are added to the second ABI corpus group to be used in the comparison.--added-fnsโ In the resulting report about the differences between first-shared-library and second-shared-library, only display the globally defined functions that were added to second-shared-library. New functions only. Fresh out the oven ๐.--added-varsโ In the resulting report about the differences between first-shared-library and second-shared-library, only display the global variables that were added (defined) to second-shared-library.--allow-non-exported-interfacesโ When looking at the debug information accompanying a binary, this tool analyzes the descriptions of the types reachable by the interfaces (functions and variables) that are visible outside of their translation unit. Once that analysis is done, an ABI corpus is constructed by only considering the subset of types reachable from interfaces associated to ELF symbols that are defined and exported by the binary. It's those final ABI Corpora that are compared by this tool.
The problem with that approach however is that analyzing all the interfaces that are visible from outside their translation unit can amount to a lot of data, especially when those binaries are applications, as opposed to shared libraries. One example of such applications is the Linux Kernel. Analyzing massive ABI Corpora like these can be extremely slow ๐ญ.
In the presence of an "average sized" binary however one can afford having libabigail analyze all interfaces that are visible outside of their translation unit, using this option.
Note that this option is turned on by default, unless we are in the presence of the Linux Kernel. Yes, the kernel gets special treatment ๐ซถ.
--btfโ When comparing binaries, extract ABI information from BTF debug information, if present.--changed-fnsโ In the resulting report about the differences between first-shared-library and second-shared-library, only display the changes in sub-types of the global functions defined in first-shared-library.--changed-varsโ In the resulting report about the differences between first-shared-library and second-shared-library, only display the changes in the sub-types of the global variables defined in first-shared-library.--ctfโ When comparing binaries, extract ABI information from CTF debug information, if present.--debug-info-dir1|--d1<di-path1> โ For cases where the debug information for first-shared-library is split out into a separate file, tellsabidiffwhere to find that separate debug information file.
Note that di-path must point to the root directory under which the debug information is arranged in a tree-like manner. Under Red Hat based systems, that directory is usually <root>/usr/lib/debug.
This option can be provided several times with different root directories. In that case, abidiff will potentially look into all those root directories to find the split debug info for first-shared-library.
Note also that this option is not mandatory for split debug information installed by your system's package manager because then abidiff knows where to find it. It's got the map already ๐บ๏ธโจ.
--debug-info-dir2|--d2<di-path2> โ Like--debug-info-dir1, this options tellsabidiffwhere to find the split debug information for the second-shared-library file.
This option can be provided several times with different root directories. In that case, abidiff will potentially look into all those root directories to find the split debug info for second-shared-library.
--debug-self-comparisonโ In this mode, error messages are emitted for types which fail type canonicalization, in some circumstances, when comparing a binary against itself.
When comparing a binary against itself, canonical types of the second binary should be equal (as much as possible) to canonical types of the first binary. When some discrepancies are detected in this mode, an abort signal is emitted and execution is halted. This option should be used while executing the tool in a debugger, for troubleshooting purposes ๐.
This is an optional debugging and sanity check option. To enable it the libabigail package needs to be configured with the โenable-debug-self-comparison configure option.
--debug-tcโ In this mode, the process of type canonicalization is put under heavy scrutiny. Basically, during type canonicalization, each type comparison is performed twice: once in a structural mode (comparing every sub-type member-wise), and once using canonical comparison. The two comparisons should yield the same result. Otherwise, an abort signal is emitted and the process can be debugged to understand why the two kinds of comparison yield different results.
This is an optional debugging and sanity check option. To enable it the libabigail package needs to be configured with the โenable-debug-type-canonicalization configure option.
--deleted-fnsโ In the resulting report about the differences between first-shared-library and second-shared-library, only display the globally defined functions that got deleted from first-shared-library. Gone girl era ๐ญ๐.--deleted-vars
So the report that comes out of comparing first-shared-library against second-shared-library will only show you the globally defined variables that got deleted from first-shared-library. Nothing else. Just the sad little deletions. ๐
--drop <regex>โ while it's reading the first-shared-library and second-shared-library ELF input files, this one yeets the globally defined functions and variables whose name matches the regular expression regex. Consequence? No change involving those functions or variables will show up in the diff report at all. Gone. Not invited. ๐ซฅ--drop-fn <regex>โ same energy, but functions only: while reading the first-shared-library and second-shared-library ELF input files, it drops the globally defined functions whose name matches the regular expression regex, so no change involving those functions gets emitted in the diff report. ๐--drop-var <regex>โ and the variable-flavoured version: while reading the first-shared-library and second-shared-library ELF input files, it drops the globally defined variables matching the regular expression regex. ๐--drop-private-typesโ this one's a team player, it's meant to be used together with the--headers-dir1,--header-file1,--header-file2and--headers-dir2options. With it, types that are NOT defined in the headers are dropped entirely from the internal representation that Libabigail builds to represent the ABI. Which means they never even have to be filtered out of the final ABI change report โ they're literally not present in Libabigail's representation in the first place. โจ
Without this option, those private types stay in the internal representation and get filtered out of the report later, the old-fashioned way.
So basically this option can make Libabigail eat less memory. It's meant to be mainly used to optimize the memory consumption of the tool on binaries with a lot of publicly defined and exported types. Iconic resource-management behaviour, lowkey. ๐ง
--dump-diff-treeโ after the diff report, emits a textual representation of the diff nodes tree that the comparison engine uses to represent the changed functions and variables. That representation goes out on the error output, for debugging purposes. Heads up though: this diff tree is only relevant to functions and variables that have some sub-type changes โ added or removed functions and variables do not have any diff nodes tree associated to them. ๐--exported-interfaces-onlyโ okay bestie, sit down for this one, it's long but it's important.
By default, when it's looking at the debug information that comes with a binary, this tool analyzes the descriptions of the types reachable by the interfaces (functions and variables) that are visible outside of their translation unit. Once that analysis is done, an ABI corpus is constructed by only considering the subset of types reachable from interfaces associated to ELF symbols that are defined and exported by the binary. And it's those final ABI Corpora that this tool compares. ๐
The problem with that approach? Analyzing all the interfaces visible from outside their translation unit can amount to a lot of data, especially when those binaries are applications instead of shared libraries. One example of such an application is the Linux Kernel. Analyzing massive ABI corpora like that can be extremely slow. ๐ญ
To mitigate that performance issue, this option lets libabigail only analyze types that are reachable from interfaces associated with defined and exported ELF symbols.
Note that this option is turned on by default when analyzing the Linux Kernel. Otherwise, it's turned off by default. โญ
--fail-no-debug-infoโ if no debug info was found, this option makes the program fail. Dramatic, but valid. Without this option, the program will instead attempt to compare properties of the binaries that aren't related to debug info โ like pure ELF properties. ๐ฅ--follow-dependencies|--fdepsโ for each dependency of the first argument ofabidiff, if it's found in the directory specified by the--added-binaries-dir1option, then construct an ABI corpus out of the dependency and add it to a set of corpora (called an ABI Corpus Group) that includes the first argument ofabidiff. ๐ฆ
Similarly, for each dependency of the second argument of abidiff, if it's found in the directory specified by the --added-binaries-dir2 option, then construct an ABI corpus out of the dependency and add it to an ABI corpus group that includes the second argument of abidiff.
These two ABI corpus groups are then compared against each other.
Said otherwise: this makes abidiff compare the set of its first input plus its dependencies against the set of its second input plus its dependencies. Package deal. ๐ฆ
--harmlessโ in the diff report, display only the harmless changes. By default the harmless changes get filtered out of the diff report, which keeps the clutter to a minimum and gives you a greater chance to spot real ABI issues. So this is the "actually, show me the soft ones" button. ๐--headers-dir1|--hd1 <headers-directory-path-1>โ tells the tool where to find the public headers of the first shared library (or binary in general) that it has to consider. The tool will then filter out ABI changes on types that aren't defined in public headers. ๐ธ
Note that several public header directories can be specified for the first shared library. In that case the --headers-dir1 option should be present several times on the command line, like in the following example:
$ abidiff --headers-dir1 /some/path \
--headers-dir1 /some/other/path \
binary-version-1 binary-version-2
--headers-dir2|--hd2 <headers-directory-path-2>โ same thing but for the second shared library: it specifies where to find the public headers of the second shared library that the tool has to consider, and the tool will thus filter out ABI changes on types that aren't defined in public headers. ๐
And yeah, several public header directories can be specified for the second shared library too โ in that case the --headers-dir2 option should be present several times, like so:
$ abidiff --headers-dir2 /some/path \
--headers-dir2 /some/other/path \
binary-version-1 binary-version-2
--header-file1|--hf1 <header-file-path-1>โ points at one public header of the first shared library that the tool has to consider. The tool will thus filter out ABI changes on types that aren't defined in public headers. ๐--header-file2|--hf2 <header-file-path-2>โ and the matching one for the second shared library: points at one public header of the second shared library that the tool has to consider, so ABI changes on types not defined in public headers get filtered out. ๐--help|-hโ display a short help about the command and exit. The classic. ๐ซถ--ignore-sonameโ ignore differences in the SONAME when doing a comparison. Not today, soname drama. ๐--impacted-interfacesโ when showing leaf changes, this option instructsabidiffto show the list of impacted interfaces. So it's meant to be used in addition to the--leaf-changes-onlyoption โ otherwise, it's just ignored. ๐โจ--keep <regex>โ the opposite of--drop, bestie: while reading the first-shared-library and second-shared-library ELF input files, keep the globally defined functions and variables whose names match the regular expression regex. All the other functions and variables get dropped on the floor and will thus not appear in the resulting diff report. ๐--keep-fn <regex>โ functions only: while reading the first-shared-library and second-shared-library ELF input files, keep the globally defined functions whose name matches the regular expression regex. All other functions get dropped on the floor and won't appear in the resulting diff report. ๐--keep-var <regex>โ and for variables: while reading the first-shared-library and second-shared-library ELF input files, keep the globally defined ones whose names match the regular expression regex. All other variables get dropped on the floor and won't appear in the resulting diff report. ๐ซ--kmi-whitelist|-w <path-to-whitelist>โ when analyzing a Linux kernel binary, this option points to the white list of names of ELF symbols of functions and variables whose ABI must be considered. That white list is called a "Kernel Module Interface white list". Why? Because for the Kernel we don't talk about ABI โ we talk about the interface between the Kernel and its module. Hence the term KMI rather than ABI. iykyk. ๐ง
Any other function or variable whose ELF symbol isn't present in that white list will not be considered by this tool.
If this option isn't provided โ so if no white list is provided โ then the entire KMI is considered, that is, the set of all publicly defined and exported functions and global variables by the Linux Kernel binaries. ๐
--leaf-changes-only|-lโ only show leaf changes, so don't show the impact analysis report. This option implies--redundant. ๐ฅ
The typical output of abidiff when comparing two binaries looks like this:
$ abidiff libtest-v0.so libtest-v1.so
Functions changes summary: 0 Removed, 1 Changed, 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function void fn(C&)' at test-v1.cc:13:1 has some indirect sub-type changes:
parameter 1 of type 'C&' has sub-type changes:
in referenced type 'struct C' at test-v1.cc:7:1:
type size hasn't changed
1 data member change:
type of 'leaf* C::m0' changed:
in pointed to type 'struct leaf' at test-v1.cc:1:1:
type size changed from 32 to 64 bits
1 data member insertion:
'char leaf::m1', at offset 32 (in bits) at test-v1.cc:4:1
$
So in that example the report emits information about how the data member insertion change of "struct leaf" is reachable from function "void fn(C&)". In other words, the report doesn't just show the data member change on "struct leaf" โ it also shows the impact of that change on the function "void fn(C&)". The way it connects the dots?? Obsessed. ๐ โจ
In abidiff speak, a change on struct leaf is literally called a leaf change. So --leaf-changes-only + --impacted-interfaces gives you โ wait for it โ only the leaf change ๐ and it looks like this:
$ abidiff -l libtest-v0.so libtest-v1.so
'struct leaf' changed:
type size changed from 32 to 64 bits
1 data member insertion:
'char leaf::m1', at offset 32 (in bits) at test-v1.cc:4:1
one impacted interface:
function void fn(C&)
$
See how the report wraps up by listing all the interfaces impacted by that leaf change? Iconic behavior, honestly ๐
Now if you don't wanna see that impacted-interfaces list, just don't use the --impacted-interface option. You can learn all about it below anyway, no pressure ๐ซถ
--list-dependencies|--ldepsโ lists every dependency ofabidiff's input arguments that it finds in the directories you pointed at with the--added-binaries-dir1and--added-binaries-dir2options. Cute lil dependency reveal โจ--no-added-symsโ in the report about the differences between first-shared-library and second-shared-library, don't show added functions or variables. Also don't show added functions or variables ELF symbols. Every other kind of change still gets displayed unless some other option on the command line explicitly forbids it. So it's a very targeted "not these ones" โ--no-assume-odr-for-cplusplusโ okay so when libabigail analyzes a binary that came from C++ code and is using DWARF debug info, it assumes the One Definition Rule to speed things up. Meaning: when several types share the same name in the binary, it just assumes they're all equal. This option kills that assumption and tells libabigail to actually, actually compare the types to figure out if they really are equal. No vibes-based type checking today ๐ง--no-architectureโ don't take architecture into account when comparing ABIs.--no-change-categorization|-xโ disables the categorization of changes into harmless vs harmful. Heads up: that categorization is a pre-requisite for filtering changes, so this option disables the filtering too. The whole point is to speed things up when your graph of changes is huge and you're only interested in, like, leaf node changes and honestly could not care less about their possible impact on interfaces. In that case you'd use this alongside--leaf-changes-only๐โโ๏ธ๐จ--no-corpus-pathโ don't emit the path attribute for the ABI corpus.--no-default-suppressionโ don't load the default suppression specification files.--no-harmfulโ in the diff report, don't display the harmful changes. Lowkey wild, because by default only the harmful changes are displayed in the diff report ๐ญ--no-leverage-dwarf-factorizationโ when analyzing a binary whose DWARF debug information was processed with the DWZ tool, the type information is supposed to already be factorized. libabigail uses that context to do some speed optimizations. This option disables those optimizations. Bye-bye shortcuts ๐ฆ--no-linkage-nameโ in the resulting report, don't display the linkage names of added, removed, or changed functions or variables.--no-linux-kernel-modeโ without this option, ifabidiffdetects that the binaries it's looking at are Linux Kernel binaries (eithervmlinuxor modules) then it only considers functions and variables whose ELF symbols are listed in the__ksymtaband__ksymtab_gplsections. With this option,abidifftreats the binary as a non-special ELF binary, so it considers functions and variables that are defined and exported in the ELF sense. Two totally different modes, bestie ๐ง--no-redundantโ in the diff report, do NOT display redundant changes. A redundant change is a change that's already been displayed elsewhere in the report. This option is switched on by default. We love a default โจ--no-show-locsโ don't show information about where in the second shared library the respective type was changed.--no-show-num-filtered-data-membersโ in the change report, don't show the number of filtered data members.--no-show-relative-offset-changesโ without this option, when the offset of a data member changes, the change report mentions not only the older and newer offset, but also by how many bits the data member changed. With this option, that last bit isn't shown. Pun intended ๐--no-unreferenced-symbolsโ in the resulting report, don't display change info about function and variable symbols that aren't referenced by any debug information. Ngl, for these symbols that no debug info references, the change info displayed is either added or removed symbols.--non-reachable-types|-tโ analyze and emit change reports for all the types of the binary, including those that aren't reachable from global functions and variables. Big disclaimer though: this option might incur some serious performance degradation since the number of types analyzed can be huge ๐ณ But! If you pair it with the--headers-dir{1,2}and/orheader-file{1,2}options, the extra non-reachable types analyzed get restricted to those defined in public headers files, which hopefully makes the performance hit acceptable. Also, using this alongside suppression specifications (by also using the--suppressionsoption) might help keep the number of analyzed types โ and the potential performance degradation โ in control. And note: without this option, only types reachable from global functions and variables are analyzed, so the tool only detects and reports changes on those reachable types.--redundantโ in the diff report, do display redundant changes. A redundant change is a change that's been displayed elsewhere in the report. Yeah, that's the exact opposite of--no-redundant, and yes it goes hard ๐ฅ--show-bitsโ show sizes and offsets in bits, not bytes. This option is activated by default.--show-bytesโ show sizes and offsets in bytes, not bits. By default, sizes and offsets are shown in bits.--show-decโ show sizes and offsets in decimal base. This option is activated by default.--show-hexโ show sizes and offsets in hexadecimal base.--statโ instead of displaying the detailed ABI differences between first-shared-library and second-shared-library, just display some summary statistics about those differences. The tl;dr mode ๐--statsโ emit statistics about various internal things.--suppressions|--suppr <path-to-suppressions>โ use a suppression specification file located at path-to-suppressions. This option can appear multiple times on the command line โ in that case, all the provided suppression specification files are taken into account ๐ซถ And please note: by default, if this option isn't provided, then the default suppression specification files are loaded.--symtabsโ only display the symbol tables of first-shared-library and second-shared-library.--thread-pool-size <N|N%>|-j <N|N%>โ set the size of the thread pool libabigail uses for parallel processing. The size is either the number of threads or a percentage of the total number of available cores on the machine. By default, all the available cores of the machine are used for parallel processing ๐งต Also note: the environment variableABIGAIL_THREAD_POOL_SIZEcan be set to similar values. This command line option, if present, supersedes the value of the environment variable. (Command line stays the main character ๐)--verboseโ emit verbose logs about the progress of miscellaneous internal things. For the nosy girlies ๐ต๏ธโโ๏ธ--version|-vโ display the version of the program and exit.
๐ Return values ๐
The exit code of the abidiff command is either 0 if the ABI of the binaries being compared are equal, or non-zero if they differ, or if the tool hit an error.
In that later case, the exit code is a 8-bits-wide bit field where each bit has a specific meaning ๐
The first bit, of value 1, named ABIDIFF_ERROR means there was an error.
The second bit, of value 2, named ABIDIFF_USAGE_ERROR means there was an error in the way the user invoked the tool. It might be set, for instance, if you invoked the tool with an unknown command line switch, with a wrong number or argument, etc. And if this bit is set, then the ABIDIFF_ERROR bit must be set as well. Package deal ๐ค
The third bit, of value 4, named ABIDIFF_ABI_CHANGE means the ABI of the binaries being compared are different.
The fourth bit, of value 8, named ABIDIFF_ABI_INCOMPATIBLE_CHANGE means the ABI of the binaries compared are different in an incompatible way ๐ฑ If this bit is set, then the ABIDIFF_ABI_CHANGE bit must be set as well. And if ABIDIFF_ABI_CHANGE is set while ABIDIFF_INCOMPATIBLE_CHANGE is NOT set, then that means the ABIs being compared might or might not be compatible โ so a human being needs to review the ABI changes and decide if they're compatible or not. Yeah, you're the intern now ๐
Note that, at the moment, there are only a few kinds of ABI changes that would result in setting the ABIDIFF_ABI_INCOMPATIBLE_CHANGE flag. Those ABI changes are either:
- the removal of the symbol of a function or variable that has been defined and exported.
- the modification of the index of a member of a virtual function table (for C++ programs and libraries).
With time, when more ABI change patterns are found to always constitute incompatible ABI changes, the code will adapt to recognize those cases and set ABIDIFF_ABI_INCOMPATIBLE_CHANGE accordingly. So, if you find such patterns, please let us know! ๐ซถ
The remaining bits are not used for the moment.
๐ง Usage examples ๐ง
- Detecting an ABI change in a sub-type of a function:
$ cat -n test-v0.cc
1 // Compile this with:
2 // g++ -g -Wall -shared -o libtest-v0.so test-v0.cc
3
4 struct S0
5 {
6 int m0;
7 };
8
9 void
10 foo(S0* /*parameter_name*/)
11 {
12 // do something with parameter_name.
13 }
$
$ cat -n test-v1.cc
1 // Compile this with:
2 // g++ -g -Wall -shared -o libtest-v1.so test-v1.cc
3
4 struct type_base
5 {
6 int inserted;
7 };
8
9 struct S0 : public type_base
10 {
11 int m0;
12 };
13
14 void
15 foo(S0* /*parameter_name*/)
16 {
17 // do something with parameter_name.
18 }
$
$ g++ -g -Wall -shared -o libtest-v0.so test-v0.cc
$ g++ -g -Wall -shared -o libtest-v1.so test-v1.cc
$
$ abidiff libtest-v0.so libtest-v1.so; echo "exit code: $?"
Functions changes summary: 0 Removed, 1 Changed, 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function void foo(S0*)' has some indirect sub-type changes:
parameter 0 of type 'S0*' has sub-type changes:
in pointed to type 'struct S0':
size changed from 32 to 64 bits
1 base class insertion:
struct type_base
1 data member change:
'int S0::m0' offset changed from 0 to 32
exit code: 4
$
okay so look at that exit code โ it's 4, and that's not a random little number bestie. It means the third bit ABIDIFF_ABI_CHANGE (value 4) is set to 1. Translation? The tool is clocking this ABI change as harmful, so a human absolutely has to review it. no cap. ๐
๐ 2. Detecting an incompatible ABI change in the type of a function:
$ cat -n test-v0.cc
1 // Compile this with:
2 // g++ -g -Wall -shared -o libtest-v0.so test-v0.cc
3
4 struct S0
5 {
6 int m0;
7 };
8
9 S0
10 foo()
11 {
12 S0 s = {};
13 return s;
14 }
$
$ cat -n test-v1.cc
1 // Compile this with:
2 // g++ -g -Wall -shared -o libtest-v1.so test-v1.cc
3
4 struct type_base
5 {
6 int inserted;
7 };
8
9 struct S0 : public type_base
10 {
11 int m0;
12 };
13
14 S0
15 foo()
16 {
17 S0 s = {};
18 return s;
19 }
$
$ g++ -g -Wall -shared -o libtest-v0.so test-v0.cc
$ g++ -g -Wall -shared -o libtest-v1.so test-v1.cc
$
$ abidiff libtest-v0.so libtest-v1.so; echo "exit code: $?"
Functions changes summary: 0 Removed, 1 Changed, 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with incompatible sub-type changes:
[C] 'function S0 foo(void)' at test-v0.cc:10:1 has some sub-type changes:
return type changed:
type size changed from 32 to 64 (in bits)
1 base class insertion:
struct type_base at test-v1.cc:4:1
1 data member change:
'int m0' offset changed from 0 to 32 (in bits) (by +32 bits)
exit code: 12
$
okay this one is sending me ๐ญ the exit code is 12. That means both the third bit ABIDIFF_ABI_CHANGE (value 4) and the fourth bit ABIDIFF_ABI_INCOMPATIBLE_CHANGE (value 8) are set to 1. So the tool is saying this ABI change is incompatible. It's an ABI break, fr fr. ๐
๐ฆ 3. Detecting another change in a sub-type of a function:
$ cat -n test-v0.cc
1 // Compile this with:
2 // g++ -g -Wall -shared -o libtest-v0.so test-v0.cc
3
4 struct S0
5 {
6 int m0;
7 };
8
9 void
10 foo(S0& /*parameter_name*/)
11 {
12 // do something with parameter_name.
13 }
$
$ cat -n test-v1.cc
1 // Compile this with:
2 // g++ -g -Wall -shared -o libtest-v1.so test-v1.cc
3
4 struct S0
5 {
6 char inserted_member;
7 int m0;
8 };
9
10 void
11 foo(S0& /*parameter_name*/)
12 {
13 // do something with parameter_name.
14 }
$
$ g++ -g -Wall -shared -o libtest-v0.so test-v0.cc
$ g++ -g -Wall -shared -o libtest-v1.so test-v1.cc
$
$ abidiff libtest-v0.so libtest-v1.so; echo "exit code: $?"
Functions changes summary: 0 Removed, 1 Changed, 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function void foo(S0&)' has some indirect sub-type changes:
parameter 0 of type 'S0&' has sub-type changes:
in referenced type 'struct S0':
size changed from 32 to 64 bits
1 data member insertion:
'char S0::inserted_member', at offset 0 (in bits)
1 data member change:
'int S0::m0' offset changed from 0 to 32
exit code: 4
$
the way that a tiny char inserted_member just slides in and changes everything ๐ exit code 4 again, so it's the ABIDIFF_ABI_CHANGE bit doing its thing โ harmful, needs review.
๐ 4. Detecting that functions got removed or added to a library:
$ cat -n test-v0.cc
1 // Compile this with:
2 // g++ -g -Wall -shared -o libtest-v0.so test-v0.cc
3
4 struct S0
5 {
6 int m0;
7 };
8
9 void
10 foo(S0& /*parameter_name*/)
11 {
12 // do something with parameter_name.
13 }
$
$ cat -n test-v1.cc
1 // Compile this with:
2 // g++ -g -Wall -shared -o libtest-v1.so test-v1.cc
3
4 struct S0
5 {
6 char inserted_member;
7 int m0;
8 };
9
10 void
11 bar(S0& /*parameter_name*/)
12 {
13 // do something with parameter_name.
14 }
$
$ g++ -g -Wall -shared -o libtest-v0.so test-v0.cc
$ g++ -g -Wall -shared -o libtest-v1.so test-v1.cc
$
$ abidiff libtest-v0.so libtest-v1.so; echo "exit code: $?"
Functions changes summary: 1 Removed, 0 Changed, 1 Added functions
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 Removed function:
'function void foo(S0&)' {_Z3fooR2S0}
1 Added function:
'function void bar(S0&)' {_Z3barR2S0}
exit code: 12
$
not the function foo getting yeeted and bar showing up like nothing happened ๐ exit code 12 = ABIDIFF_ABI_CHANGE + ABIDIFF_ABI_INCOMPATIBLE_CHANGE. iconic but rude.
๐ 5. Comparing two sets of binaries that are passed on the command line:
$ abidiff --add-binaries1=file2-v1 \
--add-binaries2=file2-v2,file2-v1 \
--added-binaries-dir1 dir1 \
--added-binaries-dir2 dir2 \
file1-v1 file1-v2
quick lil heads up: the files file2-v1 and file2-v2 are to be found in dir1 and dir2 or in the current directory. no cap. ๐ซถ
โญ 6. Compare two libraries and their dependencies:
$ abidiff --follow-dependencies \
--added-binaries-dir1 /some/where \
--added-binaries-dir2 /some/where/else \
foo bar
this one compares the set of binaries comprised by foo and its dependencies against the set of binaries comprised by bar and its dependencies. like a whole friend group vs another whole friend group. ๐
๐ง abipkgdiff
okay so abipkgdiff is the package-level bestie. it compares the Application Binary Interfaces (ABI) of the ELF binaries contained in two sets of software packages. the package formats it currently supports are Deb, RPM, tar archives (either compressed or not), and plain directories that contain binaries. periodt. โจ
the ABI of the binaries contained in the second set of packages is compared against the ABI of the binaries contained in the first set of packages. that's the whole vibe-check direction, don't flip it. ๐
and listen โ for a comprehensive ABI change report that includes changes about function and variable sub-types, the two input package sets must be accompanied with their debug information packages that contain debug information either in DWARF, CTF, or in BTF formats. the more you know ๐ธ
please note however that some packages contain binaries that embed the debug information directly in a section of said binaries. in those cases, obviously, no separate debug information package is needed as the tool will find the debug information inside the binaries. the tool is not lost, it's just built different. ๐
Okay so real quick: by default abipkgdiff is gonna reach for debug info in DWARF format if it's there, and if not it peeks at CTF or BTF formats (whichever is present). And if there's like no debug info in any of those formats? It just vibes with ELF symbols and reports whether they got added or removed. That's it, that's the fallback. ๐
This tool literally uses the libabigail library to analyze the binary and its associated debug info. Here's the general mode of operation, bestie:
When it's told to, it reads a binary plus its debug info and analyzes it. Default behavior: libabigail analyzes the descriptions of the types reachable by the interfaces (functions and variables) that are visible outside of their translation unit. Once that analysis is done, an Application Binary Interface Corpus gets constructed by only considering the subset of types reachable from interfaces associated to ELF symbols that are defined and exported by the binary. And that final ABI corpus? That's the thing libabigail considers as representing the ABI of the analyzed binary. ๐
Then libabigail has the range to generate textual representations of ABI Corpora, compare them, analyze their changes, and report about them. Iconic behavior, honestly.
โจ Invocation โจ
abipkgdiff [options] <package1> <package2>
package1 and package2 are the packages that contain the binaries to be compared. Simple as that.
An alternate invocation style would be:
abipkgdiff [options] --set1 <pkg1-v1> <pkg2-v1> <pkg3-v1> \
--set2 <pkg1-v2> <pkg2-v2> <pkg3-v2>
or
abipkgdiff [options] โset1 <pkg1-v1>,<pkg2-v1>,<pkg3-v1>
โset2 <pkg1-v2>,<pkg2-v2>,<pkg3-v2>
where the ABI of binaries contained in the second set of packages are compared against binaries contained in the first set of packages. (Yes, that's the direction. Don't get it twisted. ๐)
Packages in a set are delimited either by a white spaces or by a comma. Both work, pick your fighter.
๐ฆ Environment ๐ฆ
abipkgdiff loads two default suppression specifications files, merges their content, and uses it to filter out ABI change reports that might be considered as false positives to users. Very considerate of it, ngl.
- Default system-wide suppression specification file
It's located by the optional environment variable LIBABIGAIL_DEFAULT_SYSTEM_SUPPRESSION_FILE. If that environment variable is not set, then abipkgdiff tries to load the suppression file $libdir/libabigail/libabigail-default.abignore. If that file is not present, then no default system-wide suppression specification file is loaded. No drama, we move on. ๐ซถ
- Default user suppression specification file.
It's located by the optional environment LIBABIGAIL_DEFAULT_USER_SUPPRESSION_FILE. If that environment variable is not set, then abipkgdiff tries to load the suppression file $HOME/.abignore. If that file is not present, then no default user suppression specification is loaded.
In addition to those default suppression specification files, abipkgdiff will also look inside the packages being compared, and if it sees a file that ends with the extension .abignore, then it will consider it as a suppression specification and it will combine it to the default suppression specification that might be already loaded. Sneaky little bonus suppressions, love that. ๐ธ
The user might as well use the --suppressions option (that is documented further below) to provide a suppression specification.
- Parallel processing
The environment variable ABIGAIL_THREAD_POOL_SIZE can be set to either an integer, or a percentage value, e.g, 10%. The integer value defines the number of threads used by Libabigail to perform parallel processing when applicable. The percentage defines the percentage of the total available cores to use by Libabigail to perform the parallel processing when applicable.
By default, Libabigail uses all the available cores to perform its parallel processing. All of them. Greedy but make it efficient. ๐ฅ
๐ Options ๐
--allow-non-exported-interfacesโ okay so when looking at the debug information accompanying a binary, this tool analyzes the descriptions of the types reachable by the interfaces (functions and variables) that are visible outside of their translation unit. Once that analysis is done, an ABI corpus is constructed by only considering the subset of types reachable from interfaces associated to ELF symbols that are defined and exported by the binary. It's those final ABI Corpora that are compared by this tool.
The problem with that approach however is that analyzing all the interfaces that are visible from outside their translation unit can amount to a lot of data, especially when those binaries are applications, as opposed to shared libraries. One example of such applications is the Linux Kernel. Analyzing massive ABI Corpora like these can be extremely slow. ๐ญ
In the presence of an "average sized" binary however, one can afford having libabigail analyze all interfaces that are visible outside of their translation unit, using this option.
Note that this option is turned on by default, unless we are in the presence of the Linux Kernel.
--btfโ This is used to compare packages with BTF debug information, if present.--ctfโ This is used to compare packages with CTF debug information, if present.--debug-info-pkg1|--d1<path> โ for cases where the debug information forpackage1is split out into a separate file, tellsabipkgdiffwhere to find that separate debug information package.
Note that the debug info for package1 can have been split into several different debug info packages. In that case, several instances of this options can be provided, along with those several different debug info packages. Stack 'em, bestie. ๐
--debug-info-pkg2|--d2<path> โ for cases where the debug information forpackage2is split out into a separate file, tellsabipkgdiffwhere to find that separate debug information package.
Note that the debug info for package2 can have been split into several different debug info packages. In that case, several instances of this options can be provided, along with those several different debug info packages.
--devel-pkg1|--devel1<path> โ specifies where to find the Development Package associated with the first package to be compared. That Development Package at path should at least contain header files in which public types exposed by the libraries (of the first package to be compared) are defined. When this option is provided, the tool filters out reports about ABI changes to types that are NOT defined in these header files. Bye bye, noise. โจ--devel-pkg2|--devel2<path> โ specifies where to find the Development Package associated with the second package to be compared. That Development Package at path should at least contains header files in which public types exposed by the libraries (of the second package to be compared) are defined. When this option is provided, the tool filters out reports about ABI changes to types that are NOT defined in these header files.--drop-private-typesโ this option is to be used with the--devel-pkg1and--devel-pkg2options. With this option, types that are NOT defined in the headers are entirely dropped from the internal representation build by Libabigail to represent the ABI. They thus don't have to be filtered out from the final ABI change report because they are not even present in Libabigail's representation. Gone. Deleted. Never existed. ๐
Without this option however, those private types are kept in the internal representation and later filtered out from the report.
This options thus potentially makes Libabigail consume less memory. It's meant to be mainly used to optimize the memory consumption of the tool on binaries with a lot of publicly defined and exported types. Memory queen behavior. ๐
--dso-onlyโ compare ELF files that are shared libraries, only. Do not compare executable files, for instance.--exported-interfaces-onlyโ by default, when looking at the debug information accompanying a binary, this tool analyzes the descriptions of the types reachable by the interfaces (functions and variables) that are visible outside of their translation unit. Once that analysis is done, an ABI corpus is constructed by only considering the subset of types reachable from interfaces associated to ELF symbols that are defined and exported by the binary. It's those final ABI Corpora that are compared by this tool.
The problem with that approach however is that analyzing all the interfaces that are visible from outside their translation unit can amount to a lot of data, especially when those binaries are applications, as opposed to shared libraries. One example of such applications is the Linux Kernel. Analyzing massive ABI corpora like these can be extremely slow.
To mitigate that performance issue, this option allows libabigail to only analyze types that are reachable from interfaces associated with defined and exported ELF symbols. Speed it up, periodt.
Note that this option is turned on by default when analyzing the Linux Kernel. Otherwise, it's turned off by default.
--fail-no-dbgโ make the program fail and return a non-zero exit code if couldn't read any of the debug information that comes from the debug info packages that were given on the command line. If no debug info package were provided on the command line then this option is not active. So it only starts caring once you hand it a debug info package. ๐
Note that the non-zero exit code returned by the program as a result of this option is the constant ABIDIFF_ERROR. To know the numerical value of that constant, please refer to the exit code documentation. (iykyk, go look it up.)
--full-impact|-fโ when comparing two Linux Kernel packages, this function instructsabipkgdiffto emit the so-called full impact report, which is the default report kind emitted by theabidifftool:
$ abidiff libtest-v0.so libtest-v1.so
Functions changes summary: 0 Removed, 1 Changed, 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function void fn(C&)' at test-v1.cc:13:1 has some indirect sub-type changes:
parameter 1 of type 'C&' has sub-type changes:
in referenced type 'struct C' at test-v1.cc:7:1:
type size hasn't changed
1 data member change:
type of 'leaf* C::m0' changed:
in pointed to type 'struct leaf' at test-v1.cc:1:1:
type size changed from 32 to 64 bits
1 data member insertion:
'char leaf::m1', at offset 32 (in bits) at test-v1.cc:4:1
$
โฆand that's literally just the tail end of the previous report, bestie, we're picking up mid-vibe โจ
--harmlessโ in the diff report, show only the harmless changes. By default the harmless changes get filtered out of the diff report, so the clutter stays minimal and you've got a way better chance of spotting the actual real ABI issues ๐--help,-hโ pops up a short help blurb about the command and dips. That's it, that's the whole thing ๐ซถ--ignore-sonameโ so if two binaries have different SONAMEs but the same name in the old and new packages you're comparing, this option forcesabipkgdiffto compare them anyway, regardless of that very obvious SONAME change. Why would you want that? So you can analyze the ABI changes that might have led developers to change the SONAME in the first place. Iconic detective behaviour ๐--impacted-interfacesโ when it's showing leaf changes, this tellsabipkgdiffto also show the list of impacted interfaces. So you use this one in addition to the--leaf-changes-onlyoption, or when you're comparing two Linux Kernel packages. Any other time? It's simply ignored, no cap ๐--keep-tmp-filesโ does NOT erase the temporary directory files created while the tool was running. Keep your little temp souvenirs ๐พ--leaf-changes-only,-lโ only show leaf changes, so no impact analysis report. This option implies--redundant.
The typical output of abipkgdiff and abidiff when comparing two binaries โ which we're gonna call the full impact report โ looks like this:
$ abidiff libtest-v0.so libtest-v1.so
Functions changes summary: 0 Removed, 1 Changed, 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function void fn(C&)' at test-v1.cc:13:1 has some indirect sub-type changes:
parameter 1 of type 'C&' has sub-type changes:
in referenced type 'struct C' at test-v1.cc:7:1:
type size hasn't changed
1 data member change:
type of 'leaf* C::m0' changed:
in pointed to type 'struct leaf' at test-v1.cc:1:1:
type size changed from 32 to 64 bits
1 data member insertion:
'char leaf::m1', at offset 32 (in bits) at test-v1.cc:4:1
$
So in that example the report is emitting info about how the data member insertion change of struct leaf is reachable from the function void fn(C&). In other words, the report doesn't just show the data member change on struct leaf โ it also shows the impact of that change on the function void fn(C&). It's giving full lore drop, honestly ๐ญ
In abidiff (and abipkgdiff) parlance, the change on struct leaf is called a leaf change. So the --leaf-changes-only and --impacted-interfaces options show, well, only the leaf change. And it goes like this:
$ abidiff -l libtest-v0.so libtest-v1.so
'struct leaf' changed:
type size changed from 32 to 64 bits
1 data member insertion:
'char leaf::m1', at offset 32 (in bits) at test-v1.cc:4:1
one impacted interface:
function void fn(C&)
$
Note how the report ends up showing the list of interfaces impacted by the leaf change โ that's the effect of the additional --impacted-interfaces option ๐
Now if you don't wanna see that list of impacted interfaces, then just don't use the --impacted-interface option. You can learn about that option below, in any case.
Please note that when comparing two Linux Kernel packages, it's this leaf changes report that gets emitted, by default. The normal so-called full impact report can be emitted with the --full-impact option, which is documented later below ๐
--linux-kernel-abi-whitelist,-w<path-to-whitelist>โ when comparing two Linux kernel RPM packages, this option points to the white list of names of ELF symbols of functions and variables that must be compared for ABI changes. That white list is called a "Linux kernel ABI white list".
Any other function or variable whose ELF symbol isn't present in that white list won't be considered by the ABI comparison process at all. Not invited to the party, periodt ๐
If this option isn't provided โ so if there's no white list โ then the ABI of all publicly defined and exported functions and global variables by the Linux Kernel binaries are compared.
Please note that if a white list package is given in parameter, this option handles it just fine, like if the --wp option was used โจ
--no-abignoreโ do not search the package for the presence of suppression files. We're not looking, we don't care, bye ๐--no-added-binariesโ do not show the list of binaries that got added to the second package.
Please note that the presence of such added binaries is not considered like an ABI change by this tool โ as such, it doesn't have any impact on the exit code of the tool. It only has informational value. Removed binaries, however, are considered as an ABI change. Big difference, don't get it twisted ๐
--no-added-symsโ do not show the list of functions, variables, or any symbol that was added. If it's new, we're simply not acknowledging it ๐ซฅ--no-assume-odr-for-cplusplusโ when analyzing a binary originating from C++ code using DWARF debug information, libabigail assumes the One Definition Rule to speed up the analysis. In that case, when several types have the same name in the binary, they're assumed to all be equal.
This option disables that assumption and instructs libabigail to actually compare the types to determine if they are equal. No shortcuts, we're doing the work ๐ฅ
--no-default-suppressionโ do not load the default suppression specification files. Bestie said no defaults today ๐--no-leverage-dwarf-factorizationโ when analyzing a binary whose DWARF debug information was processed with the DWZ tool, the type information is supposed to already be factorized. That context is used by libabigail to perform some speed optimizations.
This option disables those optimizations. Bye bye fast mode ๐ฆ
--no-linkage-nameโ in the resulting report, do not display the linkage names of the added, removed, or changed functions or variables.--no-parallelโ by default,abipkgdiffwill use all the processors it has available to execute concurrently. This option tells it not to extract packages or run comparisons in parallel. One lane only, no carpooling ๐--non-reachable-types,-tโ analyze and emit change reports for all the types of the binary, including those that are not reachable from global functions and variables.
Heads up: this option might incur some serious performance degradation, because the number of types analyzed can be huge ๐ญ
However, if paired with the --devel-pkg{1,2} options, the additional non-reachable types analyzed are restricted to those defined in the public header files carried by the referenced development packages, thus hopefully making the performance hit acceptable.
Also, using this option alongside suppression specifications (by also using the --suppressions option) might help keep the number of analyzed types (and the potential performance degradation) in control.
Note that without this option, only types that are reachable from global functions and variables are analyzed โ so the tool detects and reports changes on these reachable types only.
--no-show-locsโ do not show information about where in the second shared library the respective type was changed. Location reveal: cancelled ๐ซ๐--no-show-relative-offset-changesโ without this option, when the offset of a data member changes, the change report not only mentions the older and newer offset, but it also mentions by how many bits the data member changes. With this option, that last bit isn't shown. Just the before and after, no math homework ๐--no-unreferenced-symbolsโ in the resulting report, do not display change information about function and variable symbols that are not referenced by any debug information. Note that for these symbols not referenced by any debug information, the change information displayed is either added or removed symbols.--show-bitsโ show sizes and offsets in bits, not bytes. This option is activated by default โญ--show-bytesโ show sizes and offsets in bytes, not bits. By default, sizes and offsets are shown in bits. (Yes bestie, bits is the default, we already established that ๐)--show-decโ show sizes and offsets in decimal base. This option is activated by default. Base-10 girlies stay winning ๐ข--show-hexโ show sizes and offsets in hexadecimal base. For when you're feeling a little0xera ๐--show-identical-binariesโ show the names of all the binaries compared, including the binaries whose ABI compare equal. By default, when this option isn't provided, only binaries with ABI changes are mentioned in the output. Everybody gets their name in the group chat now ๐ซถ--private-dsoโ by default,abipkgdiffdoes not compare DSOs that are private to the RPM package. A private DSO is a DSO whose SONAME is NOT advertised in the "provides" property of the RPM.
This option instructs abipkgdiff to also compare DSOs that are NOT advertised in the "provides" property of the RPM.
Please note that the fact that (by default) abipkgdiff skips private DSOs is a feature that's available only for RPMs, at the moment. We would happily accept patches adding that feature for other package formats ๐ (yes, that's a real invitation, come contribute)
--redundantโ in the diff reports, do display redundant changes. A redundant change is a change that has been displayed elsewhere in a given report. Yes it's repeated, no we don't gatekeep ๐--self-check
โฆand boom, that's exactly where this part cuts off โ --self-check's description is literally mid-sentence in the source, so catch you in the next part for the rest of that vibe check ๐
โจ
--self-check time ๐ โ okay so this one is literally the tool testing its own homework. It exercises the underlying Libabigail library. When it's used, the command expects only one input package, along with its associated debug info packages. Then it compares each binary inside the package against its own ABIXML representation. If Libabigail is behaving correctly, the comparison should give you back the empty set โ nothing to report, A+ behavior, slay ๐
. If it doesn't, that means there's an issue that ought to be fixed. This option is for the people into Libabigail development doing regression testing, iykyk. Example of it in action:
$ abipkgdiff --self-check --d1 mesa-libGLU-debuginfo-9.0.1-3.fc33.x86_64.rpm mesa-libGLU-9.0.1-3.fc33.x86_64.rpm
==== SELF CHECK SUCCEEDED for 'libGLU.so.1.3.1' ====
$
--set1 <package1-path> <package2-path> <package2-path> โฆโ names the first set of packages, and their binaries get compared against the second set's. Just so you know ๐ the second set has to be given with--set2.--set2 <package1-path> <package2-path> <package2-path> โฆโ names the second set of packages, whose binaries get compared against the second one. And yeah, the first set has to be specified with--set1.--suppressions|--suppr <path-to-suppressions>โ point it at a suppression specification file living at path-to-suppressions. This option can show up multiple times on the command line, and in that case every single one of the suppression specification files gets taken into account ๐.
Heads up bestie: by default, if you don't pass this option, the default suppression specification files are loaded. No cap.
--version|-vโ shows the version of the program and bounces. Periodt.--wp <path-to-whitelist-package>โ when you're comparing two Linux kernel RPM packages, this points at an RPM package containing several white lists of names of ELF symbols of functions and variables that must be compared for ABI changes. Those white lists are called "Linux kernel ABI white lists" โจ.
From the content of that white list package, the program then picks the appropriate Linux kernel ABI white list to consider when comparing the ABI of the Linux kernel binaries contained in the Linux kernel packages you gave on the command line. That chosen Linux kernel ABI white list holds the list of names of ELF symbols of functions and variables that must be compared for ABI changes. Any other function or variable which ELF symbol isn't present in that white list just... doesn't get considered by the ABI comparison process ๐ซถ.
Note: this option can be provided twice โ not more than twice โ specifying one white list package for each Linux Kernel package provided on the command line.
If you don't provide this option โ meaning no white list at all โ then the ABI of all publicly defined and exported functions and global variables by the Linux Kernel binaries is compared. All of them. Every single one ๐.
--thread-pool-size <N|N%>|-j <N|N%>โ sets the size of the thread pool Libabigail uses for parallel processing. The size is either the number of threads, or a percentage of the total number of available cores on the machine. By default, all the available cores of the machine get used for parallel processing ๐ฅ.
Also note that the environment variable ABIGAIL_THREAD_POOL_SIZE can be set to similar values. And this command line option, if present, supersedes the value of the environment variable. Boss moves only ๐.
--verboseโ emit verbose progress messages. Very "tell me everything" of it.--verbose-diffโ emit timed verbose progress messages about the diffing process. This one implies the--verboseone.
๐ Return value ๐
The exit code of the abipkgdiff command is either 0 if the ABI of the binaries compared are equal, or non-zero if they differ โ or if the tool encountered an error. In the latter case, the value of the exit code is the same as for the abidiff tool. Simple as that, no drama ๐ธ.
๐ฆ kmidiff ๐ฆ
kmidiff compares the binary Kernel Module Interfaces of two Linux Kernel trees. So what's a binary KMI? It's the interface that the Linux Kernel exposes to its modules, duh. And the trees we're talking about here are the result of the build of the Linux Kernel source tree.
โจ General approach โจ
An example of how to build your kernel when you wanna compare it to another one with kmidiff:
git clone -b v4.5 git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux/v4.5
cd linux/v4.5
make allyesconfig all
Then install the modules into a directory โ for instance, the build/modules sub-directory of your kernel source tree:
mkdir build/modules
make modules_install INSTALL_MOD_PATH=build/modules
Then construct a list of interfaces exported by the kernel that you want to compare:
cat > kmi-whitelist << EOF
[kernel_4.5_kmi_whitelist]
init_task
schedule
dev_queue_xmit
__kmalloc
printk
EOF
Suppose you've done something similar for the v4.6 branch of the Linux kernel โ cool, now you have these two directories: linux/v4.5 and linux/v4.6. Their modules live under linux/v4.5/build/modules and linux/v4.6/build/modules ๐
.
To compare their KMI, kmidiff needs to know where to find the vmlinux binaries and their associated modules. Here's what the command line looks like:
kmidiff \
--kmi-whitelist linux/v4.6/kmi-whitelist \
--vmlinux1 linux/v4.5/vmlinux \
--vmlinux2 linux/v4.6/vmlinux \
linux/v4.5/build/modules \
linux/v4.6/build/modules
This tool uses the libabigail library to analyze the binary as well as its associated debug information. Here's its general mode of operation, bestie:
When instructed to do so, a binary and its associated debug information is read and analyzed. To that effect, libabigail analyzes by default the descriptions of the types reachable by the interfaces (functions and variables) that are visible outside of their translation unit. Once that analysis is done, an Application Binary Interface Corpus is constructed by only considering the subset of types reachable from interfaces associated to ELF symbols that are defined and exported by the binary. It's that final ABI corpus which libabigail considers as representing the ABI of the analyzed binary.
Libabigail then has capabilities to generate textual representations of ABI Corpora, compare them, analyze their changes and report about them. Iconic behavior, honestly ๐.
๐ Invocation ๐
More generally, kmidiff is invoked under the form:
kmidiff [options] <first-modules-dir> <second-modules-dir>
๐ธ Environment ๐ธ
๐ General considerations
By default, kmidiff compares all the interfaces (exported functions and variables) between the Kernel and its modules. In practice, though, some users might want to compare a subset of those interfaces. Fair.
By default, kmidiff uses debug information in the DWARF debug info format, if present โ otherwise it compares interfaces using CTF or BTF debug info formats, if present. Finally, if no debug info in these formats is found, it only considers ELF symbols and reports about their addition or removal. Adaptable queen ๐.
Users can then define a "white list" of the interfaces to compare. Such a white list is just a file in the "INI" format that looks like:
[kernel_version_x86_64_whitelist]
function1_name
function2_name
global_variable1_name
....
Note that the name of the section โ the name between the two brackets โ of that INI file just has to end with the string whitelist. So you can name it whatever you want, for instance [kernel_46_x86_64_whitelist] ๐.
Then each line of that whitelist file is the name of an exported function or variable. Only those interfaces, along with the types reachable from their signatures, are going to be compared by kmidiff recursively.
Note that by default kmidiff analyzes the types reachable from the interfaces associated with ELF symbols that are defined and exported by the Linux Kernel as being the union of the vmlinux binary and all its compiled modules. It then compares those interfaces (along with their types).
๐ง Parallel processing
The environment variable ABIGAIL_THREAD_POOL_SIZE can be set to either an integer, or a percentage value, e.g, 10%. The integer value defines the number of threads used by Libabigail to perform parallel processing when applicable. The percentage defines the percentage of the total available cores to use by Libabigail to perform the parallel processing when applicable.
By default, Libabigail uses all the available cores to perform its parallel processing. All of 'em. Go off ๐ฅ.
โญ Options โญ
--allow-non-exported-interfacesโ when you use this option, the tool analyzes the descriptions of the types reachable by the interfaces (functions and variables) that are visible outside of their translation unit. Once that analysis is done, an ABI Corpus is constructed by only considering the subset of types reachable from interfaces associated to ELF symbols that are defined and exported by the binary. It's that final ABI corpus which gets compared against another one.
The problem with that approach, though? Analyzing all the interfaces visible from outside their translation unit can amount to a lot of data, leading to very slow operations ๐ญ. So yeah.
Note that this option is turned off by default. ๐
--btfโ extract ABI information from BTF debug information, if present, in the Kernel and Modules.--ctfโ extract ABI information from CTF debug information, if present, in the Kernel and Modules.--debug-info-dir1|--d1 <di-path1>โ for the cases where the debug information for the binaries of the first Linux kernel is split out into separate files, this tellskmidiffwhere to find those separate debug information files.
Note that di-path must point to the root directory under which the debug information is arranged in a tree-like manner. Under Red Hat based systems, that directory is usually <root>/usr/lib/debug ๐.
--debug-info-dir2|--d2 <di-path2>โ like--debug-info-dir1, this one tellskmidiffwhere to find the split debug information for the binaries of the second Linux kernel.--exported-interfaces-onlyโ
So when you're running this bad boy, the tool is out here analyzing the descriptions of the types reachable by the interfaces (functions and variables) associated with ELF symbols that are defined and exported by the Linux Kernel. ๐
Otherwise (i.e. not the kernel case), the tool also has the ability to analyze the descriptions of the types reachable by the interfaces associated with ELF symbols that are visible outside their translation unit. That second possibility is however much more resource intensive and results in much slower operations, bestie. ๐ญ
That is literally why this option is enabled by default. no cap.
--full-impact|-fโ Emit a change report that shows the full impact of each change on exported interfaces. This is the default kind of report emitted by tools likeabidifforabipkgdiff. ๐ซถ--help|-hโ Display a short help about the command and exit. iconic behavior, honestly.--impacted-interfaces|-iโ Tell what interfaces got impacted by each individual ABI change. like, which of your faves got hit. ๐--kmi-whitelist|-w <path-to-interface-whitelist>โ Set the path to the white list of interfaces to compare while comparing the Kernel Module Interface of the first kernel against the one of the second kernel.
If you don't pass this option, all the exported interfaces of the two kernels are compared. That takes a lot of time and is not necessarily meaningful, because many interfaces are probably meant to see their reachable types change.
So please, make sure you always use this option unless you really know what you are doing. ๐ (this is the group-chat equivalent of "girl don't").
--no-change-categorization|-xโ This option disables the categorization of changes into harmless and harmful changes. Note that this categorization is a pre-requisite for the filtering of changes, so this option disables that filtering too. The goal here is to speed up the execution of the program for cases where the graph of changes is huge and the user is just interested in looking at, for instance, leaf node changes without caring about their possible impact on interfaces. โก--show-bitsโ Show sizes and offsets in bits, not bytes. By default, sizes and offsets are shown in bytes.--show-bytesโ Show sizes and offsets in bytes, not bits. This option is activated by default. โจ--show-decโ Show sizes and offsets in decimal base.--show-hexโ Show sizes and offsets in hexadecimal base. This option is activated by default.--suppressions|--suppr <path-to-suppressions>โ Use a suppression specification file located at path-to-suppressions. Note that this option can appear multiple times on the command line โ in that case, all of the provided suppression specification files are taken into account.
Please note that, by default, if this option is not provided, then the default suppression specification files are loaded. ๐
--thread-pool-size <N|N%>|-j <N|N%>โ Set the size of the thread pool used by Libabigail for parallel processing. The size is either the number of threads or a percentage of the total number of available cores on the machine. By default, all the available cores of the machine are used for parallel processing. ๐ฅ
Note that the environment variable ABIGAIL_THREAD_POOL_SIZE can also be set to similar values. This command line option, if present, supersedes the value of the environment variable.
--verboseโ Display some verbose messages while executing.--version|-vโ Display the version of the program and exit.--vmlinux1|--l1 <path-to-first-vmlinux>โ Sets the path to the first vmlinux binary to consider. This has to be the uncompressed vmlinux binary compiled with debug info. ๐ง--vmlinux2|--l2 <path-to-first-vmlinux>โ Sets the path to the second vmlinux binary to consider. This has to be the uncompressed vmlinux binary compiled with debug info.
๐ฆ abidw ๐ฆ
abidw reads a shared library in ELF format and emits an XML representation of its ABI to standard output. The emitted representation format, named ABIXML, includes all the globally defined functions and variables, along with a complete representation of their types. It also includes a representation of the globally defined ELF symbols of the file. ๐
When you give it the --linux-tree option, this program can also handle a Linux kernel tree. That is, a directory tree that contains both the vmlinux binary and Linux Kernel modules. It analyses those Linux Kernel binaries and emits an XML representation of the interface between the kernel and its module, to standard output. In this case, we don't call it an ABI, we call it a KMI (Kernel Module Interface). The emitted KMI includes all the globally defined functions and variables, along with a complete representation of their types. โจ
To generate either ABI or KMI representation, by default abidw uses debug information in the DWARF format, if present, otherwise it looks for debug information in CTF or BTF formats, if present. Finally, if no debug info in these formats is found, it only considers ELF symbols and reports about their addition or removal. it's giving fallback queen. ๐
This tool uses the libabigail library to analyze the binary as well as its associated debug information. Here is its general mode of operation.
When instructed to do so, a binary and its associated debug information is read and analyzed. To that effect, libabigail analyzes by default the descriptions of the types reachable by the interfaces (functions and variables) that are visible outside of their translation unit. Once that analysis is done, an Application Binary Interface Corpus is constructed by only considering the subset of types reachable from interfaces associated to ELF symbols that are defined and exported by the binary. It's that final ABI corpus which libabigail considers as representing the ABI of the analyzed binary. ๐ซถ
Libabigail then has capabilities to generate textual representations of ABI Corpora, compare them, analyze their changes and report about them. main character energy fr fr.
๐ Environment ๐
- Parallel processing
The environment variable ABIGAIL_THREAD_POOL_SIZE can be set to either an integer, or a percentage value, e.g, 10%. The integer value defines the number of threads used by Libabigail to perform parallel processing when applicable. The percentage defines the percentage of the total available cores to use by Libabigail to perform the parallel processing when applicable.
By default, Libabigail uses all the available cores to perform its parallel processing. โก
๐ Invocation ๐
abidw [options] [<path-to-elf-file>]
๐ Options ๐
--abidiffโ Load the ABI of the ELF binary given in argument, save it in libabigail's XML format in a temporary file; read the ABI from the temporary XML file and compare the ABI that has been read back against the ABI of the ELF binary given in argument. The ABIs should compare equal. If they don't, the program emits a diagnostic and exits with a non-zero code. ๐ญ
This is a debugging and sanity check option. (vibe check for your ABI, basically.)
--abixml-versionโ Display the version of the ABIXML format emitted by this program and exit.--add-binaries <bin1,bin2,โฆ>โ For each of the comma-separated binaries given in argument to this option, if the binary is found in the directory specified by theโadded-binaries-diroption, then load the ABI corpus of the binary and add it to a set of ABI corpora (called a ABI Corpus Group) made of the binary denoted by the Argument ofabidw. That corpus group is then serialized out. ๐ธ--added-binaries-dir|--abd <dir-path>โ This option is to be used in conjunction with the--add-binaries, the--follow-dependenciesor the--list-dependenciesoption. Binaries listed as arguments of the--add-binariesoption, or being dependencies of the input binary in the case of the--follow-dependenciesoption, and found in the directory<dir-path>are going to be loaded as ABI corpus and added to the set of ABI corpora (called an ABI corpus group) built and serialized. ๐--allow-non-exported-interfacesโ When looking at the debug information accompanying a binary, this tool analyzes the descriptions of the types reachable by the interfaces (functions and variables) that are visible outside of their translation unit. Once that analysis is done, an ABI corpus is constructed by only considering the subset of types reachable from interfaces associated to ELF symbols that are defined and exported by the binary. It's that final ABI corpus which textual representation is saved as ABIXML. ๐
The problem with that approach however is that analyzing all the interfaces that are visible from outside their translation unit can amount to a lot of data, especially when those binaries are applications, as opposed to shared libraries. One example of such applications is the Linux Kernel. Analyzing massive ABI corpora like these can be extremely slow. ๐
In the presence of an "average sized" binary however one can afford having libabigail analyze all interfaces that are visible outside of their translation unit, using this option.
Note that this option is turned on by default, unless we are in the presence of the Linux Kernel. โจ
--annotateโ Annotate the ABIXML output with comments above most elements. The comments are made of the pretty-printed form types, declaration or even ELF symbols. The purpose is to make the ABIXML output more human-readable for debugging or documenting purposes. ๐ฆ--btfโ Extract ABI information from BTF debug information, if present in the given object.--check-alternate-debug-info <elf-path>โ If the debug info for the file elf-path contains a reference to an alternate debug info file,abidwchecks that it can find that alternate debug info file. In that case, it emits a meaningful success message mentioning the full path to the alternate debug info file found. Otherwise, it emits an error code. ๐ค--check-alternate-debug-info-base-name <elf-path>โ Like--check-alternate-debug-info, but in the success message, only mention the base name of the debug info file; not its full path. ๐ซถ--debug-abidiffโ literally the same as--abidiffbut in debug mode ๐. In this mode you get error messages emitted for types that fail type canonicalization.
This one is an optional debugging and sanity check option. Heads up though: to even enable it, the libabigail package needs to be configured with the โenable-debug-self-comparison option. No configure flag, no debug vibes, iykyk โจ
--ctfโ pull ABI info out of CTF debug information, if it's actually present in the given object. If it's not there, well, nothing to extract bestie ๐--debug-info-dir|-d<dir-path>โ okay so when the debug info for *path-to-elf-file lives in a separate file that's chilling in a non-standard place, this tellsabidwwhere to go looking for that debug info file ๐
Important bit: dir-path has to point to the root directory under which the debug information is arranged in a tree-like manner. On Red Hat based systems, that directory is usually <root>/usr/lib/debug.
You can pass this option several times with different root directories โ in that case abidw will potentially look into all those root directories to find the split debug info for the elf file ๐ฆ
Also: this option is not mandatory for split debug information installed by your system's package manager, because then abidw already knows where to find it. Main character energy, it just knows ๐
--debug-type-canonicalization|--debug-tcโ debug the type canonicalization process ๐. How? By using structural and canonical equality when canonicalizing every single type. Structural and canonical equality should yield the same result. If they don't yield the same result for a given type, then the canonicalization of that type went wrong โ in that case an error message is emitted and the execution of the program is aborted ๐ญ๐
This option is available only if the package was configured with the โenable-debug-type-canonicalization option.
--drop-private-typesโ this option is implicitly set by theโheaders-dirandโheader-fileoptions.
With this option, types that are NOT defined in the headers are entirely dropped from the internal representation built by Libabigail to represent the ABI, and will not end up in the abi XML file ๐ซถ
Fr fr: this option is provided for compatibility reasons only and should not be explicitly used anymore. It's giving deprecated, bestie ๐
--drop-undefined-symsโ with this option, functions or variables whose (exported) ELF symbol is undefined get dropped from the internal representation built by Libabigail to represent the ABI โ so they won't end up in the abi XML file. Clean sweep โจ--emit-member-hashesโ emit hashes for data members and virtual member functions in the ABIXML file ๐ฅ--emit-native-offsetsโ emit the offsets of the debug information entries which ABI artifacts originate from. This is useful for debug purposes ๐--exported-interfaces-onlyโ big one, buckle up ๐ข
By default, when looking at the debug information accompanying a binary, this tool analyzes the descriptions of the types reachable by the interfaces (functions and variables) that are visible outside of their translation unit. Once that analysis is done, an ABI corpus is constructed by only considering the subset of types reachable from interfaces associated to ELF symbols that are defined and exported by the binary. It's that final ABI corpus whose textual representation is saved as ABIXML.
The problem with that approach though: analyzing all the interfaces visible from outside their translation unit can amount to a lot of data โ especially when those binaries are applications, as opposed to shared libraries. One example of such an application is the Linux Kernel. Analyzing massive ABI corpora like these can be extremely slow ๐ญ
To mitigate that performance issue, this option lets libabigail only analyze types that are reachable from interfaces associated with defined and exported ELF symbols ๐
Note that this option is turned on by default when analyzing the Linux Kernel. Otherwise, it's turned off by default. Sneaky little default, ik ๐
--fail-no-debug-infoโ if the program couldn't find the debug info for the input binary, then it exits with a non-zero error code ๐. Without this option, the program just emits the ABI information about ELF symbols in the absence of the debug info โ and in that case, type information is omitted.--follow-dependenciesโ for each dependency of the input binary ofabidw, if it's found in the directory specified by the--added-binaries-diroption, then construct an ABI corpus out of that dependency and add it to a set of ABI corpora (called an ABI Corpus Group) along with the ABI corpus of the input binary of the program ๐ซถ. Then the ABI Corpus Group gets serialized out. Squad goals fr ๐--force-early-suppressionโ this option must be used alongside theโsuppressionoption โ ๏ธ
It forces suppression specifications to be applied in early suppression mode. Artifacts of the internal representation that are matched by suppression specifications are thus suppressed from memory ๐ง
If this option isn't used, then only suppression specifications with the drop property set to 'yes' are effective โ all the other suppression specification directives will appear to be ignored ๐
--headers-dir|--hd*<headers-directory-path-1>โ tells the tool where to find the public headers of the binary it has to consider. The tool will thus filter out types that are NOT defined in public headers. This option implicitly sets theโdrop-private-typesoption ๐
Note that several public header directories can be specified for the binary to consider. In that case the --header-dir option should be present several times on the command line, like in the following example:
$ abidw --header-dir /some/path \
--header-dir /some/other/path \
binary > binary.abi
--header-file|--hf*<header-file-path>โ tells the tool where to find one of the public headers of the abi file that it has to consider. The tool will thus filter out types that are not defined in public headers ๐
This option implicitly sets the โdrop-private-types option too.
--help|-hโ show a short help about the command and dip โ๏ธ--kmi-whitelist|--kmi-stablelist|-w*<path-to-stablelist>โ when analyzing a Linux Kernel binary, this option points to the list of names of ELF symbols of functions and variables whose ABI must be written out ๐. Any function or variable with a name that's not included in that list gets ignored (the source literally says "will not ignored", but likeโฆ we all know what they meant bestie ๐). That list is called a "Kernel Module Interface stable list". Why? Because for the Kernel we don't talk about the ABI โ we talk about the interface between the Kernel and its module. Hence the term KMI rather than KABI. Cute terminology moment โจ
Any other function or variable whose ELF symbol isn't present in that stable list will not be considered by the KMI writing process.
If this option isn't provided โ thus if no stable list is provided โ then the entire KMI gets emitted, that is, all publicly defined and exported functions and global variables by the Linux Kernel binaries ๐
--list-dependenciesโ for each dependency of the input binary ofabidw, if it's found in the directory specified by the--added-binaries-diroption, then the name of that dependency gets printed out ๐จ๏ธโจ--load-all-typesโ by default, libabigail (and thusabidw) only loads types that are reachable from function and variable declarations that are publicly defined and exported by the binary. So only those types show up in the output ofabidw. This option, however, makesabidwload all the types defined in the binaries โ even the ones that aren't reachable from public declarations. Gotta catch 'em all ๐ฆ--linux-tree|--ltโ makesabidwconsider the input path as a path to a directory containing thevmlinuxbinary plus several kernel module binaries ๐ง. In that case, this program emits the representation of the Kernel Module Interface (KMI) on standard output.
Below is an example of usage of abidw on a Linux Kernel tree.
First, checkout a Linux Kernel source tree and build it. Then install the kernel modules in a directory somewhere. Copy the vmlinux binary into that directory too. And then serialize the KMI of that kernel to disk, using abidw:
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
$ cd linux && git checkout v4.5
$ make allyesconfig all
$ mkdir build-output
$ make INSTALL_MOD_PATH=./build-output modules_install
$ cp vmlinux build-output/modules/4.5.0
$ abidw --linux-tree build-output/modules/4.5.0 > build-output/linux-4.5.0.kmi
--no-assume-odr-for-cplusplusโ when analyzing a binary originating from C++ code using DWARF debug information, libabigail assumes the One Definition Rule to speed up the analysis โก. In that case, when several types have the same name in the binary, they're assumed to all be equal.
This option disables that assumption and instructs libabigail to actually actually compare the types to determine if they're equal ๐ (yes, "actually actually" โ the source is that serious about it ๐ )
--no-corpus-pathโ don't emit the path attribute for the ABI corpus ๐ โโ๏ธ--no-elf-neededโ don't include the list ofDT_NEEDEDdependency names in the corpus.--no-leverage-dwarf-factorizationโ when analyzing a binary whose DWARF debug information was processed with the DWZ tool, the type information is supposed to already be factorized. Libabigail uses that context to perform some speed optimizations ๐๏ธ
This option disables those optimizations. Bye speed, hello thoroughness ๐ข
--no-load-undefined-interfacesโ by default, libabigail (and thusabidw) loads information about undefined function and variable symbols, as well as functions and variables that are associated with those undefined symbols. Those are called undefined interfaces. This option, however, makesabidwavoid loading information about undefined interfaces. The resulting XML file thus doesn't contain information about those undefined interfaces ๐ซถ--no-linux-kernel-modeโ- the option from the last part, continued โ okay so without this option, if
abipkgdiffclocks that the binaries it's looking at are Linux Kernel binaries (eithervmlinuxor modules) then it only considers functions and variables whose ELF symbols are listed in the__ksymtaband__ksymtab_gplsections. With this option though,abipkgdifftreats the binary as a non-special ELF binary, so it considers functions and variables that are defined and exported in the ELF sense. two totally different vibes, bestie ๐ --nooutโ tellsabidwto not emit the XML representation of the ABI at all. It just reads the ELF and debug info, builds the internal representation of the ABI, and dips ๐ lowkey this one is usually for debugging purposes, ngl.--no-parameter-namesโ in the emitted ABI representation, don't show the names of function parameters, just the types. names are cancelled ๐ซ--no-show-locsโ in the emitted ABI representation, don't show the file, line or column where ABI artifacts are defined. location info? never heard of her ๐--no-write-default-sizesโ in the XML ABI representation, don't write thesize-in-bitsfor pointer type definitions, reference type definitions, function declarations and function types when they're equal to the default address size of the translation unit. โจ important footnote โจ note that libabigail before1.8will not set the default size and will interpret types without asize-in-bitsattribute as zero sized. so like, be aware of the timeline bestie ๐ญ--nulloutโ emit the ABIXML output to/dev/null. bye ๐จ into the void it goes.--out-file|-ofile-path โ this one tellsabidwto emit the XML representation of path-to-elf-file into the file file-path, instead of blasting it to standard output. very demure, very file-based ๐--parameter-namesโ emit function parameter names in the ABIXML output. yes queen, give us the names ๐--show-locsโ show location information of ABI artifacts in the ABIXML output. where everything lives, exposed ๐--statsโ emit statistics about various internal things. numbers!! we love numbers ๐--suppressions|supprpath-to-suppression-specifications-file โ use a suppression specification file located at path-to-suppression-specifications-file. Note that this option can appear multiple times on the command line, and in that case, all the provided suppression specification files are taken into account ๐ซถ ABI artifacts matched by the suppression specifications are suppressed from the output of this tool, periodt.
Only suppression specifications that have the drop property set to yes are going to be effective. All other suppression specification directives will appear as being ignored โ unless the command line option --force-early-suppression is provided. so rude of them to be ignored honestly ๐
--type-id-style<sequence|hash>โ this option controls how types are identified in the generated XML files. The defaultsequencestyle just numbers (withtype-id-as prefix) the types in the order they're encountered. Thehashstyle uses a (stable, portable) hash of libabigail's internal type names, and it's intended to make the XML files easier to diff. diff girlies rise ๐ธ--thread-pool-size<N|N%>|-j<N|N%>โ set the size of the thread pool that Libabigail uses for parallel processing. The size is either the number of threads or a percentage of the total number of available cores on the machine. By default, all the available cores of the machine are used for parallel processing ๐ฅ go off, entire CPU.
Note that the environment variable ABIGAIL_THREAD_POOL_SIZE can also be set to similar values. This command line option, if present, supersedes the value of the environment variable. command line stays winning ๐
--verboseโ emit verbose logs about the progress of miscellaneous internal things. yap mode: engaged ๐ฃ๏ธ--version|-vโ display the version of the program and exit. hi, this is me, and then I'm leaving โ๏ธ
โจ Usage examples โจ
- Emitting an ABIXML representation of a binary:
$ abidw binary > binary.abi
- Emitting an ABIXML representation of a set of binaries specified on the command line:
$ abidw --added-binaries=bin1,bin2,bin3 \
--added-binaries-dir /some/where \
binary > binaries.abi
Note that the binaries bin1, bin2 and bin3 are to be found in the directory /some/where. A representation of the ABI of the set of binaries binary, bin1, bin2 and bin3 โ called an ABI corpus group โ is serialized in the file binaries.abi. teamwork makes the dream work ๐ง
- Emitting an ABIXML representation of a binary and its dependencies:
$ abidw --follow-dependencies \
--added-binaries-dir /some/where \
binary > binary.abi
Note that only the dependencies found in the directory /some/where are analysed. Their ABIs, along with the ABI of the binary named binary, are represented as an ABI corpus group and serialized in the file binary.abi, in the ABIXML format. bringing your friends to the function, iconic ๐ฆ
๐ Notes ๐
Alternate debug info files
As of version 4 of the DWARF specification, Alternate debug information is a GNU extension to the DWARF specification. It has however been proposed for inclusion into the upcoming version 5 of the DWARF standard. You can read more about the GNU extensions to the DWARF standard โ the page points you at the reading for that one, so go click it, iykyk ๐โจ
๐ abidb ๐
abidb manages a git repository of abixml files describing shared libraries, and checks binaries against them. elfutils and libabigail programs are used to query and process the binaries. And here's the best part: abidb works well with debuginfod to fetch needed DWARF content automatically ๐ค literally the friend that handles the group project logistics.
Invocation
abidb [OPTIONS] [--submit PATH1 PATH2 ...] [--check PATH1 PATH2 ...]
Common Options
--abicompat PATHโ specify the path to theabicompatprogram to use. By default, in the absence of this option, theabicompatprogram found in directories listed in the$PATHenvironment is used. #default behaviour ๐ซ--abidw PATHโ specify the path to theabidwprogram to use. By default, in the absence of this option, theabidwprogram found in directories listed in the$PATHenvironment is used. same energy, different binary ๐ซถ--distrobranch BRANCHโ specify the git branch for theabixmlfiles in the git repo. The default is a string likeDISTRO/VERSION/ARCHITECTURE, computed from the running environment. it just knows where it is ๐ฎ--git REPOโ specify the preexisting git working tree forabidbto submit to or check against. The default is the current working directory. โ ๏ธ heads up: it may be used concurrently by multiple "check" operations, but only one "submit" operation. one submitter at a time bestie, no fighting ๐--help|-hโ display a short help about the command and exit. we love a quick lil orientation ๐--loglevel LOGLEVELโ specify the diagnostic level for messages tostderr. One ofdebug,info,warning,error, orcritical; case-insensitive. The default isinfo. pick your volume ๐ฃ๏ธ--timeout SECONDSโ specify a maximum limit to the execution time (in seconds) allowed for theabidwandabicompatprograms that are executed. By default, no limit is set for the execution time of these programs. no clock, no stress ๐
Submit Options
--allow-no-debug-infoโ by default, the program fails to submit a binary if its associated debug info could not be found. With this option, however, if no debug info was found, then the ABI information submitted for the binary only comes from ELF symbols; the type information is omitted. the type info just doesn't show up to the party ๐ญ--archive|-Z.EXT[=CMD] โ designate PATH names with a.EXTsuffix to be treated as archives. IfCMDis present, pipe the PATH through the given shell command, otherwise pass as if throughcat. The resulting stream is then opened by libarchive, to enumerate the contents of a wide variety of possible archive file formats. Process each file in the archive individually intoabixml. ๐๏ธ
For example, -Z .zip will process each file in a zip file, and -Z .deb='dpkg-deb --fsys-tarfile' will process each payload file in a Debian archive. unzipping is a personality trait now fr fr ๐
--filter REGEXโ limit files selected forabixmlextraction to those that match the given regular expression. The default is/lib.*\.so, as a heuristic to identify shared libraries. regex girlies stay winning ๐--submit PATH1 PATH2 ...โ usingabidw, extractabixmlfor each of the listed files, generally shared libraries, subject to the filename filter and the archive decoding options. Save the output of eachabidwrun into the selecteddistrobranchof the selected git repo. If--submitand--checkare both given, do submit operations first. order matters, bestie ๐--sysroot PREFIXโ specify a prefix path that is to be removed from submitted file names. chop chop โ๏ธ
Check Options
--check PATH1 PATH2 ...โ usingabidiff, compare each of the listed files, generally executables, againstabixmldocuments for selected versions for all shared libraries needed by the executable. These are listed by enumerating the dynamic segment tagsDT_NEEDEDof the executable. it's giving dependency detective ๐--ld-library-path DIR1:DIR2:DIR3...โ select the search paths forabixmldocuments used to locate any particularSONAME. The first given directory wins ๐ However, all versions of the sameSONAMEin that directory are selected for comparison. The default is unspecified, which means to search for all matchingSONAMEentries in thedistrobranch, regardless of specific directory. go wide or go home ๐
Exit Code
In case of successful submission and/or checking of all paths, the exit code is 0. slay โ
In case of error, the exit code of abidb is nonzero, and a brief listing of the binaries unable to be submitted and/or checked is printed. so you know exactly who ghosted you ๐
Git Repository Schema
abidb stores abixml documents in a git repo with the following naming schema within the distrobranch:
- The directory path leading to the shared library file
- The
SONAMEof the shared library file, as a subdirectory name - A file named
BUILDID.xml, whereBUILDIDis the hexadecimal ELF build-id note of the shared library.
For example:
the page then lays out a lil table with two columns โ shared library file name on the left and abixml path in git on the right โ and the rows start coming through in the next bit ๐ซถ hang tight, we're so close to the end ๐ฆ
โ /usr/lib64/libc.so.6.2.32 โ /usr/lib64/libc.so.6/788cdd41a15985bf8e0a48d213a46e07d58822df.xml โ
โ /usr/lib64/libc.so.6.2.33 โ /usr/lib64/libc.so.6/e2ca832f1c2112aea9d7b9bc639e97e873a6b516.xml โ
โ /lib/ld-linux.so.2 โ /lib/ld-linux.so.2/b65f3c15b129f33f44f504da1719926aec03c07d.xml โ
So why is the buildid shoved in the name like that? Bestie, it's genuinely clever: when a distro gets updated and suddenly has multiple versions of the same shared library floating around, those versions can sit right next to each other in the directory without ever beefing. No conflicts, no drama. And the SONAME lives in the second-last name component โ which is a direct nod to how ld.so and ldconfig do their thing, relying on symbolic links to map references from the SONAME over to an actual file. ๐
๐ See Also ๐
- ELF:
http://en.wikipedia.org/wiki/Executable_and_Linkable_Format - DWARF:
https://www.dwarfstd.org - Debuginfod:
https://sourceware.org/elfutils/Debuginfod.html - Git:
https://git-scm.com/ - Libarchive:
https://www.libarchive.org/
๐ abicompat ๐
Okay so abicompat is the vibe-check tool for ABI compatibility. It checks that an application which links against a given shared library is still ABI compatible with a later version of that same library. And if the new version of the library rolls up with an ABI incompatibility? abicompat doesn't just say "nope" and leave you hanging โ it literally hints at what exactly that incompatibility is. It's giving helpful bestie energy, fr fr. ๐ฅ
โจ Invocation โจ
abicompat [options] [<application> <shared-library-first-version> <shared-library-second-version>]
๐ธ Environment ๐ธ
- Parallel processing
The environment variable ABIGAIL_THREAD_POOL_SIZE can be set to either an integer, or a percentage value, e.g, 10%. The integer value defines the number of threads used by Libabigail to perform parallel processing when applicable. The percentage defines the percentage of the total available cores to use by Libabigail to perform the parallel processing when applicable.
By default, Libabigail uses all the available cores to perform its parallel processing. Main character energy, honestly. ๐
๐ Options ๐
--app-debug-info-dir|--appd <path-to-app-debug-info-directory>โ Sets the path to the directory under which the debug information of the application is supposed to be laid out. Super useful for application binaries whose debug info lives in a separate set of files. ๐๏ธ--btfโ When comparing binaries, extract ABI information from BTF debug information, if present.--ctfโ When comparing binaries, extract ABI information from CTF debug information, if present.--fail-no-debug-infoโ If no debug info was found, this option makes the program fail. Without it, the program will attempt to compare properties of the binaries that are not related to debug info, like pure ELF properties.--helpโ Display a short help about the command and exit. Classic. ๐ซถ--ignore-sonameโ Ignore differences in the SONAME when doing a comparison.--lib-debug-info-dir1|--libd1 <path-to-lib1-debug-info>โ Sets the path to the directory under which the debug information of the first version of the shared library is supposed to be laid out. Useful for shared library binaries whose debug info is in a separate set of files. โจ--lib-debug-info-dir2|--libd2 <path-to-lib1-debug-info>โ Sets the path to the directory under which the debug information of the second version of the shared library is supposed to be laid out. Useful for shared library binaries whose debug info is in a separate set of files. ๐--list-undefined-symbols|-uโ Display the list of undefined symbols of the application and exit.--no-show-locsโ Do not show information about where in the second shared library the respective type was changed.--show-base-names|-bโ In the resulting report emitted by the tool, this makes the application and libraries be referred to by their base names only; not by a full absolute name. Lowkey perfect for scripts that want to compare names of the application and libraries independently of whatever their directory names happen to be. ๐ง--suppressions|--suppr <path-to-suppressions>โ Use a suppression specification file located at path-to-suppressions. Note that this option can appear multiple times on the command line; all the suppression specification files are then taken into account. ๐โversion|-vโ Display the version of the program and exit.--thread-pool-size <N|N%>|-j <N|N%>โ Set the size of the thread pool used by Libabigail for parallel processing. The size is either the number of threads or a percentage of the total number of available cores on the machine. By default, all the available cores of the machine are used for parallel processing.
Note that the environment variable ABIGAIL_THREAD_POOL_SIZE can also be set to similar values. This command line option, if present, supersedes the value of the environment variable. ๐
--weak-modeโ This triggers the weak mode ofabicompat. In this mode, only one version of the library is required. That is,abicompatis invoked like this:
abicompat --weak-mode <the-application> <the-library>
Note that the --weak-mode option can even be omitted if only one version of the library is given, along with the application; in that case, abicompat automatically switches to operate in weak mode:
abicompat <the-application> <the-library>
In this weak mode, the types of functions and variables exported by the library and consumed by the application (as in, the symbols of these functions and variables are undefined in the application and are defined and exported by the library) are compared to the version of these types as expected by the application. And if these two versions of types are different, abicompat tells the user what the differences are.
In other words, in this mode, abicompat checks that the types of the functions and variables exported by the library mean the same thing as what the application expects, as far as the ABI is concerned.
โ ๏ธ Heads up bestie: in this mode, abicompat doesnโt detect exported functions or variables (symbols) that are expected by the application but that are removed from the library. That is literally why it is called weak mode. ๐ญ
๐ Return values ๐
The exit code of the abicompat command is either 0 if the ABI of the binaries being compared are equal, or non-zero if they differ or if the tool encountered an error.
In the latter case, the exit code is an 8-bits-wide bit field in which each bit has a specific meaning. Let's break it down, iykyk:
- The first bit, of value
1, namedABIDIFF_ERRORโ means there was an error. - The second bit, of value
2, namedABIDIFF_USAGE_ERRORโ means there was an error in the way the user invoked the tool. It might be set, for instance, if the user invoked the tool with an unknown command line switch, with a wrong number or argument, etc. If this bit is set, then theABIDIFF_ERRORbit must be set as well. - The third bit, of value
4, namedABIDIFF_ABI_CHANGEโ means the ABI of the binaries being compared are different. - The fourth bit, of value
8, namedABIDIFF_ABI_INCOMPATIBLE_CHANGEโ means the ABI of the binaries compared are different in an incompatible way. If this bit is set, then theABIDIFF_ABI_CHANGEbit must be set as well. If theABIDIFF_ABI_CHANGEis set and theABIDIFF_INCOMPATIBLE_CHANGEis NOT set, then it means the ABIs being compared might or might not be compatible. In that case, a human being needs to review the ABI changes to decide if they're compatible or not. No cap โ you gotta look at it yourself. ๐
The remaining bits are not used for the moment.
๐ Usage examples ๐
- Detecting a possible ABI incompatibility in a new shared library version:
$ cat -n test0.h
1 struct foo
2 {
3 int m0;
4
5 foo()
6 : m0()
7 {}
8 };
9
10 foo*
11 first_func();
12
13 void
14 second_func(foo&);
15
16 void
17 third_func();
$
$ cat -n test-app.cc
1 // Compile with:
2 // g++ -g -Wall -o test-app -L. -ltest-0 test-app.cc
3
4 #include "test0.h"
5
6 int
7 main()
8 {
9 foo* f = first_func();
10 second_func(*f);
11 return 0;
12 }
$
$ cat -n test0.cc
1 // Compile this with:
2 // g++ -g -Wall -shared -o libtest-0.so test0.cc
3
4 #include "test0.h"
5
6 foo*
7 first_func()
8 {
9 foo* f = new foo();
10 return f;
11 }
12
13 void
14 second_func(foo&)
15 {
16 }
17
18 void
19 third_func()
20 {
21 }
$
$ cat -n test1.h
1 struct foo
2 {
3 int m0;
4 char m1; /* <-- a new member got added here! */
5
6 foo()
7 : m0(),
8 m1()
9 {}
10 };
11
12 foo*
13 first_func();
14
15 void
16 second_func(foo&);
17
18 void
19 third_func();
$
$ cat -n test1.cc
1 // Compile this with:
2 // g++ -g -Wall -shared -o libtest-1.so test1.cc
3
4 #include "test1.h"
5
6 foo*
7 first_func()
8 {
9 foo* f = new foo();
10 return f;
11 }
12
13 void
14 second_func(foo&)
15 {
16 }
17
18 /* Let's comment out the definition of third_func()
19 void
20 third_func()
21 {
22 }
23 */
$
- Compile the first and second versions of the libraries,
libtest-0.soandlibtest-1.so:
$ g++ -g -Wall -shared -o libtest-0.so test0.cc
$ g++ -g -Wall -shared -o libtest-1.so test1.cc
- Compile the app and link it against the first version of the library, which gives you the
test-appbinary:
$ g++ -g -Wall -o test-app -L. -ltest-0.so test-app.cc
- Now let
abicompatdo its thing โ islibtest-1.soABI-compatible with the app, like, with respect to the ABI oflibtest-0.so? ๐
$ abicompat test-app libtest-0.so libtest-1.so
ELF file 'test-app' might not be ABI compatible with 'libtest-1.so' due to differences with 'libtest-0.so' below:
Functions changes summary: 0 Removed, 2 Changed, 0 Added functions
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
2 functions with some indirect sub-type change:
[C]'function foo* first_func()' has some indirect sub-type changes:
return type changed:
in pointed to type 'struct foo':
size changed from 32 to 64 bits
1 data member insertion:
'char foo::m1', at offset 32 (in bits)
[C]'function void second_func(foo&)' has some indirect sub-type changes:
parameter 0 of type 'foo&' has sub-type changes:
referenced type 'struct foo' changed, as reported earlier
$
- And now the weak mode of
abicompatโ that's when you hand it just the app plus the new library version and nothing else โจ
$ abicompat --weak-mode test-app libtest-1.so
functions defined in library
'libtest-1.so'
have sub-types that are different from what application
'test-app'
expects:
function foo* first_func():
return type changed:
in pointed to type 'struct foo':
size changed from 32 to 64 bits
1 data member insertion:
'char foo::m1', at offset 32 (in bits)
$
๐ง abilint
Okay so abilint is the little sanity-checker bestie ๐ It parses the native XML representation of an ABI โ the exact stuff abidw spits out โ and once it's parsed that XML, it builds an in-memory model out of it. Then it tries to save that model back out to XML form on standard output. And here's the tea: if that read-write round trip actually succeeds, chances are the input XML ABI representation is meaningful. Iconic behaviour, honestly.
Heads up: the main intent of this tool is helping you debug issues in the underlying Libabigail library itself. So it's a dev-tools queen, not your everyday driver ๐
Also note โ abilint can read an ELF input file too, build the in-memory model for its ABI, and serialize that model back into XML to standard output. In that case though, the ELF input file must come with its debug information in the DWARF format. No DWARF, no party ๐
๐ธ Invocation
abilint [options] [<abi-file1>]
โจ Options
--abidiffโ runs an ABI comparison between the internal representation against the input ABIXML, and the internal representation of the file that was saved to disk and read back into memory. The result of that comparison should be the empty set โ unless Libabigail has a bug. (Which, respectfully, is the whole point of checking ๐)--annotateโ annotates the ABIXML output with comments above most elements. Those comments are made of the pretty-printed form of types, declarations, or even ELF symbols. The whole purpose is making the ABIXML output more human-readable for debugging or documenting purposes. It's giving considerate ๐--ctfโ extract ABI information from CTF debug information, if present in the given object.--debug-info-dir <path>โ when you're reading an ELF input file whose debug info got split out into a separate file, this option tellsabilintwhere to go find that separate debug information file.
Note that path must point to the root directory under which the debug info is arranged in a tree-like manner. On Red Hat based systems, that directory is usually <root>/usr/lib/debug.
Also note this option isn't mandatory for split debug info installed by your system's package manager, because then abidiff knows where to find it. Slay, package manager ๐ซถ
--diffโ for XML inputs, it does a text diff between the input and the memory model saved back to disk. Super handy for spotting issues in how the underlying Libabigail library handles the XML format ๐--header-file | --hf <header-file-path>โ specifies where to find one of the public headers of the abi file that the tool has to consider. The tool will thus filter out types that aren't defined in public headers.--headers-dir | --hd <headers-directory-path-1>โ specifies where to find the public headers of the first shared library that the tool has to consider. The tool will thus filter out types that aren't defined in public headers.--helpโ display a short help message and exit. The classic ๐--load-all-typesโ analyze all the types coming from the ABIXML input, including those that aren't reachable from exported declarations. No type left behind fr fr.--nooutโ do not display anything on standard output. The return code of the command is the only way to know if the command succeeded. Silent but deadly ๐--suppressions | suppr <path-to-suppression-specifications-file>โ use a suppression specification file located at path-to-suppression-specifications-file. Note that this option can appear multiple times on the command line โ in that case, all of the provided suppression specification files are taken into account. ABI artifacts matched by the suppression specifications get suppressed from the output of this tool.--stdin | --โ read the input content from standard input.--tuโ expect the input XML to represent a single translation unit.โverboseโ shows verbose messages about internal stuff. This one's for debugging the tool and its underlying library. (And yes, the page really does spell it with a fancy dash, don't come for me ๐ญ)โversion | -vโ display the version of the program and exit.
๐ฆ fedabipkgdiff
fedabipkgdiff compares the ABI of shared libraries inside Fedora packages ๐ฅ It's the convenient way to do it so you don't have to manually download packages from the Fedora Build System like some kind of Victorian orphan.
This tool knows how to talk with the Fedora Build System to find the right package versions, their associated debug information and development packages, download them, compare their ABI locally, and report back about the possible ABI changes. It's giving full service ๐
Note that by default, this tool reports ABI changes about types that are defined in public header files found in the development packages associated with the packages being compared. It also reports ABI changes about functions and global variables whose symbols are defined and exported in the ELF binaries found in the packages being compared. So yeah, it's picky in the best way ๐
๐ฆ Invocation
fedabipkgdiff [option] <NVR> ...
๐ธ Environment
fedabipkgdiff loads two default suppression specification files, merges their content, and uses it to filter out ABI change reports that might be considered as false positives to users. Thoughtful queen ๐
- Default system-wide suppression specification file
It's located by the optional environment variable LIBABIGAIL_DEFAULT_SYSTEM_SUPPRESSION_FILE. If that environment variable isn't set, then fedabipkgdiff tries to load the suppression file $libdir/libabigail/libabigail-default.abignore. If that file isn't present, then no default system-wide suppression specification file is loaded. No file, no problem ๐ซถ
- Default user suppression specification file
It's located by the optional environment LIBABIGAIL_DEFAULT_USER_SUPPRESSION_FILE. If that environment variable isn't set, then fedabipkgdiff tries to load the suppression file $HOME/.abignore. If that file isn't present, then no default user suppression specification is loaded.
โจ Options
--abipkgdiff <path/to/abipkgdiff>โ specify an alternativeabipkgdiffinstead of the one installed in the system. Main character energy: you choose who's doing the diffing ๐--all-subpackagesโ instructs the tool to also compare the ABI of the binaries in the sub-packages of the packages specified. All of them. Every last one ๐--clean-cacheโ if you want to clean the cache both before and after the ABI comparison,--clean-cacheis the convenient way to save yourself typing two options at the same time. Efficiency icon โจ--clean-cache-afterโ clean cache after ABI comparison.--clean-cache-beforeโ clean cache before ABI comparison.--debugโ emit debugging messages about the execution of the program. Details about each method invocation, including input parameters and returned values, are emitted. So much detail. Every last crumb ๐--dry-runโ don't actually perform the ABI comparison. Details about what's going to be done are emitted on standard output. Rehearsal era ๐--dso-onlyโ compares the ABI of shared libraries only. If this option isn't provided, the tool compares the ABI of all ELF binaries found in the packages.--from <distro>โ specifies the name of the baseline Fedora distribution in which to find the first build used for comparison. The distro value can be any valid value of the RPM macro%{?dist}for Fedora โ for example,fc4,fc23,fc25.--help | -hโ display a short help about the command and exit.--no-default-suppressionโ do not load the default suppression specification files. Free spirit vibe ๐--no-devel-pkgโ โฆand this is exactly where this chunk of the page cuts off, bestie ๐ hold that thought.
Okay so the vibe check on this next option is literally "don't drag the dev packages into it" ๐
Do not take associated development packages into account when performing the ABI comparison. This makes the tool report ABI changes about all types that are reachable from functions and global variables which symbols are defined and publicly exported in the binaries being compared, even if those types are not defined in public header files available from the packages being compared. tl;dr: it'll still clock ABI changes on types it can reach through exported symbols, even when those types aren't sitting in any public header shipped by the packages you're comparing. ๐ซถ
--private-dsoโ compares the ABI of shared libraries that are private to the packages. No flag, no comparison: if you don't pass this, those private-to-the-package shared libraries just don't get compared at all. ๐--server <URL>โ tells the tool which Koji XMLRPC service URL to talk to. The default ishttp://koji.fedoraproject.org/kojihub. ๐ฐ๏ธ--show-identical-binariesโ shows the names of all the binaries compared, including the ones whose ABI came out equal. By default, when you don't pass this, only binaries with ABI changes get mentioned in the output. so if you want the full roster, not just the drama, this is your flag โจ--suppressions <path-to-suppresions>โ uses a suppression specification file located at path-to-suppressions. ๐คซ--to <distro>โ the name of the Fedora distribution where the tool should find the build compared against the baseline given by--from. The distro value can be any valid value of the RPM macro%{?dist}for Fedora โ for example,fc4,fc23. ๐ง--topurl <URL>โ the URL of the package store the tool downloads RPMs from. The default ishttps://kojipkgs.fedoraproject.org. ๐ฆ--tracebackโ shows the traceback when an exception gets raised. Genuinely useful for developers of the tool itself who want to see the more exceptional errors. iykyk, stack traces are love letters to future you ๐
Note, bestie: a build is a specific version and release of an RPM package. It's specified by its package name, version and release โ those are specified by the Fedora Naming Guidelines. ๐
๐ Return value ๐
The exit code of the abipkgdiff command is 0 if the ABI of the binaries compared are equivalent, and non-zero if they differ or if the tool hit an error. ๐
In the latter case, the value of the exit code is the same as for the abidiff tool. (consistency, we stan ๐ซถ)
๐ Use cases ๐
Below are some usage examples that fedabipkgdiff currently supports. Grab a snack, these are iconic.
- Compare the ABI of binaries in a local package against the ABI of the latest stable package in Fedora 23. ๐
So say you just built the httpd package and you wanna compare the ABI of the binaries in this locally built package against the ABI of the binaries in the latest http build from Fedora 23. The command line invocation would be:
$ fedabipkgdiff --from fc23 ./httpd-2.4.18-2.fc24.x86_64.rpm
- Compare the ABI of binaries in two local packages. ๐ง
Say you built two versions of the httpd package and you wanna see what ABI differences exist between these two versions of RPM files. The command line invocation would be:
$ fedabipkgdiff path/to/httpd-2.4.23-3.fc23.x86_64.rpm another/path/to/httpd-2.4.23-4.fc24.x86_64.rpm
Everything fedabipkgdiff does happens on your local machine, no querying or downloading RPMs from Koji needed. ๐ โจ
- Compare the ABI of binaries in the latest build of the
httpdpackage in Fedora 23 against the ABI of the binaries in the latest build of the same package in 24. ๐ธ
Heads up: in this case neither of the two packages is available locally. The tool is going to talk with the Fedora Build System, figure out what the versions and releases of the latest packages are, download them and do the comparison locally. The command line invocation would be:
$ fedabipkgdiff --from fc23 --to fc24 httpd
- Compare the ABI of binaries of two builds of the
httpdpackage, designated by their versions and releases. ๐ฆ
If we wanna do the ABI comparison for all the processor architectures supported by Fedora, the command line invocation would be:
$ fedabipkgdiff httpd-2.8.14.fc23 httpd-2.8.14.fc24
But if we wanna do the ABI comparison for one specific architecture โ say, x86_64 โ then the command line invocation would be:
$ fedabipkgdiff httpd-2.8.14.fc23.x86_64 httpd-2.8.14.fc24.x86_64
- If the user wants to also compare the sub-packages of a given package, she can use the
--all-subpackagesoption. The first command of the previous example would thus look like: ๐
$ fedabipkgdiff --all-subpackages httpd-2.8.14.fc23 httpd-2.8.14.fc24
๐ AUTHOR ๐
Dodji Seketeli โ the main character who wrote this whole thing, fr fr. ๐ซก
๐ COPYRIGHT ๐
2014-2026, Red Hat, Inc.
And that's a wrap on the whole page, bestie!! ๐ You read all of LIBABIGAIL(7) and honestly? Slay. This is LIBABIGAIL(7) as of Oct 08, 2026 โ go forth and compare ABIs responsibly. ๐
โจ๐๐ฆ
๐ฆ where this page comes from
- centos-stream 9: libabigail 3.0-1.el9 ยท Apache-2.0 WITH LLVM-exception
- centos-stream 9: libabigail 3.0-1.el9 ยท Apache-2.0 WITH LLVM-exception
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms