[applied] Add support for BTF

Message ID 87pmbrl7nd.fsf@redhat.com
State New
Headers
Series [applied] Add support for BTF |

Commit Message

Dodji Seketeli Jan. 6, 2023, 8:39 p.m. UTC
  Hello,

This patch adds support for the BTF debug information format.  It
provides a new BTF front-end which can be instantiated by the function
tools::create_best_elf_based_reader().

For now, the BTF front-end supports the basic types (integers,
pointers, qualified types, typedefs, struct and unions and function
pointers) for functions and variables as emitted for the C language by
GCC.  It seems to be able to support the BTF debug information emitted
for the vmlinux kernel by the pahole tool as well.

When configured with the --enable-btf option, the WITH_BTF
pre-processor macro is defined, enabling the BTF support.  That option
is turned on by default if the /usr/include/bpf/btf.h header is found
on the system.  To disable this, one can use the --disable-btf option.

The abidw and abidiff programs have been adapted to use the BTF
front-end when provided with the '--btf' option, or if BTF debug
information is the only one present in the binary.

	* configure.ac: If the header /usr/include/bpf/btf.h exists, then
	define the WITH_BTF pre-processor macro, unless --disable-btf was
	provided.
	* doc/manuals/abidiff.rst: Document the new --btf option.
	* doc/manuals/abidw.rst: Likewise.
	* doc/manuals/kmidiff.rst: Likewise.
	* doc/manuals/abipkgdiff.rst: Likewise.
	* include/abg-btf-reader.h: New header file.  Contains the
	declaration of the new btf::reader class.
	* src/abg-btf-reader.cc: New source file.  Contains the
	definitions of the new btf::reader class.
	* include/Makefile.am: Add the new include/abg-btf-reader.h header
	file to source distribution.
	* include/abg-corpus.h (enum origin): Add a new BTF_ORIGIN
	enumerator.
	* include/abg-tools-utils.h (file_has_btf_debug_info): Declare new
	function.
	* src/abg-tools-utils.cc (file_has_btf_debug_info): Define new
	function.
	(create_best_elf_based_reader): Adapt to support BTF input.  If
	the user requested the BTF front-end, instantiate it.  Otherwise,
	if the input file has only BTF debug info, instantiate the BTF
	front end.
	* include/abg-elf-reader.h (elf::reader::find_btf_section):
	Declare new member function.
	(elf::reader::{function, variable}_symbol_is_exported): Add new
	overloads.
	* src/abg-elf-reader.cc (reader::priv::btf_section): New data
	member.
	(reader::find_btf_section): Define new member function.
	* src/Makefile.am: Add the new abg-ctf-reader.cc file to source
	distribution.
	* tools/abidw.cc (options::use_btf): New data member.
	(display_usage): Add a help string for the new --btf option.
	(parse_command_line): Support the new --btf option.
	(load_corpus_and_write_abixml):  If the user asked to use the btf
	front-end then use that one.
	* tools/abidiff.cc (options::use_btf): New data member.
	(options::options): Initialize it.
	(display_usage):: Add a help string to the new --btf options.
	(parse_command_line): Support the new --btf options.
	(main): If the user asked to use the btf front-end, then use that
	one.
	* tools/abidw.cc (options::use_btf): New data member.
	(options::options): Initialize it.
	(parse_command_line): Add a help string to the new --btf options.
	(load_corpus_and_write_abixml): If the user asked to use the btf
	front-end, then use that one.
	* tools/kmidiff.cc (options::use_btf): New data member.
	(options::options): Initialize it.
	(display_usage): Add a help string to the new --btf options.
	(parse_command_line): Add a help string to the new --btf options.
	(main): If the user asked to use the btf front-end, then use that
	one.
	* tools/abipkgdiff.cc (options::use_btf): New data member.
	(options::options): Initialize it.
	(display_usage): Add a help string to the new --btf options.
	(parse_command_line): Add a help string to the new --btf options.
	(compare, compare_to_self)
	(compare_prepared_linux_kernel_packages): If the user asked to use
	the btf front-end, then use that one.
	* tests/data/test-read-btf/test{0,1}.o: New binary test input
	file.
	* tests/data/test-read-btf/test{0,1}.c: Source code of the binary
	input file above.
	* tests/data/test-read-btf/test{0,1}.o.abi: Reference ABIXML
	output.
	* tests/data/test-abidiff-exit/btf/test0-report-{1,2}.txt: New
	test reference output.
	* tests/data/test-abidiff-exit/btf/test0-v{0,1}.o: New binary test
	input.
	* tests/data/test-abidiff-exit/btf/test0-v{0,1}.c: The source
	files of the binary inputs above.
	* tests/test-read-btf.cc: New test file to run the btf/abixml
	tests.
	* tests/Makefile.am: Add the new test files to the source
	distribution.

Signed-off-by: Dodji Seketeli <dodji@redhat.com>
---
 configure.ac                                  |   81 +-
 doc/manuals/abidiff.rst                       |   11 +-
 doc/manuals/abidw.rst                         |    9 +-
 doc/manuals/abipkgdiff.rst                    |   16 +-
 doc/manuals/kmidiff.rst                       |   19 +-
 include/Makefile.am                           |    4 +
 include/abg-btf-reader.h                      |   34 +
 include/abg-corpus.h                          |    3 +-
 include/abg-elf-reader.h                      |    6 +
 include/abg-tools-utils.h                     |    2 +
 src/Makefile.am                               |    4 +
 src/abg-btf-reader.cc                         | 1102 +++++++++++++++++
 src/abg-elf-reader.cc                         |   22 +
 src/abg-tools-utils.cc                        |   46 +
 tests/Makefile.am                             |   11 +
 tests/data/Makefile.am                        |   13 +
 .../test-abidiff-exit/btf/test0-report-1.txt  |   16 +
 .../test-abidiff-exit/btf/test0-report-2.txt  |   53 +
 tests/data/test-abidiff-exit/btf/test0-v0.c   |   40 +
 tests/data/test-abidiff-exit/btf/test0-v0.o   |  Bin 0 -> 2320 bytes
 tests/data/test-abidiff-exit/btf/test0-v1.c   |   45 +
 tests/data/test-abidiff-exit/btf/test0-v1.o   |  Bin 0 -> 2376 bytes
 tests/data/test-read-btf/test0.c              |   40 +
 tests/data/test-read-btf/test0.o              |  Bin 0 -> 2312 bytes
 tests/data/test-read-btf/test0.o.abi          |   90 ++
 tests/data/test-read-btf/test1.c              |   20 +
 tests/data/test-read-btf/test1.o              |  Bin 0 -> 1536 bytes
 tests/data/test-read-btf/test1.o.abi          |   23 +
 tests/test-abidiff-exit.cc                    |   24 +
 tests/test-read-btf.cc                        |  188 +++
 tools/abidiff.cc                              |   26 +
 tools/abidw.cc                                |   20 +
 tools/abipkgdiff.cc                           |   62 +-
 tools/kmidiff.cc                              |   24 +-
 34 files changed, 2026 insertions(+), 28 deletions(-)
 create mode 100644 include/abg-btf-reader.h
 create mode 100644 src/abg-btf-reader.cc
 create mode 100644 tests/data/test-abidiff-exit/btf/test0-report-1.txt
 create mode 100644 tests/data/test-abidiff-exit/btf/test0-report-2.txt
 create mode 100644 tests/data/test-abidiff-exit/btf/test0-v0.c
 create mode 100644 tests/data/test-abidiff-exit/btf/test0-v0.o
 create mode 100644 tests/data/test-abidiff-exit/btf/test0-v1.c
 create mode 100644 tests/data/test-abidiff-exit/btf/test0-v1.o
 create mode 100644 tests/data/test-read-btf/test0.c
 create mode 100644 tests/data/test-read-btf/test0.o
 create mode 100644 tests/data/test-read-btf/test0.o.abi
 create mode 100644 tests/data/test-read-btf/test1.c
 create mode 100644 tests/data/test-read-btf/test1.o
 create mode 100644 tests/data/test-read-btf/test1.o.abi
 create mode 100644 tests/test-read-btf.cc

new file mode 100644
index 00000000..c299b582
new file mode 100644
index 00000000..5e5bdd57
new file mode 100644
index 00000000..165beaee
new file mode 100644
index 00000000..273039ba
  

Patch

diff --git a/configure.ac b/configure.ac
index a009fbc6..d2896020 100644
--- a/configure.ac
+++ b/configure.ac
@@ -201,6 +201,12 @@  AC_ARG_ENABLE(ctf,
 	      ENABLE_CTF=$enableval,
 	      ENABLE_CTF=auto)
 
+dnl check if user has enabled BTF code
+AC_ARG_ENABLE(btf,
+	      AS_HELP_STRING([--enable-btf=yes|no],
+			     [disable support of btf files)]),
+	      ENABLE_BTF=$enableval,
+	      ENABLE_BTF=auto)
 dnl *************************************************
 dnl check for dependencies
 dnl *************************************************
@@ -339,6 +345,77 @@  if test x$ENABLE_CTF != xno; then
   fi
 fi
 
+dnl configure BTF usage
+BPF_LIBS=
+if test x$ENABLE_BTF != xno; then
+  AC_CHECK_HEADER([bpf/btf.h],
+		  [ENABLE_BTF=yes],
+		  [AC_MSG_NOTICE([could not find bpf/btf.h])])
+  if test x$ENABLE_BTF = xyes; then
+    AC_MSG_NOTICE([enable BTF support])
+    ENABLE_BTF=yes
+    AC_DEFINE([WITH_BTF], 1,
+	     [Defined if user enabled BTF usage])
+    BPF_LIBS=-lbpf
+  else
+    AC_MSG_NOTICE([BTF support was disabled])
+    ENABLE_BTF=no
+  fi
+
+dnl Test if various functions and structs are present.
+
+  if test x$ENABLE_BTF = xyes; then
+    dnl Test if struct btf_enum64 is present.
+    AC_CHECK_TYPE([struct btf_enum64],
+    		  [HAVE_BTF_ENUM64=yes],
+		  [HAVE_BTF_ENUM64=no],
+		  [#include <bpf/btf.h>])
+
+    if test x$HAVE_BTF_ENUM64 = xyes; then
+      AC_DEFINE([WITH_BTF_ENUM64], 1, [struct btf_enum64 is present])
+    fi
+
+    dnl Test if btf__get_nr_types is present
+    AC_CHECK_DECL([btf__get_nr_types],
+		  [HAVE_BTF__GET_NR_TYPES=yes],
+		  [HAVE_BTF__GET_NR_TYPES=no],
+		  [#include <bpf/btf.h>])
+
+    if test x$HAVE_BTF__GET_NR_TYPES = xyes; then
+       AC_DEFINE(WITH_BTF__GET_NR_TYPES, 1, [The function btf__get_nr_types is present])
+    fi
+
+    dnl Test if btf__type_cnt is present
+    AC_CHECK_DECL([btf__type_cnt],
+		  [HAVE_BTF__TYPE_CNT=yes],
+		  [HAVE_BTF__TYPE_CNT=no],
+		  [#include <bpf/btf.h>])
+    if test x$HAVE_BTF__TYPE_CNT = xyes; then
+       AC_DEFINE(WITH_BTF__TYPE_CNT, 1, [The function btf__type_cnt is present])
+    fi
+
+    dnl Test if BTF_KIND_TYPE_TAG exists
+    AC_CHECK_DECL([int kind = BTF_KIND_TYPE_TAG],
+    		  [HAVE_BTF_KIND_TYPE_TAG=yes],
+		  [HAVE_BTF_KIND_TYPE_TAG=no],
+		  [#include <bpf/btf.h>])
+    if test x$HAVE_BTF_KIND_TYPE_TAG = xyes; then
+       AC_DEFINE([WITH_BTF_KIND_TYPE_TAG], 1,
+       [The BTF_KIND_TYPE_TAG enumerator is present])
+    fi
+
+    dnl Test if BTF_KIND_DECL_TAG exists
+    AC_CHECK_DECL([int kind = BTF_KIND_DECL_TAG],
+    		  [HAVE_BTF_KIND_DECL_TAG=yes],
+		  [HAVE_BTF_KIND_DECL_TAG=no],
+		  [#include <bpf/btf.h>])
+    if test x$HAVE_BTF_KIND_DECL_TAG = xyes; then
+       AC_DEFINE([WITH_BTF_KIND_DECL_TAG], 1,
+       [The BTF_KIND_DECL_TAG enumerator is present])
+    fi
+  fi
+fi
+
 dnl Check for dependency: libxml
 LIBXML2_VERSION=2.6.22
 PKG_CHECK_MODULES(XML, libxml-2.0 >= $LIBXML2_VERSION)
@@ -741,7 +818,7 @@  AX_VALGRIND_CHECK
 
 dnl Set the list of libraries libabigail depends on
 
-DEPS_LIBS="$XML_LIBS $ELF_LIBS $DW_LIBS $CTF_LIBS"
+DEPS_LIBS="$XML_LIBS $ELF_LIBS $DW_LIBS $CTF_LIBS $BPF_LIBS"
 AC_SUBST(DEPS_LIBS)
 
 if test x$ABIGAIL_DEVEL != x; then
@@ -782,6 +859,7 @@  fi
 dnl Set a few Automake conditionals
 
 AM_CONDITIONAL([CTF_READER],[test "x$ENABLE_CTF" = "xyes"])
+AM_CONDITIONAL([BTF_READER],[test "x$ENABLE_BTF" = "xyes"])
 
 dnl Set the level of C++ standard we use.
 CXXFLAGS="$CXXFLAGS -std=$CXX_STANDARD"
@@ -1092,6 +1170,7 @@  AC_MSG_NOTICE([
     Enable fedabipkgdiff                           : ${ENABLE_FEDABIPKGDIFF}
     Enable python 3				   : ${ENABLE_PYTHON3}
     Enable CTF front-end                           : ${ENABLE_CTF}
+    Enable BTF front-end                           : ${ENABLE_BTF}
     Enable running tests under Valgrind            : ${enable_valgrind}
     Enable build with -fsanitize=address    	   : ${ENABLE_ASAN}
     Enable build with -fsanitize=memory    	   : ${ENABLE_MSAN}
diff --git a/doc/manuals/abidiff.rst b/doc/manuals/abidiff.rst
index dd357fba..2debc20d 100644
--- a/doc/manuals/abidiff.rst
+++ b/doc/manuals/abidiff.rst
@@ -16,8 +16,9 @@  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`_ format, if present, finally, if neither is found, it uses only
-`ELF`_ symbols to report which of them were added or removed.
+`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.
 
 .. include:: tools-use-libabigail.txt
 
@@ -605,6 +606,11 @@  Options
     When comparing binaries, extract ABI information from `CTF`_ debug
     information, if present.
 
+  * ``--btf``
+
+    When comparing binaries, extract ABI information from `BTF`_ debug
+    information, if present.
+
   * ``--stats``
 
     Emit statistics about various internal things.
@@ -830,6 +836,7 @@  Usage examples
 .. _ELF: http://en.wikipedia.org/wiki/Executable_and_Linkable_Format
 .. _DWARF: http://www.dwarfstd.org
 .. _CTF: https://raw.githubusercontent.com/wiki/oracle/binutils-gdb/files/ctf-spec.pdf
+.. _BTF: https://docs.kernel.org/bpf/btf.html
 .. _ODR: https://en.wikipedia.org/wiki/One_Definition_Rule
 .. _One Definition Rule: https://en.wikipedia.org/wiki/One_Definition_Rule
 .. _DWZ: https://sourceware.org/dwz
diff --git a/doc/manuals/abidw.rst b/doc/manuals/abidw.rst
index 87db1890..93eced71 100644
--- a/doc/manuals/abidw.rst
+++ b/doc/manuals/abidw.rst
@@ -21,10 +21,10 @@  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 `DWARF`_ format, if present, otherwise it
-looks for debug information in `CTF`_ format, if present, finally, if
-neither is found, it uses only `ELF`_ symbols to report which of them
-were added or removed.
+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 report about their addition or removal.
 
 .. include:: tools-use-libabigail.txt
 
@@ -389,6 +389,7 @@  standard `here
 .. _GNU: http://www.gnu.org
 .. _Linux Kernel: https://kernel.org/
 .. _CTF: https://raw.githubusercontent.com/wiki/oracle/binutils-gdb/files/ctf-spec.pdf
+.. _BTF: https://docs.kernel.org/bpf/btf.html
 .. _ODR: https://en.wikipedia.org/wiki/One_Definition_Rule
 .. _One Definition Rule: https://en.wikipedia.org/wiki/One_Definition_Rule
 .. _DWZ: https://sourceware.org/dwz
diff --git a/doc/manuals/abipkgdiff.rst b/doc/manuals/abipkgdiff.rst
index 9d7a3973..d448ad3e 100644
--- a/doc/manuals/abipkgdiff.rst
+++ b/doc/manuals/abipkgdiff.rst
@@ -13,17 +13,17 @@  binaries.
 For a comprehensive ABI change report that includes changes about
 function and variable sub-types, the two input packages must be
 accompanied with their debug information packages that contain debug
-information either in `DWARF`_ or in `CTF`_ formats.  Please note
-however that some packages contain binaries that embed the debug
+information either in `DWARF`_, `CTF`_ or in `BTF`_ formats.  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.
 
 By default, ``abipkgdiff`` uses debug information in `DWARF`_ format,
 if present, otherwise it compares binaries interfaces using debug
-information in `CTF`_ format, if present, finally, if neither is
-found, it uses only `ELF`_ symbols to report which of them were added
-or removed.
+information in `CTF`_ or in `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.
 
 .. include:: tools-use-libabigail.txt
 
@@ -554,6 +554,11 @@  Options
      This is used to compare packages with `CTF`_ debug information,
      if present.
 
+  * ``--btf``
+
+     This is used to compare packages with `BTF`_ debug information,
+     if present.
+
 .. _abipkgdiff_return_value_label:
 
 Return value
@@ -573,6 +578,7 @@  In the later case, the value of the exit code is the same as for the
 .. _tar: https://en.wikipedia.org/wiki/Tar_%28computing%29
 .. _DWARF: http://www.dwarfstd.org
 .. _CTF: https://raw.githubusercontent.com/wiki/oracle/binutils-gdb/files/ctf-spec.pdf
+.. _BTF: https://docs.kernel.org/bpf/btf.html
 .. _Development Package: https://fedoraproject.org/wiki/Packaging:Guidelines?rd=Packaging/Guidelines#Devel_Packages
 .. _ODR: https://en.wikipedia.org/wiki/One_Definition_Rule
 .. _One Definition Rule: https://en.wikipedia.org/wiki/One_Definition_Rule
diff --git a/doc/manuals/kmidiff.rst b/doc/manuals/kmidiff.rst
index a27d2456..40358b92 100644
--- a/doc/manuals/kmidiff.rst
+++ b/doc/manuals/kmidiff.rst
@@ -74,10 +74,11 @@  functions and variables) between the Kernel and its modules.  In
 practice, though, some users might want to compare a subset of the
 those interfaces.
 
-By default, ``kmidiff`` uses debug information in `DWARF`_ format,
-if present, otherwise it compares interfaces using debug information
-in `CTF`_ format, if present, finally, if neither is found, it uses
-only `ELF`_ symbols to report which were added or removed.
+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 report
+about their addition or removal.
 
 Users can then define a "white list" of the interfaces to compare.
 Such a white list is a just a file in the "INI" format that looks
@@ -179,8 +180,13 @@  Options
 
   * ``--ctf``
 
-    Extract ABI information from `CTF`_ debug information, if present in
-    the Kernel and Modules.
+    Extract ABI information from `CTF`_ debug information, if present,
+    in the Kernel and Modules.
+
+  * ``--btf``
+
+    Extract ABI information from `BTF`_ debug information, if present,
+    in the Kernel and Modules.
 
   * ``--impacted-interfaces | -i``
 
@@ -249,3 +255,4 @@  Options
 .. _Linux Kernel: https://kernel.org
 .. _DWARF: http://www.dwarfstd.org
 .. _CTF: https://raw.githubusercontent.com/wiki/oracle/binutils-gdb/files/ctf-spec.pdf
+.. _BTF: https://docs.kernel.org/bpf/btf.html
diff --git a/include/Makefile.am b/include/Makefile.am
index 6b7f1e49..22f7151d 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -34,4 +34,8 @@  if CTF_READER
 pkginclude_HEADERS += abg-ctf-reader.h
 endif
 
+if BTF_READER
+pkginclude_HEADERS += abg-btf-reader.h
+endif
+
 EXTRA_DIST = abg-version.h.in
diff --git a/include/abg-btf-reader.h b/include/abg-btf-reader.h
new file mode 100644
index 00000000..c85ff9bd
--- /dev/null
+++ b/include/abg-btf-reader.h
@@ -0,0 +1,34 @@ 
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+// -*- Mode: C++ -*-
+//
+// Copyright (C) 2022 Red Hat, Inc.
+//
+// Author: Dodji Seketeli
+
+/// @file
+///
+/// This file contains the declarations of the front-end to analyze the
+/// BTF information contained in an ELF file.
+
+#ifndef __ABG_BTF_READER_H__
+#define __ABG_BTF_READER_H__
+
+#include "abg-elf-based-reader.h"
+
+namespace abigail
+{
+
+namespace btf
+{
+
+elf_based_reader_sptr
+create_reader(const std::string& elf_path,
+	      const vector<char**>& debug_info_root_paths,
+	      environment& env,
+	      bool load_all_types = false,
+	      bool linux_kernel_mode = false);
+
+}//end namespace btf
+}//end namespace abigail
+
+#endif //__ABG_BTF_READER_H__
diff --git a/include/abg-corpus.h b/include/abg-corpus.h
index 90419694..fabda0f9 100644
--- a/include/abg-corpus.h
+++ b/include/abg-corpus.h
@@ -48,7 +48,8 @@  public:
     ELF_ORIGIN        = 1 << 1,
     DWARF_ORIGIN      = 1 << 2,
     CTF_ORIGIN        = 1 << 3,
-    LINUX_KERNEL_BINARY_ORIGIN = 1 << 4
+    BTF_ORIGIN        = 1 << 4,
+    LINUX_KERNEL_BINARY_ORIGIN = 1 << 5
   };
 
 private:
diff --git a/include/abg-elf-reader.h b/include/abg-elf-reader.h
index 2d25b500..9e370c0b 100644
--- a/include/abg-elf-reader.h
+++ b/include/abg-elf-reader.h
@@ -97,6 +97,9 @@  class reader : public fe_iface
   bool
   has_ctf_debug_info() const;
 
+  bool
+  has_btf_debug_info() const;
+
   const Dwarf*
   alternate_dwarf_debug_info() const;
 
@@ -118,6 +121,9 @@  class reader : public fe_iface
   const Elf_Scn*
   find_alternate_ctf_section() const;
 
+  const Elf_Scn*
+  find_btf_section() const;
+
   const vector<string>&
   dt_needed()const;
 
diff --git a/include/abg-tools-utils.h b/include/abg-tools-utils.h
index ff7618fe..70745b0f 100644
--- a/include/abg-tools-utils.h
+++ b/include/abg-tools-utils.h
@@ -41,6 +41,8 @@  bool file_has_dwarf_debug_info(const string& elf_file_path,
 			       const vector<char**>& debug_info_root_paths);
 bool file_has_ctf_debug_info(const string& elf_file_path,
 			     const vector<char**>& debug_info_root_paths);
+bool file_has_btf_debug_info(const string& elf_file_path,
+			     const vector<char**>& debug_info_root_paths);
 bool is_dir(const string&);
 bool dir_exists(const string&);
 bool dir_is_empty(const string &);
diff --git a/src/Makefile.am b/src/Makefile.am
index 70cc04a5..3044c136 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -48,6 +48,10 @@  if CTF_READER
 libabigail_la_SOURCES += abg-ctf-reader.cc
 endif
 
+if BTF_READER
+libabigail_la_SOURCES += abg-btf-reader.cc
+endif
+
 libabigail_la_LIBADD = $(DEPS_LIBS) $(FTS_LIBS)
 libabigail_la_LDFLAGS = -lpthread -Wl,--as-needed -no-undefined -version-info $(LIBABIGAIL_SO_CURRENT):$(LIBABIGAIL_SO_REVISION):$(LIBABIGAIL_SO_AGE)
 
diff --git a/src/abg-btf-reader.cc b/src/abg-btf-reader.cc
new file mode 100644
index 00000000..b3bcfe2f
--- /dev/null
+++ b/src/abg-btf-reader.cc
@@ -0,0 +1,1102 @@ 
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+// -*- Mode: C++ -*-
+//
+// Copyright (C) 2022 Red Hat, Inc.
+//
+// Author: Dodji Seketeli
+
+/// @file
+///
+/// This file contains the definitions of the front-end to analyze the
+/// BTF information contained in an ELF file.
+
+#include "abg-internal.h"
+
+#ifdef WITH_BTF
+
+#include <bpf/btf.h>
+#include <iostream>
+#include <unordered_map>
+
+#include "abg-elf-helpers.h"
+
+// <headers defining libabigail's API go under here>
+ABG_BEGIN_EXPORT_DECLARATIONS
+
+#include "abg-btf-reader.h"
+#include "abg-ir.h"
+#include "abg-tools-utils.h"
+
+ABG_END_EXPORT_DECLARATIONS
+// </headers defining libabigail's API>
+
+namespace abigail
+{
+using namespace ir;
+
+namespace btf
+{
+
+class reader;
+
+/// A convenience typedef for a shared pointer to
+/// abigail::btf::reader.
+typedef shared_ptr<reader> reader_sptr;
+
+static const char*
+btf_offset_to_string(const ::btf* btf, uint32_t offset)
+{
+  if (!offset)
+    return "__anonymous__";
+  return btf__name_by_offset(btf, offset) ?: "(invalid string offset)";
+}
+
+/// A convenience typedef of a map that associates a btf type id to a
+/// libabigail ABI artifact.
+typedef std::unordered_map<int, type_or_decl_base_sptr>
+btf_type_id_to_abi_artifact_map_type;
+
+/// The BTF front-end abstraction type.
+class reader : public elf_based_reader
+{
+  ::btf*				btf_handle_ = nullptr;
+  translation_unit_sptr		cur_tu_;
+  vector<type_base_sptr>		types_to_canonicalize_;
+  btf_type_id_to_abi_artifact_map_type	btf_type_id_to_artifacts_;
+
+  /// Getter of the handle to the BTF data as returned by libbpf.
+  ///
+  /// @return the handle to the BTF data as returned by libbpf.
+  ::btf*
+  btf_handle()
+  {
+    if (btf_handle_ == nullptr)
+      {
+	btf_handle_ = btf__parse(corpus_path().c_str(), nullptr);
+	if (!btf_handle_)
+	  std::cerr << "Could not parse BTF information from file '"
+		    << corpus_path().c_str() << "'" << std::endl;
+      }
+    return btf_handle_;
+  }
+
+  /// Getter of the environment of the current front-end.
+  ///
+  /// @return The environment of the current front-end.
+  environment&
+  env()
+  {return options().env;}
+
+  /// Getter of the environment of the current front-end.
+  ///
+  /// @return The environment of the current front-end.
+  const environment&
+  env() const
+  {return const_cast<reader*>(this)->env();}
+
+  /// Getter of the current translation unit being built.
+  ///
+  /// Actually, BTF doesn't keep track of the translation unit each
+  /// ABI artifact originates from.  So an "artificial" translation
+  /// unit is built.  It contains all the ABI artifacts of the binary.
+  ///
+  /// @return The current translation unit being built.
+  translation_unit_sptr&
+  cur_tu()
+  {return cur_tu_;}
+
+  /// Getter of the current translation unit being built.
+  ///
+  /// Actually, BTF doesn't keep track of the translation unit each
+  /// ABI artifact originates from.  So an "artificial" translation
+  /// unit is built.  It contains all the ABI artifacts of the binary.
+  ///
+  /// @return The current translation unit being built.
+  const translation_unit_sptr&
+  cur_tu() const
+  {return cur_tu_;}
+
+  /// Getter of the current translation unit being built.
+  ///
+  /// Actually, BTF doesn't keep track of the translation unit each
+  /// ABI artifact originates from.  So an "artificial" translation
+  /// unit is built.  It contains all the ABI artifacts of the binary.
+  ///
+  /// @return The current translation unit being built.
+  void
+  cur_tu(const translation_unit_sptr& tu)
+  {cur_tu_ = tu;}
+
+  /// Getter of the map that associates a BTF type ID to an ABI
+  /// artifact.
+  ///
+  /// @return The map that associates a BTF type ID to an ABI
+  /// artifact.
+  btf_type_id_to_abi_artifact_map_type&
+  btf_type_id_to_artifacts()
+  {return btf_type_id_to_artifacts_;}
+
+  /// Getter of the map that associates a BTF type ID to an ABI
+  /// artifact.
+  ///
+  /// @return The map that associates a BTF type ID to an ABI
+  /// artifact.
+  const btf_type_id_to_abi_artifact_map_type&
+  btf_type_id_to_artifacts() const
+  {return btf_type_id_to_artifacts_;}
+
+  /// Get the ABI artifact that is associated to a given BTF type ID.
+  ///
+  /// If no ABI artifact is associated to the BTF type id, then return
+  /// nil.
+  ///
+  /// @return the ABI artifact that is associated to a given BTF type
+  /// id.
+  type_or_decl_base_sptr
+  lookup_artifact_from_btf_id(int btf_id)
+  {
+    auto i = btf_type_id_to_artifacts().find(btf_id);
+    if (i != btf_type_id_to_artifacts().end())
+      return i->second;
+    return type_or_decl_base_sptr();
+  }
+
+  /// Associate an ABI artifact to a given BTF type ID.
+  ///
+  /// @param artifact the ABI artifact to consider.
+  ///
+  /// @param btf_type_id the BTF type ID to associate to @p artifact.
+  void
+  associate_artifact_to_btf_type_id(const type_or_decl_base_sptr& artifact,
+				    int btf_type_id)
+  {btf_type_id_to_artifacts()[btf_type_id] = artifact;}
+
+  /// Schecule a type for canonicalization at the end of the debug
+  /// info loading.
+  ///
+  /// @param t the type to schedule.
+  void
+  schedule_type_for_canonocalization(const type_base_sptr& t)
+  {types_to_canonicalize_.push_back(t);}
+
+  /// Canonicalize all the types scheduled for canonicalization using
+  /// schedule_type_for_canonocalization().
+  void
+  canonicalize_types()
+  {
+    for (auto t : types_to_canonicalize_)
+      canonicalize(t);
+  }
+
+  uint64_t
+  nr_btf_types() const
+  {
+#ifdef WITH_BTF__GET_NR_TYPES
+#define GET_NB_TYPES btf__get_nr_types
+#endif
+
+#ifdef WITH_BTF__TYPE_CNT
+#undef GET_NB_TYPES
+#define GET_NB_TYPES btf__type_cnt
+#endif
+
+#ifndef GET_NB_TYPES
+    ABG_ASSERT_NOT_REACHED;
+    return 0;
+#endif
+
+    return GET_NB_TYPES(const_cast<reader*>(this)->btf_handle());
+  }
+
+protected:
+  reader() = delete;
+
+  /// Initializer of the current instance of @ref btf::reader.
+  ///
+  /// This frees the resources used by the current instance of @ref
+  /// btf::reader and gets it ready to analyze another ELF
+  /// file.
+  ///
+  /// @param elf_path the path to the ELF file to read from.
+  ///
+  /// @param debug_info_root_paths the paths where to look for
+  /// seperate debug info.
+  ///
+  /// @param load_all_types if true, then load all the types described
+  /// in the binary, rather than loading only the types reachable from
+  /// the exported decls.
+  ///
+  /// @param linux_kernel_mode
+  void
+  initialize(const string&		elf_path,
+	     const vector<char**>&	debug_info_root_paths,
+	     bool			load_all_types,
+	     bool			linux_kernel_mode)
+  {
+    reset(elf_path, debug_info_root_paths);
+    btf__free(btf_handle_);
+    options().load_all_types = load_all_types;
+    options().load_in_linux_kernel_mode = linux_kernel_mode;
+  }
+
+  /// Constructor of the btf::reader type.
+  ///
+  /// @param elf_path the path to the ELF file to analyze.
+  ///
+  /// @param debug_info_root_paths the set of directory where to look
+  /// debug info from, for cases where the debug is split.
+  ///
+  /// @param environment the environment of the current front-end.
+  ///
+  /// @param load_all_types if true load all the types described by
+  /// the BTF debug info, as opposed to loading only the types
+  /// reachable from the decls that are defined and exported.
+  ///
+  /// @param linux_kernel_mode if true, then consider the binary being
+  /// analyzed as a linux kernel binary.
+  reader(const string&		elf_path,
+	 const vector<char**>&	debug_info_root_paths,
+	 environment&		environment,
+	 bool			load_all_types,
+	 bool			linux_kernel_mode)
+    : elf_based_reader(elf_path,
+		       debug_info_root_paths,
+		       environment)
+  {
+    initialize(elf_path, debug_info_root_paths,
+	       load_all_types, linux_kernel_mode);
+  }
+
+public:
+
+  /// Constructor of the btf::reader type.
+  ///
+  /// @param elf_path the path to the ELF file to analyze.
+  ///
+  /// @param debug_info_root_paths the set of directory where to look
+  /// debug info from, for cases where the debug is split.
+  ///
+  /// @param environment the environment of the current front-end.
+  ///
+  /// @param load_all_types if true load all the types described by
+  /// the BTF debug info, as opposed to loading only the types
+  /// reachable from the decls that are defined and exported.
+  ///
+  /// @param linux_kernel_mode if true, then consider the binary being
+  /// analyzed as a linux kernel binary.
+  static btf::reader_sptr
+  create(const string&		elf_path,
+	 const vector<char**>&	debug_info_root_paths,
+	 environment&		environment,
+	 bool			load_all_types,
+	 bool			linux_kernel_mode)
+  {
+    reader_sptr result(new reader(elf_path, debug_info_root_paths, environment,
+				  load_all_types, linux_kernel_mode));
+    return result;
+  }
+
+  /// Destructor of the btf::reader type.
+  ~reader()
+  {
+    btf__free(btf_handle_);
+  }
+
+  /// Read the ELF information as well as the BTF type information to
+  /// build an ABI corpus.
+  ///
+  /// @param status output parameter.  The status of the analysis.
+  ///
+  /// @return the resulting ABI corpus.
+  corpus_sptr
+  read_corpus(status& status)
+  {
+    // Read the properties of the ELF file.
+    elf::reader::read_corpus(status);
+
+    corpus::origin origin = corpus()->get_origin();
+    origin |= corpus::BTF_ORIGIN;
+    corpus()->set_origin(origin);
+
+    if ((status & STATUS_NO_SYMBOLS_FOUND)
+	|| !(status & STATUS_OK))
+      // Either we couldn't find ELF symbols or something went badly
+      // wrong.  There is nothing we can do with this ELF file.  Bail
+      // out.
+      return corpus_sptr();
+
+    if (find_btf_section() == nullptr)
+      status |= STATUS_DEBUG_INFO_NOT_FOUND;
+
+    read_debug_info_into_corpus();
+
+    status |= STATUS_OK;
+
+    return corpus();
+  }
+
+  /// Read the BTF debug info to construct the ABI corpus.
+  ///
+  /// @return the resulting ABI corpus.
+  corpus_sptr
+  read_debug_info_into_corpus()
+  {
+    btf_handle();
+
+    translation_unit_sptr artificial_tu
+      (new translation_unit(env(), "", /*address_size=*/64));
+    corpus()->add(artificial_tu);
+    cur_tu(artificial_tu);
+
+    int number_of_types = nr_btf_types();
+    int first_type_id = 1;
+
+    // Let's cycle through whatever is described in the BTF section
+    // and emit libabigail IR for it.
+    for (int type_id = first_type_id;
+	 type_id < number_of_types;
+	 ++type_id)
+      {
+	// Build IR nodes only for decls (functions and variables)
+	// that have associated ELF symbols that are publicly defined
+	// and exported, unless the user asked to load all types.
+
+	bool do_construct_ir_node = false;
+
+	const btf_type* t = btf__type_by_id(btf_handle(), type_id);
+	string name;
+	if (t->name_off)
+	  name = btf_offset_to_string(btf_handle(), t->name_off);
+
+	int kind = btf_kind(t);
+	if (kind == BTF_KIND_FUNC)
+	  {
+	    ABG_ASSERT(!name.empty());
+	    if (btf_vlen(t) == BTF_FUNC_GLOBAL
+		|| btf_vlen(t) == BTF_FUNC_EXTERN
+		|| function_symbol_is_exported(name))
+	      do_construct_ir_node = true;
+	  }
+	else if (kind == BTF_KIND_VAR)
+	  {
+	    ABG_ASSERT(!name.empty());
+	    if (btf_vlen(t) == BTF_VAR_GLOBAL_ALLOCATED
+		|| btf_vlen(t) == BTF_VAR_GLOBAL_EXTERN
+		|| variable_symbol_is_exported(name))
+	      do_construct_ir_node = true;
+	  }
+	else if (options().load_all_types)
+	  do_construct_ir_node = true;
+
+	if (do_construct_ir_node)
+	  build_ir_node_from_btf_type(type_id);
+      }
+
+    canonicalize_types();
+
+    return corpus();
+  }
+
+  /// Build an abigail IR node for a given type described by a BTF
+  /// type ID.  The node is added to the ABI corpus.
+  ///
+  /// @param type_id the ID of the type to build and IR node for.
+  ///
+  /// @return the IR node representing the type @p type_id.
+  type_or_decl_base_sptr
+  build_ir_node_from_btf_type(int type_id)
+  {
+    type_or_decl_base_sptr result;
+    const btf_type *t = nullptr;
+
+    if ((result = lookup_artifact_from_btf_id(type_id)))
+      return result;
+
+    if (type_id == 0)
+      result = build_ir_node_for_void_type();
+    else
+      t = btf__type_by_id(btf_handle(), type_id);
+
+    if (!result)
+      {
+	ABG_ASSERT(t);
+	int type_kind = btf_kind(t);
+
+	switch(type_kind)
+	  {
+	  case BTF_KIND_INT/* Integer */:
+	    result = build_int_type(type_id);
+	    break;
+
+	  case BTF_KIND_FLOAT/* Floating point */:
+	    result = build_float_type(type_id);
+	    break;
+
+	  case BTF_KIND_TYPEDEF/* Typedef*/:
+	    result = build_typedef_type(type_id);
+	    break;
+
+	  case BTF_KIND_PTR/* Pointer */:
+	    result = build_pointer_type(type_id);
+	    break;
+
+	  case BTF_KIND_ARRAY/* Array */:
+	    result = build_array_type(type_id);
+	    break;
+
+	  case BTF_KIND_ENUM/* Enumeration up to 32-bit values */:
+#ifdef WITH_BTF_ENUM64
+	  case BTF_KIND_ENUM64/* Enumeration up to 64-bit values */:
+#endif
+	    result = build_enum_type(type_id);
+	    break;
+
+	  case BTF_KIND_STRUCT/* Struct */:
+	  case BTF_KIND_UNION/* Union */:
+	    result = build_class_or_union_type(type_id);
+	    break;
+
+	  case BTF_KIND_FWD/* Forward */:
+	    result = build_class_or_union_type(type_id);
+	    break;
+
+	  case BTF_KIND_CONST/* Const	*/:
+	  case BTF_KIND_VOLATILE/* Volatile */:
+	  case BTF_KIND_RESTRICT/* Restrict */:
+	    result = build_qualified_type(type_id);
+	    break;
+
+	  case BTF_KIND_FUNC/* Function */:
+	    result = build_function_decl(type_id);
+	    break;
+
+	  case BTF_KIND_FUNC_PROTO/* Function Proto */:
+	    result = build_function_type(type_id);
+	    break;
+
+	  case BTF_KIND_VAR/* Variable */:
+	    result = build_variable_decl(type_id);
+	    break;
+
+#ifdef WITH_BTF_KIND_TYPE_TAG
+	  case BTF_KIND_TYPE_TAG/* Type Tag */:
+#endif
+#ifdef WITH_BTF_KIND_DECL_TAG
+	  case BTF_KIND_DECL_TAG/* Decl Tag */:
+#endif
+	  case BTF_KIND_DATASEC/* Section */:
+	  case BTF_KIND_UNKN/* Unknown	*/:
+	  default:
+	    ABG_ASSERT_NOT_REACHED;
+	    break;
+	  }
+      }
+
+    add_decl_to_scope(is_decl(result), cur_tu()->get_global_scope());
+
+    if (type_base_sptr type = is_type(result))
+      schedule_type_for_canonocalization(type);
+
+    associate_artifact_to_btf_type_id(result, type_id);
+
+    if (function_decl_sptr fn = is_function_decl(result))
+      {
+	if (fn->get_is_in_public_symbol_table())
+	  maybe_add_fn_to_exported_decls(fn.get());
+      }
+    else if (var_decl_sptr var = is_var_decl(result))
+      {
+	if (var->get_is_in_public_symbol_table())
+	  maybe_add_var_to_exported_decls(var.get());
+      }
+
+    return result;
+  }
+
+  /// Build an IR node for the "void" type.
+  ///
+  /// @return the IR node for the void type.
+  type_base_sptr
+  build_ir_node_for_void_type()
+  {
+    type_base_sptr t = env().get_void_type();
+    decl_base_sptr type_declaration = get_type_declaration(t);
+    if (!has_scope(type_declaration))
+      {
+	add_decl_to_scope(type_declaration, cur_tu()->get_global_scope());
+	canonicalize(t);
+      }
+
+    return t;
+  }
+
+  /// Build an IR node for the "variadic parameter" type.
+  ///
+  /// @return the IR node for the "variadic parameter" type.
+  type_base_sptr
+  build_ir_node_for_variadic_parameter_type()
+  {
+    type_base_sptr t = env().get_variadic_parameter_type();
+    decl_base_sptr t_decl = get_type_declaration(t);
+    if (!has_scope(t_decl))
+      {
+	add_decl_to_scope(t_decl, cur_tu()->get_global_scope());
+	canonicalize(t);
+      }
+    return t;
+  }
+
+  /// Build an IR node for an integer type expressed in BTF.
+  ///
+  /// @param t a pointer a BTF type describing an integer.
+  ///
+  /// @return a pointer to @ref type_decl representing an integer
+  /// type.
+  type_or_decl_base_sptr
+  build_int_type(int type_id)
+  {
+    type_decl_sptr result;
+
+    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
+    ABG_ASSERT(btf_kind(t) == BTF_KIND_INT);
+
+    uint32_t info = *reinterpret_cast<const uint32_t*>(t + 1);
+    uint64_t byte_size = 0, bit_size = 0;
+    string type_name;
+
+    byte_size = t->size;
+    bit_size = byte_size * 8;
+
+    if (BTF_INT_ENCODING(info) & BTF_INT_CHAR)
+      {
+	if (!(BTF_INT_ENCODING(info) & BTF_INT_SIGNED))
+	  type_name = "unsigned ";
+	type_name += "char";
+      }
+    else if (BTF_INT_ENCODING(info) & BTF_INT_BOOL)
+      type_name = "bool";
+    else if (!(BTF_INT_ENCODING(info) & BTF_INT_SIGNED))
+      {
+	type_name = "unsigned ";
+	type_name += btf_offset_to_string(btf_handle(), t->name_off);
+      }
+    else
+      type_name = btf_offset_to_string(btf_handle(), t->name_off);
+
+    location loc;
+    result.reset(new type_decl(env(), type_name,
+			       bit_size, /*alignment=*/0,
+			       loc, type_name));
+
+    return result;
+  }
+
+  /// Build an IR node for a float type expressed in BTF.
+  ///
+  /// @return a pointer to @ref type_decl representing a float type.
+  type_or_decl_base_sptr
+  build_float_type(int type_id)
+  {
+    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
+    ABG_ASSERT(btf_kind(t) == BTF_KIND_FLOAT);
+
+    string type_name = btf_offset_to_string(btf_handle(), t->name_off);;
+    uint64_t byte_size = t->size, bit_size = byte_size * 8;
+    location loc;
+    type_decl_sptr result(new type_decl(env(), type_name, bit_size,
+					/*alignment=*/0, loc, type_name));
+
+    return result;
+  }
+
+  /// Build an IR type that represents the underlying type of an enum type.
+  ///
+  /// This is a sub-routine of the build_enum_type() function.
+  ///
+  /// @param enum_name the name of the enum type this type is an
+  /// underlying type for.
+  ///
+  /// @param enum_size the size of the enum.
+  ///
+  /// @param is_anonymous if true, the enum type is anonymous.
+  ///
+  /// @return a pointer to type_decl that represents a integer type
+  /// that is the underlying type of an enum type.
+  type_decl_sptr
+  build_enum_underlying_type(const string enum_name, uint64_t enum_size,
+			     bool is_anonymous = true)
+  {
+    string underlying_type_name =
+      build_internal_underlying_enum_type_name(enum_name,
+					       is_anonymous,
+					       enum_size);
+    type_decl_sptr result(new type_decl(env(), underlying_type_name,
+					enum_size, enum_size, location()));
+    result->set_is_anonymous(is_anonymous);
+    result->set_is_artificial(true);
+    add_decl_to_scope(result, cur_tu()->get_global_scope());
+    canonicalize(result);
+    return result;
+  }
+
+  /// Build an IR node that represents an enum type expressed in BTF.
+  ///
+  /// @param type_id the ID of the BTF representation of the enum.
+  ///
+  /// @return a pointer to @ref enum_type_decl representing @p t.
+  type_or_decl_base_sptr
+  build_enum_type(int type_id)
+  {
+    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
+    int kind = btf_kind(t);
+#ifdef WITH_BTF_ENUM64
+    ABG_ASSERT(kind == BTF_KIND_ENUM || kind == BTF_KIND_ENUM64);
+#else
+    ABG_ASSERT(kind == BTF_KIND_ENUM);
+#endif
+
+    int byte_size = t->size, bit_size = byte_size * 8;
+
+    string enum_name;
+    if (t->name_off)
+      enum_name = btf_offset_to_string(btf_handle(), t->name_off);
+    bool is_anonymous = enum_name.empty();
+
+    int num_enms = btf_vlen(t);
+    enum_type_decl::enumerators enms;
+    string e_name;
+    if (kind == BTF_KIND_ENUM)
+      {
+	const struct btf_enum* e = btf_enum(t);
+	uint32_t e_value = 0;
+	for (int i = 0; i < num_enms; ++i, ++e)
+	  {
+	    e_name = btf_offset_to_string(btf_handle(), e->name_off);
+	    e_value = e->val;
+	    enms.push_back(enum_type_decl::enumerator(e_name, e_value));
+	  }
+      }
+#ifdef WITH_BTF_ENUM64
+    else if (kind == BTF_KIND_ENUM64)
+      {
+	const struct btf_enum64* e =
+	  reinterpret_cast<const struct btf_enum64*>(t + 1);
+	uint64_t e_value = 0;
+	for (int i = 0; i < num_enms; ++i, ++e)
+	  {
+	    e_name = btf_offset_to_string(btf_handle(), e->name_off);
+	    e_value = (static_cast<uint64_t>(e->val_hi32) << 32) | e->val_lo32;
+	    enms.push_back(enum_type_decl::enumerator(e_name, e_value));
+	  }
+      }
+#endif
+    else
+      ABG_ASSERT_NOT_REACHED;
+
+    type_decl_sptr underlying_type =
+      build_enum_underlying_type(enum_name, bit_size, is_anonymous);
+    enum_type_decl_sptr result(new enum_type_decl(enum_name,
+						  location(),
+						  underlying_type,
+						  enms, enum_name));
+    result->set_is_anonymous(is_anonymous);
+    return result;
+  }
+
+  /// Build an IR node for a typedef that is expressed in BTF.
+  ///
+  /// @param type_id the ID of the BTF representation of a typedef.
+  ///
+  /// @return a pointer to @ref typedef_decl representing @p t.
+  type_or_decl_base_sptr
+  build_typedef_type(int type_id)
+  {
+    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
+    int kind = btf_kind(t);
+    ABG_ASSERT(kind == BTF_KIND_TYPEDEF);
+
+    string type_name = btf_offset_to_string(btf_handle(), t->name_off);
+    type_base_sptr underlying_type =
+      is_type(build_ir_node_from_btf_type(t->type));
+    if (!underlying_type)
+      return type_or_decl_base_sptr();
+
+    typedef_decl_sptr result(new typedef_decl(type_name, underlying_type,
+					      location(),
+					      /*linkage_name=*/type_name));
+    if ((is_class_or_union_type(underlying_type)
+	 || is_enum_type(underlying_type))
+	&& is_anonymous_type(underlying_type))
+      get_type_declaration(underlying_type)->set_naming_typedef(result);
+
+    return result;
+  }
+
+  /// Build an IR node representing a pointer described in BTF.
+  ///
+  /// @param type_id the ID of a BTF representation of a pointer type.
+  ///
+  /// @return a pointer to pointer_type_def that represents @p t.
+  type_or_decl_base_sptr
+  build_pointer_type(int type_id)
+  {
+    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
+    int kind = btf_kind(t);
+    ABG_ASSERT(kind == BTF_KIND_PTR);
+
+    type_base_sptr underlying_type =
+      is_type(build_ir_node_from_btf_type(t->type));
+    if (!underlying_type)
+      return type_or_decl_base_sptr();
+
+    int size = elf_helpers::get_architecture_word_size(elf_handle());
+    size *= 8;
+    pointer_type_def_sptr result(new pointer_type_def(underlying_type, size,
+						      /*alignment=*/0,
+						      location()));
+    return result;
+  }
+
+  /// Build an IR node representing an array type described in BTF.
+  ///
+  /// @param type_id the ID of the BTF representation of an array
+  /// type.
+  ///
+  /// return a pointer to @ref array_type_def representing @p t.
+  type_or_decl_base_sptr
+  build_array_type(int type_id)
+  {
+    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
+    int kind = btf_kind(t);
+    ABG_ASSERT(kind == BTF_KIND_ARRAY);
+
+    const struct btf_array* arr = btf_array(t);
+
+    type_base_sptr underlying_type =
+      is_type(build_ir_node_from_btf_type(arr->type));
+    if (!underlying_type)
+      return type_or_decl_base_sptr();
+
+    uint64_t lower_boud = 0;
+    // Note that arr->nelems can be 0;
+    uint64_t upper_bound = arr->nelems ? arr->nelems - 1: 0;
+
+    array_type_def::subrange_sptr subrange(new array_type_def::subrange_type
+					   (env(), /*name=*/"",
+					    lower_boud, upper_bound,
+					    location()));
+    add_decl_to_scope(subrange, cur_tu()->get_global_scope());
+    canonicalize(subrange);
+    array_type_def::subranges_type subranges = {subrange};
+    array_type_def_sptr result(new array_type_def(underlying_type,
+						  subranges, location()));
+
+    return result;
+  }
+
+  /// Build an IR node representing a qualified type described in BTF.
+  ///
+  /// @param type_id the ID of the BTF representation of an array
+  /// type.
+  ///
+  /// @return a pointer to a qualified_type_def representing @ t.
+  type_or_decl_base_sptr
+  build_qualified_type(int type_id)
+  {
+    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
+    int kind = btf_kind(t);
+    ABG_ASSERT(kind == BTF_KIND_CONST
+	       || kind == BTF_KIND_VOLATILE
+	       || kind == BTF_KIND_RESTRICT);
+
+    type_base_sptr underlying_type =
+      is_type(build_ir_node_from_btf_type(t->type));
+    if (!underlying_type)
+      return type_or_decl_base_sptr();
+
+    qualified_type_def::CV qual = qualified_type_def::CV_NONE;
+    if (kind == BTF_KIND_CONST)
+      qual |= qualified_type_def::CV_CONST;
+    else if (kind == BTF_KIND_VOLATILE)
+      qual |= qualified_type_def::CV_VOLATILE;
+    else if (kind == BTF_KIND_RESTRICT)
+      qual |= qualified_type_def::CV_RESTRICT;
+    else
+      ABG_ASSERT_NOT_REACHED;
+
+    qualified_type_def_sptr result(new qualified_type_def(underlying_type,
+							  qual, location()));
+    return result;
+  }
+
+  /// Build an IR node for a class or union type expressed in BTF.
+  ///
+  /// @param type_id the ID of a pointer to a BTF type describing a
+  /// class or union type.
+  ///
+  /// @return a pointer to either a @ref class_decl or a @ref
+  /// union_decl type representing the type expressed by @p t.
+  type_or_decl_base_sptr
+  build_class_or_union_type(int type_id)
+  {
+    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
+
+    int kind = btf_kind(t);
+    ABG_ASSERT(kind == BTF_KIND_STRUCT
+	       || kind == BTF_KIND_UNION
+	       || kind == BTF_KIND_FWD);
+
+    string type_name;
+    if (t->name_off)
+      type_name = btf_offset_to_string(btf_handle(), t->name_off);
+
+    bool is_anonymous = type_name.empty();
+    uint64_t size = t->size;
+    size *= 8;
+
+    bool is_decl_only = (kind == BTF_KIND_FWD);
+
+    class_or_union_sptr result;
+    if (kind == BTF_KIND_STRUCT
+	|| (kind == BTF_KIND_FWD
+	    && BTF_INFO_KFLAG(t->info) == 0 /*struct*/))
+    result.reset(new class_decl(env(), type_name, size,
+				/*alignment=*/0,
+				/*is_struct=*/true,
+				location(),
+				decl_base::VISIBILITY_DEFAULT,
+				is_anonymous));
+    else if (kind == BTF_KIND_UNION
+	     || (kind == BTF_KIND_FWD
+		 && BTF_INFO_KFLAG(t->info) == 1/*union*/))
+      result.reset(new union_decl(env(), type_name, size, location(),
+				  decl_base::VISIBILITY_DEFAULT,
+				  is_anonymous));
+    else
+      ABG_ASSERT_NOT_REACHED;
+
+    if (is_decl_only)
+      result->set_is_declaration_only(is_decl_only);
+
+    add_decl_to_scope(result, cur_tu()->get_global_scope());
+
+    associate_artifact_to_btf_type_id(result, type_id);
+
+    // For defined classes and unions, add data members to the type
+    // being built.
+    if (!is_decl_only)
+      {
+	const struct btf_member *m =
+	  reinterpret_cast<const struct btf_member*>(t + 1);
+	uint64_t nb_members = btf_vlen(t);
+
+	for (uint64_t i = 0; i < nb_members; ++i, ++m)
+	  {
+	    type_base_sptr member_type =
+	      is_type(build_ir_node_from_btf_type(m->type));
+	    if (!member_type)
+	      continue;
+
+	    string member_name;
+	    if (m->name_off)
+	      member_name = btf_offset_to_string(btf_handle(), m->name_off);
+	    var_decl_sptr data_member(new var_decl(member_name,
+						   member_type,
+						   location(),
+						   /*linkage_name=*/""));
+	    uint64_t offset_in_bits =
+	      BTF_INFO_KFLAG(t->info)
+	      ? BTF_MEMBER_BIT_OFFSET(m->offset)
+	      : m->offset;
+
+	    result->add_data_member(data_member,
+				    public_access,
+				    /*is_laid_out=*/true,
+				    /*is_static=*/false,
+				    offset_in_bits);
+	  }
+      }
+    return result;
+  }
+
+  /// Build an IR node for a function type expressed in BTF.
+  ///
+  /// @param type_id the ID of a pointer to a BTF type describing a
+  /// function type.
+  ///
+  /// @return a pointer to a @ref function_type representing the
+  /// function type expressed by @p t.
+  type_or_decl_base_sptr
+  build_function_type(int type_id)
+  {
+    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
+    int kind = btf_kind(t);
+    ABG_ASSERT(kind == BTF_KIND_FUNC_PROTO);
+
+    type_base_sptr return_type = is_type(build_ir_node_from_btf_type(t->type));
+    if (return_type == nullptr)
+      return type_or_decl_base_sptr();
+
+    int address_size = elf_helpers::get_architecture_word_size(elf_handle());
+    address_size *= 8;
+    function_type_sptr result(new function_type(env(), address_size,
+						/*alignment=*/0));
+    result->set_return_type(return_type);
+
+    associate_artifact_to_btf_type_id(result, type_id);
+
+    uint16_t nb_parms = btf_vlen(t);
+    const struct btf_param* parm =
+      reinterpret_cast<const struct btf_param*>(t + 1);
+
+    function_decl::parameters function_parms;
+    for (uint16_t i = 0; i < nb_parms; ++i, ++parm)
+      {
+	type_base_sptr parm_type;
+	string parm_name;
+	bool is_variadic = false;
+
+	if (parm->name_off == 0 && parm->type == 0)
+	  {
+	    is_variadic = true;
+	    parm_type = build_ir_node_for_variadic_parameter_type();
+	  }
+	else
+	  {
+	    parm_name = btf_offset_to_string(btf_handle(), parm->name_off);
+	    parm_type = is_type(build_ir_node_from_btf_type(parm->type));
+	  }
+
+	if (!parm_type)
+	  continue;
+
+	function_decl::parameter_sptr p
+	  (new function_decl::parameter(parm_type, parm_name,
+					location(), is_variadic));
+	function_parms.push_back(p);
+      }
+    result->set_parameters(function_parms);
+
+    cur_tu()->bind_function_type_life_time(result);
+
+    return result;
+  }
+
+  /// Build an IR node for a function declaration expressed in BTF.
+  ///
+  /// @param type_id the ID of a pointer to a BTF "type" which realy
+  /// describes a function declaration.
+  ///
+  /// @return a pointer to a @ref function_decl representing the
+  /// function declaration expressed by @p t.
+  type_or_decl_base_sptr
+  build_function_decl(int type_id)
+  {
+    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
+    int kind = btf_kind(t);
+    ABG_ASSERT(kind == BTF_KIND_FUNC);
+
+    function_decl_sptr result;
+
+    string fn_name = btf_offset_to_string(btf_handle(), t->name_off);
+
+    type_base_sptr fn_type = is_type(build_ir_node_from_btf_type(t->type));
+    if (!fn_type)
+      return result;
+
+    result.reset(new function_decl(fn_name, fn_type, /*is_inline=*/false,
+				   location(), /*linkage_name=*/fn_name));
+
+    elf_symbol_sptr fn_sym;
+    if ((fn_sym = function_symbol_is_exported(fn_name)))
+      {
+	result->set_symbol(fn_sym);
+	result->set_is_in_public_symbol_table(true);
+      }
+    return result;
+  }
+
+  /// Build an IR node for a variable declaration expressed in BTF.
+  ///
+  /// @param t a pointer to a BTF "type" describing a variable
+  /// declaration.
+  ///
+  /// @return a pointer to @ref var_decl representing the variable
+  /// declaration expressed by @p t.
+  type_or_decl_base_sptr
+  build_variable_decl(int type_id)
+  {
+    const btf_type *t = btf__type_by_id(btf_handle(), type_id);
+    int kind = btf_kind(t);
+    ABG_ASSERT(kind == BTF_KIND_VAR);
+
+    var_decl_sptr result;
+
+    string var_name = btf_offset_to_string(btf_handle(), t->name_off);
+
+    type_base_sptr var_type = is_type(build_ir_node_from_btf_type(t->type));
+    if (!var_type)
+      return result;
+
+    result.reset(new var_decl(var_name, var_type, location(),
+			      /*linkage_name=*/var_name));
+
+    elf_symbol_sptr var_sym;
+    if ((var_sym = variable_symbol_is_exported(var_name)))
+      {
+	result->set_symbol(var_sym);
+	result->set_is_in_public_symbol_table(true);
+      }
+    return result;
+  }
+
+}; // end class reader.
+
+/// Create and return a BTF reader (or front-end) which is an instance
+/// of @ref btf::reader.
+///
+/// @param elf_path the path to the path to the elf file the reader is
+/// to be used for.
+///
+/// @param debug_info_root_paths a vector to the paths to the
+/// directories under which the debug info is to be found for @p
+/// elf_path.  Pass an empty vector if th debug info is not in a split
+/// file.
+///
+/// @param environment the environment used by the current context.
+/// This environment contains resources needed by the BTF reader and
+/// by the types and declarations that are to be created later.  Note
+/// that ABI artifacts that are to be compared all need to be created
+/// within the same environment.
+///
+/// Please also note that the life time of this environment object
+/// must be greater than the life time of the resulting @ref
+/// reader the context uses resources that are allocated in the
+/// environment.
+///
+/// @param load_all_types if set to false only the types that are
+/// reachable from publicly exported declarations (of functions and
+/// variables) are read.  If set to true then all types found in the
+/// debug information are loaded.
+///
+/// @param linux_kernel_mode if set to true, then consider the special
+/// linux kernel symbol tables when determining if a symbol is
+/// exported or not.
+///
+/// @return a smart pointer to the resulting btf::reader.
+elf_based_reader_sptr
+create_reader(const std::string&	elf_path,
+	      const vector<char**>&	debug_info_root_paths,
+	      environment&		env,
+	      bool			load_all_types,
+	      bool			linux_kernel_mode)
+{
+  reader_sptr rdr = reader::create(elf_path, debug_info_root_paths, env,
+				   load_all_types, linux_kernel_mode);
+  return rdr;
+}
+
+} // end namespace btf
+} // end namespace abigail
+
+#endif //WITH_BTF
diff --git a/src/abg-elf-reader.cc b/src/abg-elf-reader.cc
index 1f704e3f..57e7f2dc 100644
--- a/src/abg-elf-reader.cc
+++ b/src/abg-elf-reader.cc
@@ -274,6 +274,7 @@  struct reader::priv
   int					alt_ctf_fd		= 0;
   Elf*					alt_ctf_handle		= nullptr;
   Elf_Scn*				alt_ctf_section	= nullptr;
+  Elf_Scn*				btf_section		= nullptr;
 
   priv(reader& reeder, const std::string& elf_path,
        const vector<char**>& debug_info_roots)
@@ -602,6 +603,13 @@  bool
 reader::has_ctf_debug_info() const
 {return (priv_->ctf_section != nullptr);}
 
+/// Test if the binary has BTF debug info.
+///
+/// @return true iff the binary has BTF debug info
+bool
+reader::has_btf_debug_info() const
+{return (priv_->btf_section != nullptr);}
+
 /// Getter of the handle use to access DWARF information from the
 /// alternate split DWARF information.
 ///
@@ -697,6 +705,20 @@  reader::find_alternate_ctf_section() const
   return priv_->alt_ctf_section;
 }
 
+/// Find and return a pointer to the BTF section of the current ELF
+/// file.
+///
+/// @return a pointer to the BTF section of the current ELF file.
+const Elf_Scn*
+reader::find_btf_section() const
+{
+  if (priv_->btf_section == nullptr)
+    priv_->btf_section =
+      elf_helpers::find_section(priv_->elf_handle,
+				".BTF", SHT_PROGBITS);
+  return priv_->btf_section;
+}
+
 /// Get the value of the DT_NEEDED property of the current ELF file.
 ///
 /// @return the value of the DT_NEEDED property.
diff --git a/src/abg-tools-utils.cc b/src/abg-tools-utils.cc
index db04ccd8..81f9aa75 100644
--- a/src/abg-tools-utils.cc
+++ b/src/abg-tools-utils.cc
@@ -47,6 +47,9 @@ 
 #ifdef WITH_CTF
 #include "abg-ctf-reader.h"
 #endif
+#ifdef WITH_BTF
+#include "abg-btf-reader.h"
+#endif
 #include "abg-internal.h"
 #include "abg-regex.h"
 
@@ -504,6 +507,34 @@  file_has_ctf_debug_info(const string& elf_file_path,
   return false;
 }
 
+/// Test if an ELF file has BTFG debug info.
+///
+/// @param elf_file_path the path to the ELF file to consider.
+///
+/// @param debug_info_root a vector of pointer to directory to look
+/// for debug info, in case the file is associated to split debug
+/// info.  If there is no split debug info then this vector can be
+/// empty.  Note that convert_char_stars_to_char_star_stars() can be
+/// used to ease the construction of this vector.
+///
+/// @return true iff the ELF file at @elf_file_path is an ELF file
+/// that contains debug info.
+bool
+file_has_btf_debug_info(const string& elf_file_path,
+			const vector<char**>& debug_info_root_paths)
+{
+    if (guess_file_type(elf_file_path) != FILE_TYPE_ELF)
+    return false;
+
+  environment env;
+  elf::reader r(elf_file_path, debug_info_root_paths, env);
+
+  if (r.find_btf_section())
+    return true;
+
+  return false;
+}
+
 /// Tests if a given path is a directory or a symbolic link to a
 /// directory.
 ///
@@ -2850,6 +2881,13 @@  create_best_elf_based_reader(const string& elf_file_path,
 #ifdef WITH_CTF
       if (file_has_ctf_debug_info(elf_file_path, debug_info_root_paths))
 	result = ctf::create_reader(elf_file_path, debug_info_root_paths, env);
+#endif
+    }
+  else if (requested_fe_kind & corpus::BTF_ORIGIN)
+    {
+#ifdef WITH_BTF
+      if (file_has_btf_debug_info(elf_file_path, debug_info_root_paths))
+	result = btf::create_reader(elf_file_path, debug_info_root_paths, env);
 #endif
     }
   else
@@ -2862,6 +2900,14 @@  create_best_elf_based_reader(const string& elf_file_path,
 	// front end even if it wasn't formally requested by the user.
 	result = ctf::create_reader(elf_file_path, debug_info_root_paths, env);
 #endif
+
+#ifdef WITH_BTF
+      if (!file_has_dwarf_debug_info(elf_file_path, debug_info_root_paths)
+	  && file_has_btf_debug_info(elf_file_path, debug_info_root_paths))
+	// The file has BTF debug info and no BTF, let's use the BTF
+	// front-end even if it wasn't formally requested by the user.
+	result = btf::create_reader(elf_file_path, debug_info_root_paths, env);
+#endif
     }
 
   if (!result)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 7c515d35..2c5e7286 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -30,6 +30,10 @@  if CTF_READER
 TESTS += runtestreadctf
 endif
 
+if BTF_READER
+TESTS += runtestreadbtf
+endif
+
 # rather cheap tests
 TESTS+=				\
 runtestabicompat		\
@@ -111,6 +115,13 @@  runtestreadctf_LDADD=libtestreadcommon.la libtestutils.la	\
 runtestreadctf_LDFLAGS=-pthread
 endif
 
+if BTF_READER
+runtestreadbtf_SOURCES=test-read-btf.cc
+runtestreadbtf_LDADD=libtestreadcommon.la libtestutils.la	\
+		     $(top_builddir)/src/libabigail.la
+runtestreadbtf_LDFLAGS=-pthread
+endif
+
 runtestannotate_SOURCES=test-annotate.cc
 runtestannotate_LDADD=libtestutils.la $(top_builddir)/src/libabigail.la
 
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index a7c4502c..a4740e3e 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -242,6 +242,12 @@  test-abidiff-exit/test-rhbz2114909-v0.o       \
 test-abidiff-exit/test-rhbz2114909-v1.cc      \
 test-abidiff-exit/test-rhbz2114909-v1.o       \
 test-abidiff-exit/test-rhbz2114909-report-1.txt  \
+test-abidiff-exit/btf/test0-report-1.txt	\
+test-abidiff-exit/btf/test0-report-2.txt	\
+test-abidiff-exit/btf/test0-v0.c	\
+test-abidiff-exit/btf/test0-v0.o	\
+test-abidiff-exit/btf/test0-v1.c	\
+test-abidiff-exit/btf/test0-v1.o	\
 \
 test-diff-dwarf/test0-v0.cc		\
 test-diff-dwarf/test0-v0.o			\
@@ -721,6 +727,13 @@  test-read-ctf/test-array-size.abi	\
 test-read-ctf/test-array-size.c		\
 test-read-ctf/test-array-size.o		\
 \
+test-read-btf/test0.c 	   			\
+test-read-btf/test0.o      			\
+test-read-btf/test0.o.abi  			\
+test-read-btf/test1.c				\
+test-read-btf/test1.o				\
+test-read-btf/test1.o.abi			\
+\
 test-annotate/test0.abi			\
 test-annotate/test1.abi			\
 test-annotate/test2.so.abi		\
diff --git a/tests/data/test-abidiff-exit/btf/test0-report-1.txt b/tests/data/test-abidiff-exit/btf/test0-report-1.txt
new file mode 100644
index 00000000..7533965a
--- /dev/null
+++ b/tests/data/test-abidiff-exit/btf/test0-report-1.txt
@@ -0,0 +1,16 @@ 
+Functions changes summary: 0 Removed, 1 Changed, 0 Added function
+Variables changes summary: 0 Removed, 0 Changed (1 filtered out), 0 Added variable
+
+1 function with some indirect sub-type change:
+
+  [C] 'function void fn0(const foo_type*)' has some indirect sub-type changes:
+    return type changed:
+      type name changed from 'void' to 'int'
+      type size changed from 0 to 32 (in bits)
+      mangled name changed from '' to int
+    parameter 1 of type 'const foo_type*' changed:
+      in pointed to type 'const foo_type':
+        entity changed from 'const foo_type' to 'typedef foo_type'
+        type size hasn't changed
+    parameter 2 of type 'int' was added
+
diff --git a/tests/data/test-abidiff-exit/btf/test0-report-2.txt b/tests/data/test-abidiff-exit/btf/test0-report-2.txt
new file mode 100644
index 00000000..7bc1f6d0
--- /dev/null
+++ b/tests/data/test-abidiff-exit/btf/test0-report-2.txt
@@ -0,0 +1,53 @@ 
+Functions changes summary: 0 Removed, 1 Changed, 0 Added function
+Variables changes summary: 0 Removed, 1 Changed, 0 Added variable
+
+1 function with some indirect sub-type change:
+
+  [C] 'function void fn0(const foo_type*)' has some indirect sub-type changes:
+    return type changed:
+      type name changed from 'void' to 'int'
+      type size changed from 0 to 32 (in bits)
+      mangled name changed from '' to int
+    parameter 1 of type 'const foo_type*' changed:
+      in pointed to type 'const foo_type':
+        entity changed from 'const foo_type' to 'typedef foo_type'
+        type size hasn't changed
+    parameter 2 of type 'int' was added
+
+1 Changed variable:
+
+  [C] 'foo_type foos[2]' was changed:
+    type of variable changed:
+      array element type 'struct foo_type' changed:
+        type size hasn't changed
+        2 data member changes:
+          type of 'const int* m0' changed:
+            in pointed to type 'const int':
+              entity changed from 'const int' to 'int'
+              type size hasn't changed
+          type of 'volatile const u_type* m5' changed:
+            in pointed to type 'volatile const u_type':
+              in unqualified underlying type 'typedef u_type':
+                underlying type 'union u_type' changed:
+                  type size hasn't changed
+                  1 data member insertion:
+                    'char* m2'
+                  2 data member changes:
+                    type of 'ENUM_TYPE* m0' changed:
+                      in pointed to type 'typedef ENUM_TYPE':
+                        underlying type 'enum ENUM_TYPE' changed:
+                          type size hasn't changed
+                          1 enumerator insertion:
+                            'ENUM_TYPE::E2_ENUM_TYPE' value '2'
+                    type of 'ANOTHER_ENUM_TYPE* m1' changed:
+                      in pointed to type 'typedef ANOTHER_ENUM_TYPE':
+                        underlying type 'enum ANOTHER_ENUM_TYPE' changed:
+                          type size hasn't changed
+                          1 enumerator insertion:
+                            'ANOTHER_ENUM_TYPE::E2_ANOTHER_ENUM_TYPE' value '2'
+                  type changed from:
+                    union u_type{ENUM_TYPE* m0; ANOTHER_ENUM_TYPE* m1;}
+                  to:
+                    union u_type{ENUM_TYPE* m0; ANOTHER_ENUM_TYPE* m1; char* m2;}
+      type size hasn't changed
+
diff --git a/tests/data/test-abidiff-exit/btf/test0-v0.c b/tests/data/test-abidiff-exit/btf/test0-v0.c
new file mode 100644
index 00000000..5e5bdd57
--- /dev/null
+++ b/tests/data/test-abidiff-exit/btf/test0-v0.c
@@ -0,0 +1,40 @@ 
+/*
+ * Compile this to emit BTF debug info with:
+ *
+ * gcc -c -gbtf test0.c
+ */
+
+typedef enum ENUM_TYPE
+{
+  E0_ENUM_TYPE = 0,
+  E1_ENUM_TYPE= 1
+} ENUM_TYPE;
+
+typedef enum ANOTHER_ENUM_TYPE
+{
+  E0_ANOTHER_ENUM_TYPE = 0,
+  E1_ANOTHER_ENUM_TYPE= 1
+} ANOTHER_ENUM_TYPE;
+
+typedef union u_type
+{
+  ENUM_TYPE *m0;
+  ANOTHER_ENUM_TYPE *m1;
+} u_type;
+
+typedef struct foo_type
+{
+  const int *m0;
+  volatile char *m1;
+  unsigned *m2;
+  const volatile unsigned char *m3;
+  float m4[10];
+  volatile const u_type *m5;
+} foo_type;
+
+void
+fn0(const foo_type* p __attribute__((unused)))
+{
+}
+
+struct foo_type foos[2] = {0};
diff --git a/tests/data/test-abidiff-exit/btf/test0-v0.o b/tests/data/test-abidiff-exit/btf/test0-v0.o
new file mode 100644
index 0000000000000000000000000000000000000000..42c725d3ff1130e63d6b924480fdfdc75109a827
GIT binary patch
literal 2320
zcmbtVOK%)S5U!ax&f3_<3Gal*<bX&dJo^Zdf`!lqYn|m~EXzJ{i0s+j*$3?GtairH
z8VQjTQaA+(i5uj`0f`F|0#1=QAaTlXK#GLKDK{hz@O{(Wp3bm0N?To3UsZMYR9E+Y
z(s*anbsRC|$m^0@1{Jw?GS};wT9;*6lD+1w`^{S)|8@KNFL{8aef_5+PiwpHijdc_
z0M~#zKL`1U_A`$7i^_KB^T0J_m*{&9Z1T1gE^SEqrphic?=vvsPL?1)2cH6yz5o-m
z$UTu8E<f{2WtZl2d~qSy7Z#y^1%`ia8}eK5qhQka;4|Pl_y_P~;G5uI!B2tjf`12}
z1#{E)A<lv8*z7mNfa>q&Cy^GHmE`EIVE9hi*{R`M%=Q;n70%H;ZRwa=ep0r{Aw*hy
zTuF`#PvpA$B=p~uy~_REQ@X?2)s@aMTbt+h448G%<nm1aP)2FVpa^+3*Rx#um}@qK
zkIp{#5)?0wb)SR&K-nwLL$Wx3D|_h$$jbom%Z}l^!sQNSV{7l-{oVJs8`7xkTSnC~
z4oAtLHwru6V3b-3SGV5VZ8mnSKuj?Mtj+-c8+N##9*skZDiT#i8{IfoC>MsO-47;o
zM>RR#lfo`ZcNhn$4C7JHJN9L1uSj%Rj%R|B$c=c=k?yD><$fH6<xbrBU{LN2((-W7
z3R;6+Fc_9wX}26sgJ?VqlX4m+X=Uw3#czv@#cw5v$hD1)b??Hpt-Xt0wdU9Ss#mMj
zYLzS1SG@~6C{{B_wPUSzQI}kF?+Wh`QgN=%U1EkDZS(v{rvIFecEp*kI_H*7m2Shy
zJ~6$Ly$@GcRzGz&mghGSkY&yuIPaW1_-B^)MAkJAd)R+xEZn?Byi2E#ua>A@Vyaas
z7C0+6V2?f!``Gky2KdQQlm;#FlXRkF-)Lz#O~vm7X`m|Zzp=Y1emjn$5bfZP;xzP8
z!?h$0+8=7WH#+pklXx6X(j)OF;V|&S{(g57L}9kv{~h=iv4+;$mrYsE#{8|;rd2tG
zoQGh(d6Z1t+eRr!#@h4}44!W>|3C-b(b5RdC|bS@S)7xu0}h~{3lkS(tB4_1VX#GK
zbrY8@jH<~ua^1r0&^PT%U^z^lf!>0`|Eh<22==zlFYc>&7g~troW=Z`I{ylMHWl-)
z!^zOP9R$6}3k~91PvTq8T$69`S!*-?N4@@(28j2JM^Vh@|7_uifZ#mz`qqC`h8OF{
R|GioH2ReV4W#vOL|6h!W&5-~A

literal 0
HcmV?d00001

diff --git a/tests/data/test-abidiff-exit/btf/test0-v1.c b/tests/data/test-abidiff-exit/btf/test0-v1.c
--- /dev/null
+++ b/tests/data/test-abidiff-exit/btf/test0-v1.c
@@ -0,0 +1,45 @@ 
+/*
+ * Compile this to emit BTF debug info with:
+ *
+ * gcc -c -gbtf test0.c
+ */
+
+typedef enum ENUM_TYPE
+{
+  E0_ENUM_TYPE = 0,
+  E1_ENUM_TYPE= 1,
+  E2_ENUM_TYPE= 2
+} ENUM_TYPE;
+
+typedef enum ANOTHER_ENUM_TYPE
+{
+  E0_ANOTHER_ENUM_TYPE = 0,
+  E1_ANOTHER_ENUM_TYPE= 1,
+  E2_ANOTHER_ENUM_TYPE= 2
+} ANOTHER_ENUM_TYPE;
+
+typedef union u_type
+{
+  ENUM_TYPE *m0;
+  ANOTHER_ENUM_TYPE *m1;
+  char *m2;
+} u_type;
+
+typedef struct foo_type
+{
+  int *m0;
+  volatile char *m1;
+  unsigned *m2;
+  const volatile unsigned char *m3;
+  float m4[10];
+  volatile const u_type *m5;
+} foo_type;
+
+int
+fn0(foo_type* p, int a)
+{
+  *p->m0 = a;
+  return a;
+}
+
+struct foo_type foos[2] = {0};
diff --git a/tests/data/test-abidiff-exit/btf/test0-v1.o b/tests/data/test-abidiff-exit/btf/test0-v1.o
new file mode 100644
index 0000000000000000000000000000000000000000..712d6aad6475a71d6f685d85c75ce006914818e2
GIT binary patch
literal 2376
zcmbtVOK%%h6h1Re6DP#MEl?ieF)R?RM8@_2Qt1L*BDV&WP=#0(YO8ddj340fSe^+=
zkPwj&h$tJDhz<P#?2r&_P!SupEGi)(v10`itrVp0==aUs8{ba7<H+AVukYMDbM77A
zkLwrC*_I_*EO}MNo{owXrpI<IHEU9od1*HG{%q`hbZ2ktps`=S)7Y2&=D}WRzkYD_
zXVSj@%aX@ZzR0ef;R+Vu8b?H?r=TBDdGZ*44ggg=0Ai8j0Iva%0y%F0y8bewkRia_
zrvU4}0w4v}e*=Jjv;h4rFauD&0~n+H2+XC{;b5aii+vQ<51E&C40Sqf&uW&)EbX5%
z-)!65yum5L{sll=r!GR@1daie-+<#l4fq{+2-pSw1|9`&05f>eC!qPlbHF0Z8eaNW
z#>PEECKbD+3@BUon>PFON0!s?#1Lif;Y@R!ePnFgCt&}R`6c%D-^{ky$697{OnbvU
zeH<|QYA?VqW=~dl5Vn$(PoI4K5YR<F`#a!21Kor+-{ncfPGoZ}JO!KPV&>034f_&o
z{L*C{_>}7$>MPB+*4N&>SeJTb-EgX=;~CD@AntVr!KTw2Bu4ewmABU#^;Lt2`6dLb
zlY-ua;Qh5*>&ebAkgy_QRW`c*NJ39kupLENmQR&2nAFdjv0s#SKk^glM}v-Ym`f{O
zlJI3YoCHQJ?LkG#-6#yoo6+Wby>h3Ql>5Dw-|BVzUccN*+T~!|4~P9AE+;{pRF*!d
zx*Pobd~sWGEOLH%`HXY&{7UnbQ}tZWtvX)C^D3vSuQ(@H(ZPnFsKk<YikpFt0B`_)
zV<^P_TQ?ZfVktk{S`mFVtq)hl+Adj(^D~7nkmPwFD5*ZXQ2NYXE>2$pAq!6&Iq#I*
z{%=z9NP4R7&G1`3Zp8JPJe$W3{}$9)W2w<8HaP7x!VYr~o_Brb(|{ZAgo)o0H%>;G
z?rJLuwi9tT{Y3TKeSPhmxEoOz1~>%nAW8xkJzR<tf8%}ScLrPTa1;%LQL-cMDCqlc
z&|Pnj{4hwD`@aMKf7nCgnn<UtrQ>){lxuV@i#!X(di{s0c~`WN)8v}+JRI(CzW$mb
zT~}HwM;l7N0G*$cscRe{2f2y!+$uPXRY*2uO&#L05u>T=wLW9SOzJ1*M_?p$oesST
zgLm3JX9)i1sy_c*`EQ{GF6YeGf3NCKBWFs!{wk8B)=U!YU4GCI-UAfg1FY5cIzDM_
v@^7j2x9LE5cX2E7^}KU)TZD*6SFLZ{UuitwKi<8Q>c3R={j_R4<m>MO6Qkh~

literal 0
HcmV?d00001

diff --git a/tests/data/test-read-btf/test0.c b/tests/data/test-read-btf/test0.c
--- /dev/null
+++ b/tests/data/test-read-btf/test0.c
@@ -0,0 +1,40 @@ 
+/*
+ * Compile this to emit BTF debug info with:
+ *
+ * gcc -c -gbtf test0.c
+ */
+
+typedef enum ENUM_TYPE
+{
+  E0_ENUM_TYPE = 0,
+  E1_ENUM_TYPE= 1
+} ENUM_TYPE;
+
+typedef enum ANOTHER_ENUM_TYPE
+{
+  E0_ANOTHER_ENUM_TYPE = 0,
+  E1_ANOTHER_ENUM_TYPE= 1
+} ANOTHER_ENUM_TYPE;
+
+typedef union u_type
+{
+  ENUM_TYPE *m0;
+  ANOTHER_ENUM_TYPE *m1;
+} u_type;
+
+typedef struct foo_type
+{
+  const int *m0;
+  volatile char *m1;
+  unsigned *m2;
+  const volatile unsigned char *m3;
+  float m4[10];
+  volatile const u_type *m5;
+} foo_type;
+
+void
+fn0(const foo_type* p __attribute__((unused)))
+{
+}
+
+struct foo_type foos[2] = {0};
diff --git a/tests/data/test-read-btf/test0.o b/tests/data/test-read-btf/test0.o
new file mode 100644
index 0000000000000000000000000000000000000000..81d6fc026098f5deecc8b780b618baefabacc3e3
GIT binary patch
literal 2312
zcmbtV&5ImG6o1{>Y-ZQpbrV0L#*fZH+<@tsX%%A-nYhemFuI0i7xWM|Ju^KYIz2s1
z_sXmwdJ^I(g5XVa^B{;IDBc1d6i@yQLO}48o8Uozzpkq7N;@|n^Xk3#d+$|Mzj{^k
zVe|DZ*Kx#<Bd<td8C2w#<AvVH)rPFfitM#++-cqT;IEt4ekuYi?aMzMc|_X-SA_f`
z7T_9C7Z)J!(|*Pge_GiNeI7Vac7?u|!6t88;nIPmZ>H=D^F9G1?sx_AQ}78e=`%1f
zi`-*{;qo&-S9WEwz?YW_eQ6o`7hw1ou0nndz8_5b4txsS0DljD0Q?d7XYgt8ZSe2l
z$H3h59f&jF1~&T@F`)Xp`AMYZRV6vPYZ$&!c7AI37PI}Ob%k?uPg**rmLHXEatM)@
zA5@a#(nE#rJ`DX=Wv_ETzbV~e?HWqwn6J%qdlbyNXmWX`e<-7}Vo-!UUg&u)eatl*
z!bj(yTZ7^ivF;Pl?<#xkNk|swZ)LAM1sMRqFF%H}3YS}y&F#H6_IKaD+LUH(-!kf!
zaWGDY{c+UwhU3gixU~J|ZmYRt1!9UhV0{kw->`%I>~IoET$8vi+UO;TLb)(Jok2LI
zI}YS%PYQcZdZQ%FWR#5i-jOd)dtTxf<Y*=+sl1mAyV4uiq&i6AsM<}s?+mN`VOAXt
z+hKdy4~L^_JL^@WSr|`7QCiKSG^_a?k%{>2G!?nLxp~psxV*i0&Z`H0;McvN76i2m
z^_RSj9aO3nX4-K+IEOOpo)z97WZ^uW+r$hx+UB{DOy4>0?}#&7ch0PwsN95;{b70}
ze-|#Tt$*xpt}bpNAkUmXaNY&E`_DY@vAkRE_3%EQvT*YTvH4COT`5sL#8guuRyD6N
zK@l$>RsWhE&LKZNjI*#Uews~{92hN&W|{ciFbh?&{a1Il#P1|=9H9;Tags$oYIr`)
z!p^(e?vD@r$uyZn)9g_EX*3G`Xt3X#hH<2K@c#tgAlA@&^YRHB`B=Qs+O#UCkh2iX
zH;<8tyJD1*WUNikz~K3o^RMfmTUr|78AZ!yA<J{pb)5s~htkC5*g9f})fjBiY2CzC
z3!`fCjl5`KcIcLU2`q=nGtg^L_#bso55aEf{PMoaccG0~&RNc%>HG`u*;LNI1}8%s
zb`Wh|Xb|6c65n{{ntX%LTbuFU>h)(dK)h!>igG^hW9f*1h-~Qf`JZZ2xqke=o0tEV
M&L7#y(98M%0waFQm;e9(

literal 0
HcmV?d00001

diff --git a/tests/data/test-read-btf/test0.o.abi b/tests/data/test-read-btf/test0.o.abi
--- /dev/null
+++ b/tests/data/test-read-btf/test0.o.abi
@@ -0,0 +1,90 @@ 
+<abi-corpus version='2.1' path='data/test-read-btf/test0.o'>
+  <elf-function-symbols>
+    <elf-symbol name='fn0' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+  </elf-function-symbols>
+  <elf-variable-symbols>
+    <elf-symbol name='foos' size='160' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+  </elf-variable-symbols>
+  <abi-instr address-size='64'>
+    <type-decl name='char' size-in-bits='8' id='type-id-1'/>
+    <enum-decl name='ANOTHER_ENUM_TYPE' linkage-name='ANOTHER_ENUM_TYPE' id='type-id-2'>
+      <underlying-type type-id='type-id-3'/>
+      <enumerator name='E0_ANOTHER_ENUM_TYPE' value='0'/>
+      <enumerator name='E1_ANOTHER_ENUM_TYPE' value='1'/>
+    </enum-decl>
+    <enum-decl name='ENUM_TYPE' linkage-name='ENUM_TYPE' id='type-id-4'>
+      <underlying-type type-id='type-id-5'/>
+      <enumerator name='E0_ENUM_TYPE' value='0'/>
+      <enumerator name='E1_ENUM_TYPE' value='1'/>
+    </enum-decl>
+    <type-decl name='enum-ANOTHER_ENUM_TYPE-underlying-type-32' size-in-bits='32' alignment-in-bits='32' id='type-id-3'/>
+    <type-decl name='enum-ENUM_TYPE-underlying-type-32' size-in-bits='32' alignment-in-bits='32' id='type-id-5'/>
+    <type-decl name='float' size-in-bits='32' id='type-id-6'/>
+    <array-type-def dimensions='1' type-id='type-id-6' size-in-bits='320' id='type-id-7'>
+      <subrange length='10' id='type-id-8'/>
+    </array-type-def>
+    <array-type-def dimensions='1' type-id='type-id-9' size-in-bits='1280' id='type-id-10'>
+      <subrange length='2' id='type-id-11'/>
+    </array-type-def>
+    <type-decl name='int' size-in-bits='32' id='type-id-12'/>
+    <class-decl name='foo_type' size-in-bits='640' is-struct='yes' visibility='default' id='type-id-9'>
+      <data-member access='public' layout-offset-in-bits='0'>
+        <var-decl name='m0' type-id='type-id-13' visibility='default'/>
+      </data-member>
+      <data-member access='public' layout-offset-in-bits='64'>
+        <var-decl name='m1' type-id='type-id-14' visibility='default'/>
+      </data-member>
+      <data-member access='public' layout-offset-in-bits='128'>
+        <var-decl name='m2' type-id='type-id-15' visibility='default'/>
+      </data-member>
+      <data-member access='public' layout-offset-in-bits='192'>
+        <var-decl name='m3' type-id='type-id-16' visibility='default'/>
+      </data-member>
+      <data-member access='public' layout-offset-in-bits='256'>
+        <var-decl name='m4' type-id='type-id-7' visibility='default'/>
+      </data-member>
+      <data-member access='public' layout-offset-in-bits='576'>
+        <var-decl name='m5' type-id='type-id-17' visibility='default'/>
+      </data-member>
+    </class-decl>
+    <typedef-decl name='ANOTHER_ENUM_TYPE' type-id='type-id-2' id='type-id-18'/>
+    <typedef-decl name='ENUM_TYPE' type-id='type-id-4' id='type-id-19'/>
+    <typedef-decl name='foo_type' type-id='type-id-9' id='type-id-20'/>
+    <typedef-decl name='u_type' type-id='type-id-21' id='type-id-22'/>
+    <union-decl name='u_type' size-in-bits='64' visibility='default' id='type-id-21'>
+      <data-member access='public'>
+        <var-decl name='m0' type-id='type-id-23' visibility='default'/>
+      </data-member>
+      <data-member access='public'>
+        <var-decl name='m1' type-id='type-id-24' visibility='default'/>
+      </data-member>
+    </union-decl>
+    <type-decl name='unsigned char' size-in-bits='8' id='type-id-25'/>
+    <type-decl name='unsigned int' size-in-bits='32' id='type-id-26'/>
+    <pointer-type-def type-id='type-id-18' size-in-bits='64' id='type-id-24'/>
+    <pointer-type-def type-id='type-id-19' size-in-bits='64' id='type-id-23'/>
+    <qualified-type-def type-id='type-id-20' const='yes' id='type-id-27'/>
+    <pointer-type-def type-id='type-id-27' size-in-bits='64' id='type-id-28'/>
+    <qualified-type-def type-id='type-id-12' const='yes' id='type-id-29'/>
+    <pointer-type-def type-id='type-id-29' size-in-bits='64' id='type-id-13'/>
+    <qualified-type-def type-id='type-id-22' const='yes' id='type-id-30'/>
+    <qualified-type-def type-id='type-id-25' const='yes' id='type-id-31'/>
+    <pointer-type-def type-id='type-id-26' size-in-bits='64' id='type-id-15'/>
+    <qualified-type-def type-id='type-id-1' volatile='yes' id='type-id-32'/>
+    <pointer-type-def type-id='type-id-32' size-in-bits='64' id='type-id-14'/>
+    <qualified-type-def type-id='type-id-30' volatile='yes' id='type-id-33'/>
+    <pointer-type-def type-id='type-id-33' size-in-bits='64' id='type-id-17'/>
+    <qualified-type-def type-id='type-id-31' volatile='yes' id='type-id-34'/>
+    <pointer-type-def type-id='type-id-34' size-in-bits='64' id='type-id-16'/>
+    <var-decl name='foos' type-id='type-id-10' mangled-name='foos' visibility='default' elf-symbol-id='foos'/>
+    <function-decl name='fn0' mangled-name='fn0' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='fn0'>
+      <parameter type-id='type-id-28' name='p'/>
+      <return type-id='type-id-35'/>
+    </function-decl>
+    <type-decl name='void' id='type-id-35'/>
+    <function-type size-in-bits='64' id='type-id-36'>
+      <parameter type-id='type-id-28' name='p'/>
+      <return type-id='type-id-35'/>
+    </function-type>
+  </abi-instr>
+</abi-corpus>
diff --git a/tests/data/test-read-btf/test1.c b/tests/data/test-read-btf/test1.c
new file mode 100644
index 00000000..adbfb8b3
--- /dev/null
+++ b/tests/data/test-read-btf/test1.c
@@ -0,0 +1,20 @@ 
+/*
+ * Compile this to emit BTF debug info with:
+ *
+ * gcc -c -gbtf test0.c
+ */
+
+struct S;
+typedef struct S S;
+
+union U;
+typedef union U U;
+
+S*
+fn0(S* p, U* u)
+{
+  if (u)
+    ;
+
+  return p;
+}
diff --git a/tests/data/test-read-btf/test1.o b/tests/data/test-read-btf/test1.o
new file mode 100644
index 0000000000000000000000000000000000000000..218e2e16f7c9d6ed832060f4f3cf0c3ee9ee3e5a
GIT binary patch
literal 1536
zcmbtUJ8u&~5T3h)*no{fM1d5Fqr#EmJ^Lz?0vW6%7J?`;xIz)Rv(Jy<m+sb5f`sTm
z77hP^zY>Xt-#|}A16?|p@4V|QC)2>3W_RYB*Lr5xZ+D;U*_K6`EP6nfnnsDv?Mu0u
zh*io{j+*t^xBBeOMSV8?QU9=earF7qcZ+T-=LzrN2D8eVSz~#HnJEkGN+Qzx4DVp3
ztg=J4bz$D~@I7G0e#or+St5Itm#dJ7a;QPi_-pdl8I{QGjfbJz9=A{WZnrPpK|k<=
ze%J30+(33*8OAbp+rIP@u^5GZyQmFi(Za8o67AP&J5FK$pt<FgJ>iM6<CQ$GR4Lzg
z3XQPs)P1Q8MQ;m7Ai}8}W}KJ<$tLR@m?lf<y63l)!Ft^w>vY}P%w@74*?5O>FlYnI
z!yD`G>{@>9DLX*_3nA|+{rt7eyrO4f7}3EE15awfjuxR4=q8hcg$dKq6G{LPpADrS
zkcee8-+Nk;;i)8nE|Uo2n8c&!dn8)p;V>MrVKkPZ;2srY>9<}fy*rwUNi?2>kvt<2
zg#%xNy_cQHABIV}|6MjY0eY0&7$aF#$&25wboiQdmla^fjZMtB@yjS3e~+CozkiM2
z=Gb&j)YbLBLNO@qTV@-uWT=MUwsLe045E(L`;LK`t}nEM2`=${%otwrA9slLV-=r1
zSNg68DrY(#Zw6GEZ;Le@f5aBN$LhjkE>u)P{ZDp{VZ`cqjiXGuHZpus^^c)2U1!FO
j)A4w3>De<x1(h@p#UPb-f!t(iS^S}jA1plQLOT98Bq(pA

literal 0
HcmV?d00001

diff --git a/tests/data/test-read-btf/test1.o.abi b/tests/data/test-read-btf/test1.o.abi
--- /dev/null
+++ b/tests/data/test-read-btf/test1.o.abi
@@ -0,0 +1,23 @@ 
+<abi-corpus version='2.1' path='data/test-read-btf/test1.o'>
+  <elf-function-symbols>
+    <elf-symbol name='fn0' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+  </elf-function-symbols>
+  <abi-instr address-size='64'>
+    <typedef-decl name='S' type-id='type-id-1' id='type-id-2'/>
+    <typedef-decl name='U' type-id='type-id-3' id='type-id-4'/>
+    <pointer-type-def type-id='type-id-2' size-in-bits='64' id='type-id-5'/>
+    <pointer-type-def type-id='type-id-4' size-in-bits='64' id='type-id-6'/>
+    <function-decl name='fn0' mangled-name='fn0' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='fn0'>
+      <parameter type-id='type-id-5' name='p'/>
+      <parameter type-id='type-id-6' name='u'/>
+      <return type-id='type-id-5'/>
+    </function-decl>
+    <class-decl name='S' size-in-bits='48' is-struct='yes' visibility='default' is-declaration-only='yes' id='type-id-1'/>
+    <union-decl name='U' visibility='default' is-declaration-only='yes' id='type-id-3'/>
+    <function-type size-in-bits='64' id='type-id-7'>
+      <parameter type-id='type-id-5' name='p'/>
+      <parameter type-id='type-id-6' name='u'/>
+      <return type-id='type-id-5'/>
+    </function-type>
+  </abi-instr>
+</abi-corpus>
diff --git a/tests/test-abidiff-exit.cc b/tests/test-abidiff-exit.cc
index e7f02eb5..df2f087c 100644
--- a/tests/test-abidiff-exit.cc
+++ b/tests/test-abidiff-exit.cc
@@ -471,6 +471,30 @@  InOutSpec in_out_specs[] =
     "data/test-abidiff-exit/test-rhbz2114909-report-1.txt",
     "output/test-abidiff-exit/test-rhbz2114909-report-1.txt"
   },
+#ifdef WITH_BTF
+  {
+    "data/test-abidiff-exit/btf/test0-v0.o",
+    "data/test-abidiff-exit/btf/test0-v1.o",
+    "",
+    "",
+    "",
+    "--no-default-suppression --btf",
+    abigail::tools_utils::ABIDIFF_ABI_CHANGE,
+    "data/test-abidiff-exit/btf/test0-report-1.txt",
+    "output/test-abidiff-exit/btf/test0-report-1.txt"
+  },
+  {
+    "data/test-abidiff-exit/btf/test0-v0.o",
+    "data/test-abidiff-exit/btf/test0-v1.o",
+    "",
+    "",
+    "",
+    "--no-default-suppression --harmless --btf",
+    abigail::tools_utils::ABIDIFF_ABI_CHANGE,
+    "data/test-abidiff-exit/btf/test0-report-2.txt",
+    "output/test-abidiff-exit/btf/test0-report-2.txt"
+  },
+#endif
   {0, 0, 0 ,0, 0, 0, abigail::tools_utils::ABIDIFF_OK, 0, 0}
 };
 
diff --git a/tests/test-read-btf.cc b/tests/test-read-btf.cc
new file mode 100644
index 00000000..837fa585
--- /dev/null
+++ b/tests/test-read-btf.cc
@@ -0,0 +1,188 @@ 
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+// -*- Mode: C++ -*-
+//
+// Copyright (C) 2022 Red Hat, Inc.
+//
+// Author: Dodji Seketeli
+
+/// @file
+///
+/// This file is part of the BTF testsuite. It reads ELF binaries
+/// containing BTF, save them in XML corpus files and diff the
+/// corpus files against reference XML corpus files.
+
+#include <cstdlib>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+#include "abg-btf-reader.h"
+#include "test-read-common.h"
+
+using std::string;
+using std::cerr;
+using std::vector;
+
+using abigail::tests::read_common::InOutSpec;
+using abigail::tests::read_common::test_task;
+using abigail::tests::read_common::display_usage;
+using abigail::tests::read_common::options;
+
+using abigail::btf::create_reader;
+using abigail::xml_writer::SEQUENCE_TYPE_ID_STYLE;
+using abigail::xml_writer::HASH_TYPE_ID_STYLE;
+using abigail::tools_utils::emit_prefix;
+
+static InOutSpec in_out_specs[] =
+{
+  {
+    "data/test-read-btf/test0.o",
+    "",
+    "",
+    SEQUENCE_TYPE_ID_STYLE,
+    "data/test-read-btf/test0.o.abi",
+    "output/test-read-btf/test0.o.abi",
+    "--btf",
+  },
+  {
+    "data/test-read-btf/test1.o",
+    "",
+    "",
+    SEQUENCE_TYPE_ID_STYLE,
+    "data/test-read-btf/test1.o.abi",
+    "output/test-read-btf/test1.o.abi",
+    "--btf",
+  },
+  // This should be the last entry.
+  {NULL, NULL, NULL, SEQUENCE_TYPE_ID_STYLE, NULL, NULL, NULL}
+};
+
+/// Task specialization to perform BTF tests.
+struct test_task_btf : public test_task
+{
+  test_task_btf(const InOutSpec &s,
+                string& a_out_abi_base,
+                string& a_in_elf_base,
+                string& a_in_abi_base);
+  virtual void
+  perform();
+
+  virtual
+  ~test_task_btf()
+  {}
+}; // end struct test_task_btf
+
+/// Constructor.
+///
+/// Task to be executed for each BTF test entry in @ref
+/// abigail::tests::read_common::InOutSpec.
+/// @param InOutSpec the array containing set of tests.
+///
+/// @param a_out_abi_base the output base directory for abixml files.
+///
+/// @param a_in_elf_base the input base directory for object files.
+///
+/// @param a_in_elf_base the input base directory for expected
+/// abixml files.
+test_task_btf::test_task_btf(const InOutSpec &s,
+                             string& a_out_abi_base,
+                             string& a_in_elf_base,
+                             string& a_in_abi_base)
+        : test_task(s, a_out_abi_base, a_in_elf_base, a_in_abi_base)
+  {}
+
+/// The thread function to execute each BTF test entry in @ref
+/// abigail::tests::read_common::InOutSpec.
+///
+/// This reads the corpus into memory, saves it to disk, loads it
+/// again and compares the new in-memory representation against the
+void
+test_task_btf::perform()
+{
+  abigail::ir::environment env;
+
+  set_in_elf_path();
+  set_in_suppr_spec_path();
+
+  abigail::fe_iface::status status =
+    abigail::fe_iface::STATUS_UNKNOWN;
+  vector<char**> di_roots;
+  ABG_ASSERT(abigail::tools_utils::file_exists(in_elf_path));
+
+  abigail::elf_based_reader_sptr rdr = abigail::btf::create_reader(in_elf_path,
+								   di_roots, env);
+  ABG_ASSERT(rdr);
+
+  corpus_sptr corp = rdr->read_corpus(status);
+
+  // if there is no output and no input, assume that we do not care about the
+  // actual read result, just that it succeeded.
+  if (!spec.in_abi_path && !spec.out_abi_path)
+    {
+        // Phew! we made it here and we did not crash! yay!
+        return;
+    }
+  if (!corp)
+    {
+        error_message = string("failed to read ") + in_elf_path  + "\n";
+        is_ok = false;
+        return;
+    }
+  corp->set_path(spec.in_elf_path);
+  // Do not take architecture names in comparison so that these
+  // test input binaries can come from whatever arch the
+  // programmer likes.
+  corp->set_architecture_name("");
+
+  if (!(is_ok = set_out_abi_path()))
+      return;
+
+  if (!(is_ok = serialize_corpus(out_abi_path, corp)))
+       return;
+
+  if (!(is_ok = run_abidw("--btf ")))
+    return;
+
+  if (!(is_ok = run_diff()))
+      return;
+}
+
+/// Create a new BTF instance for task to be execute by the testsuite.
+///
+/// @param s the @ref abigail::tests::read_common::InOutSpec
+/// tests container.
+///
+/// @param a_out_abi_base the output base directory for abixml files.
+///
+/// @param a_in_elf_base the input base directory for object files.
+///
+/// @param a_in_abi_base the input base directory for abixml files.
+///
+/// @return abigail::tests::read_common::test_task instance.
+static test_task*
+new_task(const InOutSpec* s, string& a_out_abi_base,
+         string& a_in_elf_base, string& a_in_abi_base)
+{
+  return new test_task_btf(*s, a_out_abi_base,
+                           a_in_elf_base, a_in_abi_base);
+}
+
+int
+main(int argc, char *argv[])
+{
+  options opts;
+  if (!parse_command_line(argc, argv, opts))
+    {
+      if (!opts.wrong_option.empty())
+        emit_prefix(argv[0], cerr)
+          << "unrecognized option: " << opts.wrong_option << "\n";
+      display_usage(argv[0], cerr);
+      return 1;
+    }
+
+  // compute number of tests to be executed.
+  const size_t num_tests = sizeof(in_out_specs) / sizeof(InOutSpec) - 1;
+
+  return run_tests(num_tests, in_out_specs, opts, new_task);
+}
diff --git a/tools/abidiff.cc b/tools/abidiff.cc
index 91a626a9..bf0bf9fc 100644
--- a/tools/abidiff.cc
+++ b/tools/abidiff.cc
@@ -23,6 +23,10 @@ 
 #include "abg-ctf-reader.h"
 #endif
 
+#ifdef WITH_BTF
+#include "abg-btf-reader.h"
+#endif
+
 using std::vector;
 using std::string;
 using std::ostream;
@@ -121,6 +125,9 @@  struct options
 #endif
 #ifdef WITH_CTF
   bool			use_ctf;
+#endif
+#ifdef WITH_BTF
+  bool			use_btf;
 #endif
   vector<char*> di_root_paths1;
   vector<char*> di_root_paths2;
@@ -170,6 +177,10 @@  struct options
     ,
       use_ctf()
 #endif
+#ifdef WITH_BTF
+    ,
+      use_btf()
+#endif
 #ifdef WITH_DEBUG_SELF_COMPARISON
     ,
       do_debug_self_comparison()
@@ -273,6 +284,9 @@  display_usage(const string& prog_name, ostream& out)
 #ifdef WITH_CTF
     << " --ctf use CTF instead of DWARF in ELF files\n"
 #endif
+#ifdef WITH_BTF
+    << " --btf use BTF instead of DWARF in ELF files\n"
+#endif
 #ifdef WITH_DEBUG_SELF_COMPARISON
     << " --debug-self-comparison debug the process of comparing "
     "an ABI corpus against itself"
@@ -639,6 +653,10 @@  parse_command_line(int argc, char* argv[], options& opts)
       else if (!strcmp(argv[i], "--ctf"))
         opts.use_ctf = true;
 #endif
+#ifdef WITH_BTF
+      else if (!strcmp(argv[i], "--btf"))
+        opts.use_btf = true;
+#endif
 #ifdef WITH_DEBUG_SELF_COMPARISON
       else if (!strcmp(argv[i], "--debug-self-comparison"))
 	opts.do_debug_self_comparison = true;
@@ -1232,6 +1250,10 @@  main(int argc, char* argv[])
 #ifdef WITH_CTF
 	    if (opts.use_ctf)
 	      requested_fe_kind = corpus::CTF_ORIGIN;
+#endif
+#ifdef WITH_BTF
+	    if (opts.use_btf)
+	      requested_fe_kind = corpus::BTF_ORIGIN;
 #endif
 	    abigail::elf_based_reader_sptr rdr =
 	      create_best_elf_based_reader(opts.file1,
@@ -1305,6 +1327,10 @@  main(int argc, char* argv[])
 #ifdef WITH_CTF
 	    if (opts.use_ctf)
 	      requested_fe_kind = corpus::CTF_ORIGIN;
+#endif
+#ifdef WITH_BTF
+	    if (opts.use_btf)
+	      requested_fe_kind = corpus::BTF_ORIGIN;
 #endif
             abigail::elf_based_reader_sptr rdr =
 	      create_best_elf_based_reader(opts.file2,
diff --git a/tools/abidw.cc b/tools/abidw.cc
index 102d3a8c..ddb3846e 100644
--- a/tools/abidw.cc
+++ b/tools/abidw.cc
@@ -29,6 +29,9 @@ 
 #ifdef WITH_CTF
 #include "abg-ctf-reader.h"
 #endif
+#ifdef WITH_BTF
+#include "abg-btf-reader.h"
+#endif
 #include "abg-writer.h"
 #include "abg-reader.h"
 #include "abg-comparison.h"
@@ -103,6 +106,9 @@  struct options
   bool			noout;
 #ifdef WITH_CTF
   bool			use_ctf;
+#endif
+#ifdef WITH_BTF
+  bool			use_btf;
 #endif
   bool			show_locs;
   bool			abidiff;
@@ -144,6 +150,9 @@  struct options
       noout(),
 #ifdef WITH_CTF
       use_ctf(false),
+#endif
+#ifdef WITH_BTF
+      use_btf(false),
 #endif
       show_locs(true),
       abidiff(),
@@ -234,6 +243,9 @@  display_usage(const string& prog_name, ostream& out)
     "speed-up the analysis of the binary\n"
     << "  --no-assume-odr-for-cplusplus  do not assume the ODR to speed-up the "
     "analysis of the binary\n"
+#ifdef WITH_BTF
+    << "  --btf use BTF instead of DWARF in ELF files\n"
+#endif
     << "  --annotate  annotate the ABI artifacts emitted in the output\n"
     << "  --stats  show statistics about various internal stuff\n"
     << "  --verbose show verbose messages about internal stuff\n";
@@ -335,6 +347,10 @@  parse_command_line(int argc, char* argv[], options& opts)
 #ifdef WITH_CTF
         else if (!strcmp(argv[i], "--ctf"))
           opts.use_ctf = true;
+#endif
+#ifdef WITH_BTF
+        else if (!strcmp(argv[i], "--btf"))
+          opts.use_btf = true;
 #endif
       else if (!strcmp(argv[i], "--no-architecture"))
 	opts.write_architecture = false;
@@ -588,6 +604,10 @@  load_corpus_and_write_abixml(char* argv[],
   if (opts.use_ctf)
     requested_fe_kind = corpus::CTF_ORIGIN;
 #endif
+#ifdef WITH_BTF
+  if (opts.use_btf)
+    requested_fe_kind = corpus::BTF_ORIGIN;
+#endif
 
   // First of all, create a reader to read the ABI from the file
   // specfied in opts ...
diff --git a/tools/abipkgdiff.cc b/tools/abipkgdiff.cc
index 99fc1a33..c588481c 100644
--- a/tools/abipkgdiff.cc
+++ b/tools/abipkgdiff.cc
@@ -93,6 +93,9 @@ 
 #ifdef WITH_CTF
 #include "abg-ctf-reader.h"
 #endif
+#ifdef WITH_BTF
+#include "abg-btf-reader.h"
+#endif
 
 using std::cout;
 using std::cerr;
@@ -212,6 +215,9 @@  public:
 #ifdef WITH_CTF
   bool		use_ctf;
 #endif
+#ifdef WITH_BTF
+  bool		use_btf;
+#endif
 
   vector<string> kabi_whitelist_packages;
   vector<string> suppression_paths;
@@ -256,6 +262,10 @@  public:
 #ifdef WITH_CTF
       ,
       use_ctf()
+#endif
+#ifdef WITH_BTF
+      ,
+      use_btf()
 #endif
   {
     // set num_workers to the default number of threads of the
@@ -905,6 +915,9 @@  display_usage(const string& prog_name, ostream& out)
     "binaries inside the input package against their ABIXML representation\n"
 #ifdef WITH_CTF
     << " --ctf                          use CTF instead of DWARF in ELF files\n"
+#endif
+#ifdef WITH_BTF
+    << " --btf                          use BTF instead of DWARF in ELF files\n"
 #endif
     << " --help|-h                      display this help message\n"
     << " --version|-v                   display program version information"
@@ -1353,6 +1366,10 @@  compare(const elf_file&		elf1,
 #ifdef WITH_CTF
     if (opts.use_ctf)
       requested_fe_kind = corpus::CTF_ORIGIN;
+#endif
+#ifdef WITH_BTF
+    if (opts.use_btf)
+      requested_fe_kind = corpus::BTF_ORIGIN;
 #endif
     abigail::elf_based_reader_sptr reader =
       create_best_elf_based_reader(elf1.path,
@@ -1414,6 +1431,11 @@  compare(const elf_file&		elf1,
             if (opts.use_ctf)
               ;
             else
+#endif
+#ifdef WITH_BTF
+	      if (opts.use_btf)
+		;
+	      else
 #endif
 	      reader->refers_to_alt_debug_info(alt_di_path);
 	    if (!alt_di_path.empty())
@@ -1449,10 +1471,16 @@  compare(const elf_file&		elf1,
   corpus_sptr corpus2;
   {
     corpus::origin requested_fe_kind = corpus::DWARF_ORIGIN;
+
 #ifdef WITH_CTF
     if (opts.use_ctf)
       requested_fe_kind = corpus::CTF_ORIGIN;
 #endif
+#ifdef WITH_BTF
+    if (opts.use_btf)
+      requested_fe_kind = corpus::BTF_ORIGIN;
+#endif
+
     abigail::elf_based_reader_sptr reader =
       create_best_elf_based_reader(elf2.path,
 				   di_dirs2,
@@ -1513,6 +1541,11 @@  compare(const elf_file&		elf1,
             if (opts.use_ctf)
               ;
             else
+#endif
+#ifdef WITH_BTF
+            if (opts.use_btf)
+              ;
+            else
 #endif
 	      reader->refers_to_alt_debug_info(alt_di_path);
 	    if (!alt_di_path.empty())
@@ -1617,6 +1650,10 @@  compare_to_self(const elf_file&		elf,
 #ifdef WITH_CTF
     if (opts.use_ctf)
       requested_fe_kind = corpus::CTF_ORIGIN;
+#endif
+#ifdef WITH_BTF
+    if (opts.use_btf)
+      requested_fe_kind = corpus::BTF_ORIGIN;
 #endif
     abigail::elf_based_reader_sptr reader =
       create_best_elf_based_reader(elf.path,
@@ -3044,14 +3081,24 @@  compare_prepared_linux_kernel_packages(package& first_package,
 
   suppressions_type supprs;
   corpus_group_sptr corpus1, corpus2;
+
+  corpus::origin requested_fe_kind = corpus::DWARF_ORIGIN;
+#ifdef WITH_CTF
+    if (opts.use_ctf)
+      requested_fe_kind = corpus::CTF_ORIGIN;
+#endif
+#ifdef WITH_BTF
+    if (opts.use_btf)
+      requested_fe_kind = corpus::BTF_ORIGIN;
+#endif
+
   corpus1 = build_corpus_group_from_kernel_dist_under(dist_root1,
 						      debug_dir1,
 						      vmlinux_path1,
 						      opts.suppression_paths,
 						      opts.kabi_whitelist_paths,
-						      supprs,
-						      opts.verbose,
-						      env);
+						      supprs, opts.verbose,
+						      env, requested_fe_kind);
 
   if (!corpus1)
     return abigail::tools_utils::ABIDIFF_ERROR;
@@ -3061,9 +3108,8 @@  compare_prepared_linux_kernel_packages(package& first_package,
 						      vmlinux_path2,
 						      opts.suppression_paths,
 						      opts.kabi_whitelist_paths,
-						      supprs,
-						      opts.verbose,
-						      env);
+						      supprs, opts.verbose,
+						      env, requested_fe_kind);
 
   if (!corpus2)
     return abigail::tools_utils::ABIDIFF_ERROR;
@@ -3434,6 +3480,10 @@  parse_command_line(int argc, char* argv[], options& opts)
 #ifdef WITH_CTF
 	else if (!strcmp(argv[i], "--ctf"))
           opts.use_ctf = true;
+#endif
+#ifdef WITH_BTF
+	else if (!strcmp(argv[i], "--btf"))
+          opts.use_btf = true;
 #endif
       else if (!strcmp(argv[i], "--help")
 	       || !strcmp(argv[i], "-h"))
diff --git a/tools/kmidiff.cc b/tools/kmidiff.cc
index 76fb9d92..f00895a3 100644
--- a/tools/kmidiff.cc
+++ b/tools/kmidiff.cc
@@ -63,6 +63,9 @@  struct options
   optional<bool>	exported_interfaces_only;
 #ifdef WITH_CTF
   bool			use_ctf;
+#endif
+#ifdef WITH_BTF
+  bool			use_btf;
 #endif
   string		wrong_option;
   string		kernel_dist_root1;
@@ -88,6 +91,10 @@  struct options
 #ifdef WITH_CTF
       ,
       use_ctf(false)
+#endif
+#ifdef WITH_BTF
+    ,
+      use_btf(false)
 #endif
   {}
 }; // end struct options.
@@ -117,6 +124,9 @@  display_usage(const string& prog_name, ostream& out)
     "whitelist\n"
 #ifdef WITH_CTF
     << " --ctf use CTF instead of DWARF in ELF files\n"
+#endif
+#ifdef WITH_BTF
+    << " --btf use BTF instead of DWARF in ELF files\n"
 #endif
     << " --impacted-interfaces|-i  show interfaces impacted by ABI changes\n"
     << " --full-impact|-f  show the full impact of changes on top-most "
@@ -259,6 +269,10 @@  parse_command_line(int argc, char* argv[], options& opts)
 #ifdef WITH_CTF
       else if (!strcmp(argv[i], "--ctf"))
 	opts.use_ctf = true;
+#endif
+#ifdef WITH_BTF
+      else if (!strcmp(argv[i], "--btf"))
+	opts.use_btf = true;
 #endif
       else if (!strcmp(argv[i], "--impacted-interfaces")
 	       || !strcmp(argv[i], "-i"))
@@ -421,11 +435,15 @@  main(int argc, char* argv[])
 
   corpus_group_sptr group1, group2;
   string debug_info_root_dir;
-  corpus::origin requested_fe_kind =
+  corpus::origin requested_fe_kind = corpus::DWARF_ORIGIN;
 #ifdef WITH_CTF
-   opts.use_ctf ? corpus::CTF_ORIGIN :
+  if (opts.use_ctf)
+    requested_fe_kind = corpus::CTF_ORIGIN;
+#endif
+#ifdef WITH_BTF
+  if (opts.use_btf)
+    requested_fe_kind = corpus::BTF_ORIGIN;
 #endif
-   corpus::DWARF_ORIGIN;
 
   if (!opts.kernel_dist_root1.empty())
     {