On Wed, Mar 1, 2023 at 4:54 PM John Moon <quic_johmoo@xxxxxxxxxxx> wrote:
While the kernel community has been good at maintaining backwards
compatibility with kernel UAPIs, it would be helpful to have a tool
to check if a commit introduces changes that break backwards
compatibility.
To that end, introduce check-uapi.sh: a simple shell script that
checks for changes to UAPI headers using libabigail.
libabigail is "a framework which aims at helping developers and
software distributors to spot some ABI-related issues like interface
incompatibility in ELF shared libraries by performing a static
analysis of the ELF binaries at hand."
The script uses one of libabigail's tools, "abidiff", to compile the
changed header before and after the commit to detect any changes.
abidiff "compares the ABI of two shared libraries in ELF format. It
emits a meaningful report describing the differences between the two
ABIs."
The script also includes the ability to check the compatibilty of
all UAPI headers across commits. This allows developers to inspect
the stability of the UAPIs over time.
Let's see more test cases.
[Case 1]
I think d759be8953febb6e5b5376c7d9bbf568864c6e2d
is a trivial/good cleanup.
Apparently, it still exports equivalent headers,
but this tool reports "incorrectly removed".
$ ./scripts/check-uapi.sh -b d759be8953
Saving current tree state... OK
Installing sanitized UAPI headers from d759be8953... OK
Installing sanitized UAPI headers from d759be8953^1... OK
Restoring current tree state... OK
Checking changes to UAPI headers starting from d759be8953
error - UAPI header arch/alpha/include/uapi/asm/poll.h was incorrectly removed
error - UAPI header arch/ia64/include/uapi/asm/poll.h was incorrectly removed
error - UAPI header arch/x86/include/uapi/asm/poll.h was incorrectly removed
/tmp/tmp.ixUIBlntUP/d759be8953/x86/usr/include/asm/Kbuild does not
exist - cannot compare ABI
/tmp/tmp.ixUIBlntUP/d759be8953/alpha/usr/include/asm/Kbuild does not
exist - cannot compare ABI
/tmp/tmp.ixUIBlntUP/d759be8953/ia64/usr/include/asm/Kbuild does not
exist - cannot compare ABI
error - 6/6 UAPI headers modified between d759be8953^1 and d759be8953
are not backwards compatible
error - UAPI header ABI check failed
Failure summary saved to /home/masahiro/ref/linux/abi_error_log.txt
[Case 2]
This tool compiles only changed headers.
Does it detect ABI change?
I believe the users of the headers must be compiled.
Think about this case.
$ cat foo-typedef.h
typedef int foo_cap_type;
$ cat foo.h
#include "foo-typedef.h"
struct foo {
foo_cap_type capability;
};
Then, change the first header to
typedef long long foo_cap_type;
abidiff will never notice the ABI change
until it compiles "foo.h" instead of "foo-typedef.h"
>
For testing, I applied the following patch.
--- a/include/uapi/linux/types.h
+++ b/include/uapi/linux/types.h
@@ -52,7 +52,7 @@ typedef __u32 __bitwise __wsum;
#define __aligned_be64 __be64 __attribute__((aligned(8)))
#define __aligned_le64 __le64 __attribute__((aligned(8)))
-typedef unsigned __bitwise __poll_t;
+typedef unsigned short __bitwise __poll_t;
#endif /* __ASSEMBLY__ */
#endif /* _UAPI_LINUX_TYPES_H */
I believe this is an ABI change because this will change
'struct epoll_event' in the include/uapi/linux/eventpoll.h
but the tool happily reports it is backwards compatible.
$ ./scripts/check-uapi.sh
Saving current tree state... OK
Installing sanitized UAPI headers from HEAD... OK
Installing sanitized UAPI headers from HEAD^1... OK
Restoring current tree state... OK
Checking changes to UAPI headers starting from HEAD
No ABI differences detected in include/uapi/linux/types.h from HEAD^1 -> HEAD
All 1 UAPI headers modified between HEAD^1 and HEAD are backwards compatible!
I would not use such a tool that contains both false positives
and false negatives, but you may notice this is more difficult
than you had expected.
I do not know if further review is worthwhile since this does not work
but I added some more in-line comments.
+
+# Some UAPI headers require an architecture-specific compiler to build properly.
+ARCH_SPECIFIC_CC_NEEDED=(
+ "arch/hexagon/include/uapi/asm/sigcontext.h"
+ "arch/ia64/include/uapi/asm/intel_intrin.h"
+ "arch/ia64/include/uapi/asm/setup.h"
+ "arch/ia64/include/uapi/asm/sigcontext.h"
+ "arch/mips/include/uapi/asm/bitfield.h"
+ "arch/mips/include/uapi/asm/byteorder.h"
+ "arch/mips/include/uapi/asm/inst.h"
+ "arch/sparc/include/uapi/asm/fbio.h"
+ "arch/sparc/include/uapi/asm/uctx.h"
+ "arch/xtensa/include/uapi/asm/byteorder.h"
+ "arch/xtensa/include/uapi/asm/msgbuf.h"
+ "arch/xtensa/include/uapi/asm/sembuf.h"
+)
Yes, arch/*/include/ must be compiled by the target compiler.
If you compile them by the host compiler, it is unpredictable (i.e. wrong).
BTW, was this blacklist detected on a x86 host?
If you do this on an ARM/ARM64 host, some headers
under arch/x86/include/uapi/ might be blacklisted?
+# Compile the simple test app
+do_compile() {
+ local -r inc_dir="$1"
+ local -r header="$2"
+ local -r out="$3"
+ printf "int main(void) { return 0; }\n" | \
+ "${CC:-gcc}" -c \
+ -o "$out" \
+ -x c \
+ -O0 \
+ -std=c90 \
+ -fno-eliminate-unused-debug-types \
+ -g \
+ "-I${inc_dir}" \
+ -include "$header" \
+ -
+}
+
+# Print the list of incompatible headers from the usr/include Makefile
+get_no_header_list() {
+ {
+ # shellcheck disable=SC2016
+ printf 'all: ; @echo $(no-header-test)\n'
+ cat "usr/include/Makefile"
You must pass SRCARCH=$arch.
Otherwise,
ifeq ($(SRCARCH),...)
...
endif
are all skipped.
+ } | make -f - | tr " " "\n" | grep -v "asm-generic"
+
+ # One additional header file is not building correctly
+ # with this method.
+ # TODO: why can't we build this one?
+ printf "asm-generic/ucontext.h\n"
Answer - it is not intended for standalone compiling in the first place.
<asm-generic/*.h> should be included from <asm/*.h>.
Userspace never ever includes <asm-generic/*.h> directly.
(If it does, it is a bug in the userspace program)
I am afraid you read user/include/Makefile wrongly.
+
+# Install headers for every arch and ref we need
+install_headers() {
+ local -r check_all="$1"
+ local -r base_ref="$2"
+ local -r ref="$3"
+
+ local arch_list=()
+ while read -r status file; do
+ if arch="$(printf "%s" "$file" | grep -o 'arch/.*/uapi' | cut -d '/' -f 2)"; then
+ # shellcheck disable=SC2076
+ if ! [[ " ${arch_list[*]} " =~ " $arch " ]]; then
+ arch_list+=("$arch")
+ fi
+ fi
+ done < <(get_uapi_files "$check_all" "$base_ref" "$ref")
+
+ deviated_from_current_tree="false"
+ for inst_ref in "$base_ref" "$ref"; do
+ if [ -n "$inst_ref" ]; then
+ if [ "$deviated_from_current_tree" = "false" ]; then
+ save_tree_state
+ trap 'rm -rf "$tmp_dir"; restore_tree_state;' EXIT
+ deviated_from_current_tree="true"
+ fi
+ git checkout --quiet "$(git rev-parse "$inst_ref")"
I might be wrong, but I was worried when I looked at this line
because git-checkout may change the running code
if check-uapi.sh is changed between ref and base_ref.
If bash always loads all code into memory before running
it is safe but I do not know how it works.
If this is safe, some comments might be worthwhile:
# 'git checkout' may update this script itself while running,
# but it is OK because ...
+
+# Make sure we have the tools we need
+check_deps() {
+ export ABIDIFF="${ABIDIFF:-abidiff}"
+
+ if ! command -v "$ABIDIFF" > /dev/null 2>&1; then
+ eprintf "error - abidiff not found!\n"
+ eprintf "Please install abigail-tools (version 1.7 or greater)\n"
+ eprintf "See: https://sourceware.org/libabigail/manual/libabigail-overview.html\n"
+ exit 1
+ fi
+
+ read -r abidiff_maj abidiff_min _unused < <("$ABIDIFF" --version | cut -d ' ' -f 2 | tr '.' ' ')
+ if [ "$abidiff_maj" -lt 1 ] || { [ "$abidiff_maj" -eq 1 ] && [ "$abidiff_min" -lt 7 ]; }; then
This is up to you, but I think "sort -V" would be cleaner.
(see Documentation/devicetree/bindings/Makefile for example)
+ fi
+
+ if [ ! -x "scripts/unifdef" ]; then
+ if ! make -f /dev/null scripts/unifdef; then
Previously, I wanted to point out that using Make is meaningless,
and using gcc directly is better.
But, is this still necessary?
V2 uses 'make headers_install' to install all headers.
scripts/unifdef is not used anywhere in this script.
+
+ abi_error_log="${abi_error_log:-${KERNEL_SRC}/abi_error_log.txt}"
+
+ check_deps
+
+ tmp_dir=$(mktemp -d)
+ trap 'rm -rf "$tmp_dir"' EXIT
+
+ # Set of UAPI directories to check by default
+ UAPI_DIRS=(include/uapi arch/*/include/uapi)
+
+ if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
+ eprintf "error - this script requires the kernel tree to be initialized with Git\n"
+ exit 1
+ fi
+
+ # If there are no dirty UAPI files, use HEAD as base_ref
+ if [ -z "$base_ref" ] && [ "$(get_uapi_files "" "" | wc -l)" -eq 0 ]; then
+ base_ref="HEAD"
+ fi
+
+ if [ -z "$ref_to_check" ]; then
+ if [ -n "$base_ref" ]; then
+ ref_to_check="${base_ref}^1"
+ else
+ ref_to_check="HEAD"
+ fi
+ fi
I think this is because I am not good at English, but
I was so confused between 'base_ref' vs 'ref_to_check'.
I do not get which one is the ancestor from the names.
I thought 'ref_a' and 'ref_b' would be less confusing,
but I hope somebody will come up with better naming
than that.
--
Best Regards
Masahiro Yamada