[llvm] d15f96f - [BPF][DebugInfo] Show CO-RE relocations in llvm-objdump

Eduard Zingerman via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 21 12:00:14 PDT 2023


Author: Eduard Zingerman
Date: 2023-09-21T21:59:10+03:00
New Revision: d15f96fe4b64e24d262fcc727dc0eede327804ba

URL: https://github.com/llvm/llvm-project/commit/d15f96fe4b64e24d262fcc727dc0eede327804ba
DIFF: https://github.com/llvm/llvm-project/commit/d15f96fe4b64e24d262fcc727dc0eede327804ba.diff

LOG: [BPF][DebugInfo] Show CO-RE relocations in llvm-objdump

Extend llvm-objdump to show CO-RE relocations when `-r` option is
passed and object file has .BTF and .BTF.ext sections.

For example, the following C program:

    #define __pai __attribute__((preserve_access_index))

    struct foo { int i; int j;} __pai;
    struct bar { struct foo f[7]; } __pai;
    extern void sink(void *);

    void root(struct bar *bar) {
      sink(&bar[2].f[3].j);
    }

Should lead to the following objdump output:

    $ clang --target=bpf -O2 -g t.c -c -o - | \
        llvm-objdump  --no-addresses --no-show-raw-insn -dr -

    ...
            r2 = 0x94
                    CO-RE <byte_off> [2] struct bar::[2].f[3].j (2:0:3:1)
            r1 += r2
            call -0x1
                    R_BPF_64_32     sink
            exit
    ...

More examples could be found in unit tests, see BTFParserTest.cpp.

To achieve this:
- Move CO-RE relocation kinds definitions from BPFCORE.h to BTF.h.
- Extend BTF.h with types derived from BTF::CommonType, e.g.
  BTF::IntType and BTF::StrutType, to allow dyn_cast() and access to
  type additional data.
- Extend BTFParser to load BTF type and relocation data.
- Modify llvm-objdump.cpp to create instance of BTFParser when
  disassembly of object file with BTF sections is processed and `-r`
  flag is supplied.

Additional information about CO-RE is available at [1].

[1] https://docs.kernel.org/bpf/llvm_reloc.html

Depends on D149058

Differential Revision: https://reviews.llvm.org/D150079

Added: 
    llvm/test/tools/llvm-objdump/BPF/core-relo-byte-offset.ll
    llvm/test/tools/llvm-objdump/BPF/core-relo-enum-value.ll
    llvm/test/tools/llvm-objdump/BPF/core-relo-field-info.ll
    llvm/test/tools/llvm-objdump/BPF/core-relo-formatting.s
    llvm/test/tools/llvm-objdump/BPF/core-relo-type-id.ll
    llvm/test/tools/llvm-objdump/BPF/core-relo-type-info.ll

Modified: 
    llvm/include/llvm/DebugInfo/BTF/BTF.h
    llvm/include/llvm/DebugInfo/BTF/BTFParser.h
    llvm/lib/DebugInfo/BTF/BTFContext.cpp
    llvm/lib/DebugInfo/BTF/BTFParser.cpp
    llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp
    llvm/lib/Target/BPF/BPFCORE.h
    llvm/lib/Target/BPF/BPFPreserveDIType.cpp
    llvm/lib/Target/BPF/BTFDebug.cpp
    llvm/tools/llvm-objdump/CMakeLists.txt
    llvm/tools/llvm-objdump/llvm-objdump.cpp
    llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/DebugInfo/BTF/BTF.h b/llvm/include/llvm/DebugInfo/BTF/BTF.h
index c1c77cd447d6f61..d88af2ff30bdfdb 100644
--- a/llvm/include/llvm/DebugInfo/BTF/BTF.h
+++ b/llvm/include/llvm/DebugInfo/BTF/BTF.h
@@ -48,7 +48,8 @@
 #ifndef LLVM_LIB_TARGET_BPF_BTF_H
 #define LLVM_LIB_TARGET_BPF_BTF_H
 
-#include <cstdint>
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/Support/TrailingObjects.h"
 
 namespace llvm {
 namespace BTF {
@@ -97,6 +98,10 @@ enum TypeKinds : uint8_t {
 #include "BTF.def"
 };
 
+// Constants for CommonType::Info field.
+constexpr uint32_t FWD_UNION_FLAG = 1u << 31;
+constexpr uint32_t ENUM_SIGNED_FLAG = 1u << 31;
+
 /// The BTF common type definition. Different kinds may have
 /// additional information after this structure data.
 struct CommonType {
@@ -106,8 +111,8 @@ struct CommonType {
   /// "Info" bits arrangement:
   /// Bits  0-15: vlen (e.g. # of struct's members)
   /// Bits 16-23: unused
-  /// Bits 24-27: kind (e.g. int, ptr, array...etc)
-  /// Bits 28-30: unused
+  /// Bits 24-28: kind (e.g. int, ptr, array...etc)
+  /// Bits 29-30: unused
   /// Bit     31: kind_flag, currently used by
   ///             struct, union and fwd
   uint32_t Info;
@@ -122,6 +127,9 @@ struct CommonType {
     uint32_t Size;
     uint32_t Type;
   };
+
+  uint32_t getKind() const { return Info >> 24 & 0x1f; }
+  uint32_t getVlen() const { return Info & 0xffff; }
 };
 
 // For some specific BTF_KIND, "struct CommonType" is immediately
@@ -269,6 +277,84 @@ struct SecFieldReloc {
   uint32_t NumFieldReloc; ///< Number of offset reloc's in this section
 };
 
+/// CO-RE relocation kind codes used in .BTF.ext section.
+enum PatchableRelocKind : uint32_t {
+  FIELD_BYTE_OFFSET = 0,
+  FIELD_BYTE_SIZE,
+  FIELD_EXISTENCE,
+  FIELD_SIGNEDNESS,
+  FIELD_LSHIFT_U64,
+  FIELD_RSHIFT_U64,
+  BTF_TYPE_ID_LOCAL,
+  BTF_TYPE_ID_REMOTE,
+  TYPE_EXISTENCE,
+  TYPE_SIZE,
+  ENUM_VALUE_EXISTENCE,
+  ENUM_VALUE,
+  TYPE_MATCH,
+  MAX_FIELD_RELOC_KIND,
+};
+
+// Define a number of sub-types for CommonType, each with:
+// - An accessor for a relevant "tail" information (data fields that
+//   follow the CommonType record in binary format).
+// - A classof() definition based on CommonType::getKind() value to
+//   allow use with dyn_cast<>() function.
+
+// For CommonType sub-types that are followed by a single entry of
+// some type in the binary format.
+#define BTF_DEFINE_TAIL(Type, Accessor)                                        \
+  const Type &Accessor() const { return *getTrailingObjects<Type>(); }
+
+// For CommonType sub-types that are followed by CommonType::getVlen()
+// number of entries of some type in the binary format.
+#define BTF_DEFINE_TAIL_ARR(Type, Accessor)                                    \
+  ArrayRef<Type> Accessor() const {                                            \
+    return ArrayRef<Type>(getTrailingObjects<Type>(), getVlen());              \
+  }
+
+struct ArrayType final : CommonType,
+                         private TrailingObjects<ArrayType, BTFArray> {
+  friend TrailingObjects;
+  BTF_DEFINE_TAIL(BTFArray, getArray)
+
+  static bool classof(const CommonType *V) {
+    return V->getKind() == BTF_KIND_ARRAY;
+  }
+};
+
+struct StructType final : CommonType,
+                          private TrailingObjects<StructType, BTFMember> {
+  friend TrailingObjects;
+  BTF_DEFINE_TAIL_ARR(BTFMember, members)
+
+  static bool classof(const CommonType *V) {
+    return V->getKind() == BTF_KIND_STRUCT || V->getKind() == BTF_KIND_UNION;
+  }
+};
+
+struct EnumType final : CommonType, private TrailingObjects<EnumType, BTFEnum> {
+  friend TrailingObjects;
+  BTF_DEFINE_TAIL_ARR(BTFEnum, values)
+
+  static bool classof(const CommonType *V) {
+    return V->getKind() == BTF_KIND_ENUM;
+  }
+};
+
+struct Enum64Type final : CommonType,
+                          private TrailingObjects<Enum64Type, BTFEnum64> {
+  friend TrailingObjects;
+  BTF_DEFINE_TAIL_ARR(BTFEnum64, values)
+
+  static bool classof(const CommonType *V) {
+    return V->getKind() == BTF_KIND_ENUM64;
+  }
+};
+
+#undef BTF_DEFINE_TAIL
+#undef BTF_DEFINE_TAIL_ARR
+
 } // End namespace BTF.
 } // End namespace llvm.
 

diff  --git a/llvm/include/llvm/DebugInfo/BTF/BTFParser.h b/llvm/include/llvm/DebugInfo/BTF/BTFParser.h
index 33d0f32c7c55f81..53f39919950e521 100644
--- a/llvm/include/llvm/DebugInfo/BTF/BTFParser.h
+++ b/llvm/include/llvm/DebugInfo/BTF/BTFParser.h
@@ -10,7 +10,9 @@
 // BPF backend and provides introspection for the stored information.
 // Currently the following information is accessible:
 // - string table;
-// - instruction offset to line information mapping.
+// - instruction offset to line information mapping;
+// - types table;
+// - CO-RE relocations table.
 //
 // See llvm/DebugInfo/BTF/BTF.h for some details about binary format
 // and links to Linux Kernel documentation.
@@ -32,6 +34,7 @@ using object::SectionRef;
 
 class BTFParser {
   using BTFLinesVector = SmallVector<BTF::BPFLineInfo, 0>;
+  using BTFRelocVector = SmallVector<BTF::BPFFieldReloc, 0>;
 
   // In BTF strings are stored as a continuous memory region with
   // individual strings separated by 0 bytes. Strings are identified
@@ -39,15 +42,34 @@ class BTFParser {
   // The `StringsTable` points to this region in the parsed ObjectFile.
   StringRef StringsTable;
 
+  // A copy of types table from the object file but using native byte
+  // order. Should not be too big in practice, e.g. for ~250MiB vmlinux
+  // image it is ~4MiB.
+  OwningArrayRef<uint8_t> TypesBuffer;
+
   // Maps ELF section number to instruction line number information.
   // Each BTFLinesVector is sorted by `InsnOffset` to allow fast lookups.
   DenseMap<uint64_t, BTFLinesVector> SectionLines;
 
+  // Maps ELF section number to CO-RE relocation information.
+  // Each BTFRelocVector is sorted by `InsnOffset` to allow fast lookups.
+  DenseMap<uint64_t, BTFRelocVector> SectionRelocs;
+
+  // Vector of pointers to all known types, index in this vector
+  // equals to logical type BTF id.
+  // Pointers point to memory owned by `TypesBuffer`
+  // (except pointer at index 0, which is statically allocated).
+  std::vector<const BTF::CommonType *> Types;
+
   struct ParseContext;
   Error parseBTF(ParseContext &Ctx, SectionRef BTF);
   Error parseBTFExt(ParseContext &Ctx, SectionRef BTFExt);
   Error parseLineInfo(ParseContext &Ctx, DataExtractor &Extractor,
                       uint64_t LineInfoStart, uint64_t LineInfoEnd);
+  Error parseRelocInfo(ParseContext &Ctx, DataExtractor &Extractor,
+                       uint64_t RelocInfoStart, uint64_t RelocInfoEnd);
+  Error parseTypesInfo(ParseContext &Ctx, uint64_t TypesInfoStart,
+                       StringRef RawData);
 
 public:
   // Looks-up a string in the .BTF section's string table.
@@ -61,6 +83,34 @@ class BTFParser {
   // owned by this class.
   const BTF::BPFLineInfo *findLineInfo(SectionedAddress Address) const;
 
+  // Search for CO-RE relocation information for a specific address.
+  // Return nullptr if no information found.
+  // If information is present, return a pointer to object
+  // owned by this class.
+  const BTF::BPFFieldReloc *findFieldReloc(SectionedAddress Address) const;
+
+  // Return a human readable representation of the CO-RE relocation
+  // record, this is for display purpose only.
+  // See implementation for details.
+  void symbolize(const BTF::BPFFieldReloc *Reloc,
+                 SmallVectorImpl<char> &Result) const;
+
+  // Lookup BTF type definition with a specific index.
+  // Return nullptr if no information found.
+  // If information is present, return a pointer to object
+  // owned by this class.
+  const BTF::CommonType *findType(uint32_t Id) const;
+
+  // Return total number of known BTF types.
+  size_t typesCount() const { return Types.size(); }
+
+  // Allow to selectively load BTF information.
+  struct ParseOptions {
+    bool LoadLines = false;
+    bool LoadTypes = false;
+    bool LoadRelocs = false;
+  };
+
   // Fills instance of BTFParser with information stored in .BTF and
   // .BTF.ext sections of the `Obj`. If this instance was already
   // filled, old data is discarded.
@@ -70,7 +120,8 @@ class BTFParser {
   // - state of the BTFParser might be incomplete but is not invalid,
   //   queries might be run against it, but some (or all) information
   //   might be unavailable;
-  Error parse(const ObjectFile &Obj);
+  Error parse(const ObjectFile &Obj, const ParseOptions &Opts);
+  Error parse(const ObjectFile &Obj) { return parse(Obj, {true, true, true}); }
 
   // Return true if `Obj` has .BTF and .BTF.ext sections.
   static bool hasBTFSections(const ObjectFile &Obj);

diff  --git a/llvm/lib/DebugInfo/BTF/BTFContext.cpp b/llvm/lib/DebugInfo/BTF/BTFContext.cpp
index 24898739b8241af..2e651cb378dbf0d 100644
--- a/llvm/lib/DebugInfo/BTF/BTFContext.cpp
+++ b/llvm/lib/DebugInfo/BTF/BTFContext.cpp
@@ -63,7 +63,9 @@ std::unique_ptr<BTFContext>
 BTFContext::create(const ObjectFile &Obj,
                    std::function<void(Error)> ErrorHandler) {
   auto Ctx = std::make_unique<BTFContext>();
-  if (Error E = Ctx->BTF.parse(Obj))
+  BTFParser::ParseOptions Opts;
+  Opts.LoadLines = true;
+  if (Error E = Ctx->BTF.parse(Obj, Opts))
     ErrorHandler(std::move(E));
   return Ctx;
 }

diff  --git a/llvm/lib/DebugInfo/BTF/BTFParser.cpp b/llvm/lib/DebugInfo/BTF/BTFParser.cpp
index 6151e1b15cbbf58..a1b7dd396ff1dca 100644
--- a/llvm/lib/DebugInfo/BTF/BTFParser.cpp
+++ b/llvm/lib/DebugInfo/BTF/BTFParser.cpp
@@ -12,6 +12,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/DebugInfo/BTF/BTFParser.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/Errc.h"
 
 #define DEBUG_TYPE "debug-info-btf-parser"
@@ -74,11 +75,13 @@ class Err {
 // Used by BTFParser::parse* auxiliary functions.
 struct BTFParser::ParseContext {
   const ObjectFile &Obj;
+  const ParseOptions &Opts;
   // Map from ELF section name to SectionRef
   DenseMap<StringRef, SectionRef> Sections;
 
 public:
-  ParseContext(const ObjectFile &Obj) : Obj(Obj) {}
+  ParseContext(const ObjectFile &Obj, const ParseOptions &Opts)
+      : Obj(Obj), Opts(Opts) {}
 
   Expected<DataExtractor> makeExtractor(SectionRef Sec) {
     Expected<StringRef> Contents = Sec.getContents();
@@ -119,19 +122,128 @@ Error BTFParser::parseBTF(ParseContext &Ctx, SectionRef BTF) {
     return Err(".BTF", C);
   if (HdrLen < 8)
     return Err("unexpected .BTF header length: ") << HdrLen;
-  (void)Extractor.getU32(C); // type_off
-  (void)Extractor.getU32(C); // type_len
+  uint32_t TypeOff = Extractor.getU32(C);
+  uint32_t TypeLen = Extractor.getU32(C);
   uint32_t StrOff = Extractor.getU32(C);
   uint32_t StrLen = Extractor.getU32(C);
   uint32_t StrStart = HdrLen + StrOff;
   uint32_t StrEnd = StrStart + StrLen;
+  uint32_t TypesInfoStart = HdrLen + TypeOff;
+  uint32_t TypesInfoEnd = TypesInfoStart + TypeLen;
+  uint32_t BytesExpected = std::max(StrEnd, TypesInfoEnd);
   if (!C)
     return Err(".BTF", C);
-  if (Extractor.getData().size() < StrEnd)
+  if (Extractor.getData().size() < BytesExpected)
     return Err("invalid .BTF section size, expecting at-least ")
-           << StrEnd << " bytes";
+           << BytesExpected << " bytes";
+
+  StringsTable = Extractor.getData().slice(StrStart, StrEnd);
+
+  if (TypeLen > 0 && Ctx.Opts.LoadTypes) {
+    StringRef RawData = Extractor.getData().slice(TypesInfoStart, TypesInfoEnd);
+    if (Error E = parseTypesInfo(Ctx, TypesInfoStart, RawData))
+      return E;
+  }
+
+  return Error::success();
+}
+
+// Compute record size for each BTF::CommonType sub-type
+// (including entries in the tail position).
+static size_t byteSize(BTF::CommonType *Type) {
+  size_t Size = sizeof(BTF::CommonType);
+  switch (Type->getKind()) {
+  case BTF::BTF_KIND_INT:
+    Size += sizeof(uint32_t);
+    break;
+  case BTF::BTF_KIND_ARRAY:
+    Size += sizeof(BTF::BTFArray);
+    break;
+  case BTF::BTF_KIND_VAR:
+    Size += sizeof(uint32_t);
+    break;
+  case BTF::BTF_KIND_DECL_TAG:
+    Size += sizeof(uint32_t);
+    break;
+  case BTF::BTF_KIND_STRUCT:
+  case BTF::BTF_KIND_UNION:
+    Size += sizeof(BTF::BTFMember) * Type->getVlen();
+    break;
+  case BTF::BTF_KIND_ENUM:
+    Size += sizeof(BTF::BTFEnum) * Type->getVlen();
+    break;
+  case BTF::BTF_KIND_ENUM64:
+    Size += sizeof(BTF::BTFEnum64) * Type->getVlen();
+    break;
+  case BTF::BTF_KIND_FUNC_PROTO:
+    Size += sizeof(BTF::BTFParam) * Type->getVlen();
+    break;
+  case BTF::BTF_KIND_DATASEC:
+    Size += sizeof(BTF::BTFDataSec) * Type->getVlen();
+    break;
+  }
+  return Size;
+}
+
+// Guard value for voids, simplifies code a bit, but NameOff is not
+// actually valid.
+const BTF::CommonType VoidTypeInst = {0, BTF::BTF_KIND_UNKN << 24, {0}};
+
+// Type information "parsing" is very primitive:
+// - The `RawData` is copied to a buffer owned by `BTFParser` instance.
+// - The buffer is treated as an array of `uint32_t` values, each value
+//   is swapped to use native endianness. This is possible, because
+//   according to BTF spec all buffer elements are structures comprised
+//   of `uint32_t` fields.
+// - `BTFParser::Types` vector is filled with pointers to buffer
+//   elements, using `byteSize()` function to slice the buffer at type
+//   record boundaries.
+// - If at some point a type definition with incorrect size (logical size
+//   exceeding buffer boundaries) is reached it is not added to the
+//   `BTFParser::Types` vector and the process stops.
+Error BTFParser::parseTypesInfo(ParseContext &Ctx, uint64_t TypesInfoStart,
+                                StringRef RawData) {
+  using support::big;
+  using support::endianness;
+  using support::little;
+  using support::endian::byte_swap;
+
+  TypesBuffer = OwningArrayRef<uint8_t>(arrayRefFromStringRef(RawData));
+  // Switch endianness if necessary.
+  endianness Endianness = Ctx.Obj.isLittleEndian() ? little : big;
+  uint32_t *TypesBuffer32 = (uint32_t *)TypesBuffer.data();
+  for (uint64_t I = 0; I < TypesBuffer.size() / 4; ++I)
+    TypesBuffer32[I] = byte_swap(TypesBuffer32[I], Endianness);
+
+  // The type id 0 is reserved for void type.
+  Types.push_back(&VoidTypeInst);
+
+  uint64_t Pos = 0;
+  while (Pos < RawData.size()) {
+    uint64_t BytesLeft = RawData.size() - Pos;
+    uint64_t Offset = TypesInfoStart + Pos;
+    BTF::CommonType *Type = (BTF::CommonType *)&TypesBuffer[Pos];
+    if (BytesLeft < sizeof(*Type))
+      return Err("incomplete type definition in .BTF section:")
+             << " offset " << Offset << ", index " << Types.size();
+
+    uint64_t Size = byteSize(Type);
+    if (BytesLeft < Size)
+      return Err("incomplete type definition in .BTF section:")
+             << " offset=" << Offset << ", index=" << Types.size()
+             << ", vlen=" << Type->getVlen();
+
+    LLVM_DEBUG({
+      llvm::dbgs() << "Adding BTF type:\n"
+                   << "  Id = " << Types.size() << "\n"
+                   << "  Kind = " << Type->getKind() << "\n"
+                   << "  Name = " << findString(Type->NameOff) << "\n"
+                   << "  Record Size = " << Size << "\n";
+    });
+    Types.push_back(Type);
+    Pos += Size;
+  }
 
-  StringsTable = Extractor.getData().substr(StrStart, StrLen);
   return Error::success();
 }
 
@@ -162,12 +274,24 @@ Error BTFParser::parseBTFExt(ParseContext &Ctx, SectionRef BTFExt) {
   (void)Extractor.getU32(C); // func_info_len
   uint32_t LineInfoOff = Extractor.getU32(C);
   uint32_t LineInfoLen = Extractor.getU32(C);
+  uint32_t RelocInfoOff = Extractor.getU32(C);
+  uint32_t RelocInfoLen = Extractor.getU32(C);
   if (!C)
     return Err(".BTF.ext", C);
-  uint32_t LineInfoStart = HdrLen + LineInfoOff;
-  uint32_t LineInfoEnd = LineInfoStart + LineInfoLen;
-  if (Error E = parseLineInfo(Ctx, Extractor, LineInfoStart, LineInfoEnd))
-    return E;
+
+  if (LineInfoLen > 0 && Ctx.Opts.LoadLines) {
+    uint32_t LineInfoStart = HdrLen + LineInfoOff;
+    uint32_t LineInfoEnd = LineInfoStart + LineInfoLen;
+    if (Error E = parseLineInfo(Ctx, Extractor, LineInfoStart, LineInfoEnd))
+      return E;
+  }
+
+  if (RelocInfoLen > 0 && Ctx.Opts.LoadRelocs) {
+    uint32_t RelocInfoStart = HdrLen + RelocInfoOff;
+    uint32_t RelocInfoEnd = RelocInfoStart + RelocInfoLen;
+    if (Error E = parseRelocInfo(Ctx, Extractor, RelocInfoStart, RelocInfoEnd))
+      return E;
+  }
 
   return Error::success();
 }
@@ -214,11 +338,52 @@ Error BTFParser::parseLineInfo(ParseContext &Ctx, DataExtractor &Extractor,
   return Error::success();
 }
 
-Error BTFParser::parse(const ObjectFile &Obj) {
+Error BTFParser::parseRelocInfo(ParseContext &Ctx, DataExtractor &Extractor,
+                                uint64_t RelocInfoStart,
+                                uint64_t RelocInfoEnd) {
+  DataExtractor::Cursor C = DataExtractor::Cursor(RelocInfoStart);
+  uint32_t RecSize = Extractor.getU32(C);
+  if (!C)
+    return Err(".BTF.ext", C);
+  if (RecSize < 16)
+    return Err("unexpected .BTF.ext field reloc info record length: ")
+           << RecSize;
+  while (C && C.tell() < RelocInfoEnd) {
+    uint32_t SecNameOff = Extractor.getU32(C);
+    uint32_t NumInfo = Extractor.getU32(C);
+    StringRef SecName = findString(SecNameOff);
+    std::optional<SectionRef> Sec = Ctx.findSection(SecName);
+    BTFRelocVector &Relocs = SectionRelocs[Sec->getIndex()];
+    for (uint32_t I = 0; C && I < NumInfo; ++I) {
+      uint64_t RecStart = C.tell();
+      uint32_t InsnOff = Extractor.getU32(C);
+      uint32_t TypeID = Extractor.getU32(C);
+      uint32_t OffsetNameOff = Extractor.getU32(C);
+      uint32_t RelocKind = Extractor.getU32(C);
+      if (!C)
+        return Err(".BTF.ext", C);
+      Relocs.push_back({InsnOff, TypeID, OffsetNameOff, RelocKind});
+      C.seek(RecStart + RecSize);
+    }
+    llvm::stable_sort(
+        Relocs, [](const BTF::BPFFieldReloc &L, const BTF::BPFFieldReloc &R) {
+          return L.InsnOffset < R.InsnOffset;
+        });
+  }
+  if (!C)
+    return Err(".BTF.ext", C);
+
+  return Error::success();
+}
+
+Error BTFParser::parse(const ObjectFile &Obj, const ParseOptions &Opts) {
   StringsTable = StringRef();
   SectionLines.clear();
+  SectionRelocs.clear();
+  Types.clear();
+  TypesBuffer = OwningArrayRef<uint8_t>();
 
-  ParseContext Ctx(Obj);
+  ParseContext Ctx(Obj, Opts);
   std::optional<SectionRef> BTF;
   std::optional<SectionRef> BTFExt;
   for (SectionRef Sec : Obj.sections()) {
@@ -264,20 +429,430 @@ StringRef BTFParser::findString(uint32_t Offset) const {
   return StringsTable.slice(Offset, StringsTable.find(0, Offset));
 }
 
-const BTF::BPFLineInfo *
-BTFParser::findLineInfo(SectionedAddress Address) const {
-  auto MaybeSecInfo = SectionLines.find(Address.SectionIndex);
-  if (MaybeSecInfo == SectionLines.end())
+template <typename T>
+static const T *findInfo(const DenseMap<uint64_t, SmallVector<T, 0>> &SecMap,
+                         SectionedAddress Address) {
+  auto MaybeSecInfo = SecMap.find(Address.SectionIndex);
+  if (MaybeSecInfo == SecMap.end())
     return nullptr;
 
-  const BTFLinesVector &SecInfo = MaybeSecInfo->second;
+  const SmallVector<T, 0> &SecInfo = MaybeSecInfo->second;
   const uint64_t TargetOffset = Address.Address;
-  BTFLinesVector::const_iterator LineInfo =
-      llvm::partition_point(SecInfo, [=](const BTF::BPFLineInfo &Line) {
-        return Line.InsnOffset < TargetOffset;
-      });
-  if (LineInfo == SecInfo.end() || LineInfo->InsnOffset != Address.Address)
+  typename SmallVector<T, 0>::const_iterator MaybeInfo = llvm::partition_point(
+      SecInfo, [=](const T &Entry) { return Entry.InsnOffset < TargetOffset; });
+  if (MaybeInfo == SecInfo.end() || MaybeInfo->InsnOffset != Address.Address)
     return nullptr;
 
-  return LineInfo;
+  return &*MaybeInfo;
+}
+
+const BTF::BPFLineInfo *
+BTFParser::findLineInfo(SectionedAddress Address) const {
+  return findInfo(SectionLines, Address);
+}
+
+const BTF::BPFFieldReloc *
+BTFParser::findFieldReloc(SectionedAddress Address) const {
+  return findInfo(SectionRelocs, Address);
+}
+
+const BTF::CommonType *BTFParser::findType(uint32_t Id) const {
+  if (Id < Types.size())
+    return Types[Id];
+  return nullptr;
+}
+
+enum RelocKindGroup {
+  RKG_FIELD,
+  RKG_TYPE,
+  RKG_ENUMVAL,
+  RKG_UNKNOWN,
+};
+
+static RelocKindGroup relocKindGroup(const BTF::BPFFieldReloc *Reloc) {
+  switch (Reloc->RelocKind) {
+  case BTF::FIELD_BYTE_OFFSET:
+  case BTF::FIELD_BYTE_SIZE:
+  case BTF::FIELD_EXISTENCE:
+  case BTF::FIELD_SIGNEDNESS:
+  case BTF::FIELD_LSHIFT_U64:
+  case BTF::FIELD_RSHIFT_U64:
+    return RKG_FIELD;
+  case BTF::BTF_TYPE_ID_LOCAL:
+  case BTF::BTF_TYPE_ID_REMOTE:
+  case BTF::TYPE_EXISTENCE:
+  case BTF::TYPE_MATCH:
+  case BTF::TYPE_SIZE:
+    return RKG_TYPE;
+  case BTF::ENUM_VALUE_EXISTENCE:
+  case BTF::ENUM_VALUE:
+    return RKG_ENUMVAL;
+  default:
+    return RKG_UNKNOWN;
+  }
+}
+
+static bool isMod(const BTF::CommonType *Type) {
+  switch (Type->getKind()) {
+  case BTF::BTF_KIND_VOLATILE:
+  case BTF::BTF_KIND_CONST:
+  case BTF::BTF_KIND_RESTRICT:
+  case BTF::BTF_KIND_TYPE_TAG:
+    return true;
+  default:
+    return false;
+  }
+}
+
+static bool printMod(const BTFParser &BTF, const BTF::CommonType *Type,
+                     raw_ostream &Stream) {
+  switch (Type->getKind()) {
+  case BTF::BTF_KIND_CONST:
+    Stream << " const";
+    break;
+  case BTF::BTF_KIND_VOLATILE:
+    Stream << " volatile";
+    break;
+  case BTF::BTF_KIND_RESTRICT:
+    Stream << " restrict";
+    break;
+  case BTF::BTF_KIND_TYPE_TAG:
+    Stream << " type_tag(\"" << BTF.findString(Type->NameOff) << "\")";
+    break;
+  default:
+    return false;
+  }
+  return true;
+}
+
+static const BTF::CommonType *skipModsAndTypedefs(const BTFParser &BTF,
+                                                  const BTF::CommonType *Type) {
+  while (isMod(Type) || Type->getKind() == BTF::BTF_KIND_TYPEDEF) {
+    auto *Base = BTF.findType(Type->Type);
+    if (!Base)
+      break;
+    Type = Base;
+  }
+  return Type;
+}
+
+namespace {
+struct StrOrAnon {
+  const BTFParser &BTF;
+  uint32_t Offset;
+  uint32_t Idx;
+};
+
+static raw_ostream &operator<<(raw_ostream &Stream, const StrOrAnon &S) {
+  StringRef Str = S.BTF.findString(S.Offset);
+  if (Str.empty())
+    Stream << "<anon " << S.Idx << ">";
+  else
+    Stream << Str;
+  return Stream;
+}
+} // anonymous namespace
+
+static void relocKindName(uint32_t X, raw_ostream &Out) {
+  Out << "<";
+  switch (X) {
+  default:
+    Out << "reloc kind #" << X;
+    break;
+  case BTF::FIELD_BYTE_OFFSET:
+    Out << "byte_off";
+    break;
+  case BTF::FIELD_BYTE_SIZE:
+    Out << "byte_sz";
+    break;
+  case BTF::FIELD_EXISTENCE:
+    Out << "field_exists";
+    break;
+  case BTF::FIELD_SIGNEDNESS:
+    Out << "signed";
+    break;
+  case BTF::FIELD_LSHIFT_U64:
+    Out << "lshift_u64";
+    break;
+  case BTF::FIELD_RSHIFT_U64:
+    Out << "rshift_u64";
+    break;
+  case BTF::BTF_TYPE_ID_LOCAL:
+    Out << "local_type_id";
+    break;
+  case BTF::BTF_TYPE_ID_REMOTE:
+    Out << "target_type_id";
+    break;
+  case BTF::TYPE_EXISTENCE:
+    Out << "type_exists";
+    break;
+  case BTF::TYPE_MATCH:
+    Out << "type_matches";
+    break;
+  case BTF::TYPE_SIZE:
+    Out << "type_size";
+    break;
+  case BTF::ENUM_VALUE_EXISTENCE:
+    Out << "enumval_exists";
+    break;
+  case BTF::ENUM_VALUE:
+    Out << "enumval_value";
+    break;
+  }
+  Out << ">";
+}
+
+// Produces a human readable description of a CO-RE relocation.
+// Such relocations are generated by BPF backend, and processed
+// by libbpf's BPF program loader [1].
+//
+// Each relocation record has the following information:
+// - Relocation kind;
+// - BTF type ID;
+// - Access string offset in string table.
+//
+// There are 
diff erent kinds of relocations, these kinds could be split
+// in three groups:
+// - load-time information about types (size, existence),
+//   `BTFParser::symbolize()` output for such relocations uses the template:
+//
+//     <relocation-kind> [<id>] <type-name>
+//
+//   For example:
+//   - "<type_exists> [7] struct foo"
+//   - "<type_size> [7] struct foo"
+//
+// - load-time information about enums (literal existence, literal value),
+//   `BTFParser::symbolize()` output for such relocations uses the template:
+//
+//     <relocation-kind> [<id>] <type-name>::<literal-name> = <original-value>
+//
+//   For example:
+//   - "<enumval_exists> [5] enum foo::U = 1"
+//   - "<enumval_value> [5] enum foo::V = 2"
+//
+// - load-time information about fields (e.g. field offset),
+//   `BTFParser::symbolize()` output for such relocations uses the template:
+//
+//     <relocation-kind> [<id>] \
+//       <type-name>::[N].<field-1-name>...<field-M-name> \
+//       (<access string>)
+//
+//   For example:
+//   - "<byte_off> [8] struct bar::[7].v (7:1)"
+//   - "<field_exists> [8] struct bar::v (0:1)"
+//
+// If relocation description is not valid output follows the following pattern:
+//
+//     <relocation-kind> <type-id>::<unprocessedaccess-string> <<error-msg>>
+//
+// For example:
+//
+// - "<type_sz> [42] '' <unknown type id: 42>"
+// - "<byte_off> [4] '0:' <field spec too short>"
+//
+// Additional examples could be found in unit tests, see
+// llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp.
+//
+// [1] https://www.kernel.org/doc/html/latest/bpf/libbpf/index.html
+void BTFParser::symbolize(const BTF::BPFFieldReloc *Reloc,
+                          SmallVectorImpl<char> &Result) const {
+  raw_svector_ostream Stream(Result);
+  StringRef FullSpecStr = findString(Reloc->OffsetNameOff);
+  SmallVector<uint32_t, 8> RawSpec;
+
+  auto Fail = [&](auto Msg) {
+    Result.resize(0);
+    relocKindName(Reloc->RelocKind, Stream);
+    Stream << " [" << Reloc->TypeID << "] '" << FullSpecStr << "'"
+           << " <" << Msg << ">";
+  };
+
+  // Relocation access string follows pattern [0-9]+(:[0-9]+)*,
+  // e.g.: 12:22:3. Code below splits `SpecStr` by ':', parses
+  // numbers, and pushes them to `RawSpec`.
+  StringRef SpecStr = FullSpecStr;
+  while (SpecStr.size()) {
+    unsigned long long Val;
+    if (consumeUnsignedInteger(SpecStr, 10, Val))
+      return Fail("spec string is not a number");
+    RawSpec.push_back(Val);
+    if (SpecStr.empty())
+      break;
+    if (SpecStr[0] != ':')
+      return Fail(format("unexpected spec string delimiter: '%c'", SpecStr[0]));
+    SpecStr = SpecStr.substr(1);
+  }
+
+  // Print relocation kind to `Stream`.
+  relocKindName(Reloc->RelocKind, Stream);
+
+  uint32_t CurId = Reloc->TypeID;
+  const BTF::CommonType *Type = findType(CurId);
+  if (!Type)
+    return Fail(format("unknown type id: %d", CurId));
+
+  Stream << " [" << CurId << "]";
+
+  // `Type` might have modifiers, e.g. for type 'const int' the `Type`
+  // would refer to BTF type of kind BTF_KIND_CONST.
+  // Print all these modifiers to `Stream`.
+  for (uint32_t ChainLen = 0; printMod(*this, Type, Stream); ++ChainLen) {
+    if (ChainLen >= 32)
+      return Fail("modifiers chain is too long");
+
+    CurId = Type->Type;
+    const BTF::CommonType *NextType = findType(CurId);
+    if (!NextType)
+      return Fail(format("unknown type id: %d in modifiers chain", CurId));
+    Type = NextType;
+  }
+  // Print the type name to `Stream`.
+  if (CurId == 0) {
+    Stream << " void";
+  } else {
+    switch (Type->getKind()) {
+    case BTF::BTF_KIND_TYPEDEF:
+      Stream << " typedef";
+      break;
+    case BTF::BTF_KIND_STRUCT:
+      Stream << " struct";
+      break;
+    case BTF::BTF_KIND_UNION:
+      Stream << " union";
+      break;
+    case BTF::BTF_KIND_ENUM:
+      Stream << " enum";
+      break;
+    case BTF::BTF_KIND_ENUM64:
+      Stream << " enum";
+      break;
+    case BTF::BTF_KIND_FWD:
+      if (Type->Info & BTF::FWD_UNION_FLAG)
+        Stream << " fwd union";
+      else
+        Stream << " fwd struct";
+      break;
+    default:
+      break;
+    }
+    Stream << " " << StrOrAnon({*this, Type->NameOff, CurId});
+  }
+
+  RelocKindGroup Group = relocKindGroup(Reloc);
+  // Type-based relocations don't use access string but clang backend
+  // generates '0' and libbpf checks it's value, do the same here.
+  if (Group == RKG_TYPE) {
+    if (RawSpec.size() != 1 || RawSpec[0] != 0)
+      return Fail("unexpected type-based relocation spec: should be '0'");
+    return;
+  }
+
+  Stream << "::";
+
+  // For enum-based relocations access string is a single number,
+  // corresponding to the enum literal sequential number.
+  // E.g. for `enum E { U, V }`, relocation requesting value of `V`
+  // would look as follows:
+  // - kind: BTF::ENUM_VALUE
+  // - BTF id: id for `E`
+  // - access string: "1"
+  if (Group == RKG_ENUMVAL) {
+    Type = skipModsAndTypedefs(*this, Type);
+
+    if (RawSpec.size() != 1)
+      return Fail("unexpected enumval relocation spec size");
+
+    uint32_t NameOff;
+    uint64_t Val;
+    uint32_t Idx = RawSpec[0];
+    if (auto *T = dyn_cast<BTF::EnumType>(Type)) {
+      if (T->values().size() <= Idx)
+        return Fail(format("bad value index: %d", Idx));
+      const BTF::BTFEnum &E = T->values()[Idx];
+      NameOff = E.NameOff;
+      Val = E.Val;
+    } else if (auto *T = dyn_cast<BTF::Enum64Type>(Type)) {
+      if (T->values().size() <= Idx)
+        return Fail(format("bad value index: %d", Idx));
+      const BTF::BTFEnum64 &E = T->values()[Idx];
+      NameOff = E.NameOff;
+      Val = (uint64_t)E.Val_Hi32 << 32u | E.Val_Lo32;
+    } else {
+      return Fail(format("unexpected type kind for enum relocation: %d",
+                         Type->getKind()));
+    }
+
+    Stream << StrOrAnon({*this, NameOff, Idx});
+    if (Type->Info & BTF::ENUM_SIGNED_FLAG)
+      Stream << " = " << (int64_t)Val;
+    else
+      Stream << " = " << (uint64_t)Val;
+    return;
+  }
+
+  // For type-based relocations access string is an array of numbers,
+  // which resemble index parameters for `getelementptr` LLVM IR instruction.
+  // E.g. for the following types:
+  //
+  //   struct foo {
+  //     int a;
+  //     int b;
+  //   };
+  //   struct bar {
+  //     int u;
+  //     struct foo v[7];
+  //   };
+  //
+  // Relocation requesting `offsetof(struct bar, v[2].b)` will have
+  // the following access string: 0:1:2:1
+  //                              ^ ^ ^ ^
+  //                              | | | |
+  //                  initial index | | field 'b' is a field #1
+  //                                | | (counting from 0)
+  //                                | array index #2
+  //           field 'v' is a field #1
+  //              (counting from 0)
+  if (Group == RKG_FIELD) {
+    if (RawSpec.size() < 1)
+      return Fail("field spec too short");
+
+    if (RawSpec[0] != 0)
+      Stream << "[" << RawSpec[0] << "]";
+    for (uint32_t I = 1; I < RawSpec.size(); ++I) {
+      Type = skipModsAndTypedefs(*this, Type);
+      uint32_t Idx = RawSpec[I];
+
+      if (auto *T = dyn_cast<BTF::StructType>(Type)) {
+        if (T->getVlen() <= Idx)
+          return Fail(
+              format("member index %d for spec sub-string %d is out of range",
+                     Idx, I));
+
+        const BTF::BTFMember &Member = T->members()[Idx];
+        if (I != 1 || RawSpec[0] != 0)
+          Stream << ".";
+        Stream << StrOrAnon({*this, Member.NameOff, Idx});
+        Type = findType(Member.Type);
+        if (!Type)
+          return Fail(format("unknown member type id %d for spec sub-string %d",
+                             Member.Type, I));
+      } else if (auto *T = dyn_cast<BTF::ArrayType>(Type)) {
+        Stream << "[" << Idx << "]";
+        Type = findType(T->getArray().ElemType);
+        if (!Type)
+          return Fail(
+              format("unknown element type id %d for spec sub-string %d",
+                     T->getArray().ElemType, I));
+      } else {
+        return Fail(format("unexpected type kind %d for spec sub-string %d",
+                           Type->getKind(), I));
+      }
+    }
+
+    Stream << " (" << FullSpecStr << ")";
+    return;
+  }
+
+  return Fail(format("unknown relocation kind: %d", Reloc->RelocKind));
 }

diff  --git a/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp b/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp
index 9c99765b60c080e..a878eaabe00ff5e 100644
--- a/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp
+++ b/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp
@@ -78,6 +78,7 @@
 #include "BPFCORE.h"
 #include "BPFTargetMachine.h"
 #include "llvm/BinaryFormat/Dwarf.h"
+#include "llvm/DebugInfo/BTF/BTF.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/GlobalVariable.h"
 #include "llvm/IR/Instruction.h"
@@ -369,7 +370,7 @@ bool BPFAbstractMemberAccess::IsPreserveDIAccessIndexCall(const CallInst *Call,
     CInfo.Metadata = nullptr;
     // Check validity of info_kind as clang did not check this.
     uint64_t InfoKind = getConstant(Call->getArgOperand(1));
-    if (InfoKind >= BPFCoreSharedInfo::MAX_FIELD_RELOC_KIND)
+    if (InfoKind >= BTF::MAX_FIELD_RELOC_KIND)
       report_fatal_error("Incorrect info_kind for llvm.bpf.preserve.field.info intrinsic");
     CInfo.AccessIndex = InfoKind;
     return true;
@@ -383,11 +384,11 @@ bool BPFAbstractMemberAccess::IsPreserveDIAccessIndexCall(const CallInst *Call,
     if (Flag >= BPFCoreSharedInfo::MAX_PRESERVE_TYPE_INFO_FLAG)
       report_fatal_error("Incorrect flag for llvm.bpf.preserve.type.info intrinsic");
     if (Flag == BPFCoreSharedInfo::PRESERVE_TYPE_INFO_EXISTENCE)
-      CInfo.AccessIndex = BPFCoreSharedInfo::TYPE_EXISTENCE;
+      CInfo.AccessIndex = BTF::TYPE_EXISTENCE;
     else if (Flag == BPFCoreSharedInfo::PRESERVE_TYPE_INFO_MATCH)
-      CInfo.AccessIndex = BPFCoreSharedInfo::TYPE_MATCH;
+      CInfo.AccessIndex = BTF::TYPE_MATCH;
     else
-      CInfo.AccessIndex = BPFCoreSharedInfo::TYPE_SIZE;
+      CInfo.AccessIndex = BTF::TYPE_SIZE;
     return true;
   }
   if (GV->getName().startswith("llvm.bpf.preserve.enum.value")) {
@@ -399,9 +400,9 @@ bool BPFAbstractMemberAccess::IsPreserveDIAccessIndexCall(const CallInst *Call,
     if (Flag >= BPFCoreSharedInfo::MAX_PRESERVE_ENUM_VALUE_FLAG)
       report_fatal_error("Incorrect flag for llvm.bpf.preserve.enum.value intrinsic");
     if (Flag == BPFCoreSharedInfo::PRESERVE_ENUM_VALUE_EXISTENCE)
-      CInfo.AccessIndex = BPFCoreSharedInfo::ENUM_VALUE_EXISTENCE;
+      CInfo.AccessIndex = BTF::ENUM_VALUE_EXISTENCE;
     else
-      CInfo.AccessIndex = BPFCoreSharedInfo::ENUM_VALUE;
+      CInfo.AccessIndex = BTF::ENUM_VALUE;
     return true;
   }
 
@@ -672,11 +673,11 @@ uint32_t BPFAbstractMemberAccess::GetFieldInfo(uint32_t InfoKind,
                                                uint32_t AccessIndex,
                                                uint32_t PatchImm,
                                                MaybeAlign RecordAlignment) {
-  if (InfoKind == BPFCoreSharedInfo::FIELD_EXISTENCE)
-      return 1;
+  if (InfoKind == BTF::FIELD_EXISTENCE)
+    return 1;
 
   uint32_t Tag = CTy->getTag();
-  if (InfoKind == BPFCoreSharedInfo::FIELD_BYTE_OFFSET) {
+  if (InfoKind == BTF::FIELD_BYTE_OFFSET) {
     if (Tag == dwarf::DW_TAG_array_type) {
       auto *EltTy = stripQualifiers(CTy->getBaseType());
       PatchImm += AccessIndex * calcArraySize(CTy, 1) *
@@ -695,7 +696,7 @@ uint32_t BPFAbstractMemberAccess::GetFieldInfo(uint32_t InfoKind,
     return PatchImm;
   }
 
-  if (InfoKind == BPFCoreSharedInfo::FIELD_BYTE_SIZE) {
+  if (InfoKind == BTF::FIELD_BYTE_SIZE) {
     if (Tag == dwarf::DW_TAG_array_type) {
       auto *EltTy = stripQualifiers(CTy->getBaseType());
       return calcArraySize(CTy, 1) * (EltTy->getSizeInBits() >> 3);
@@ -715,7 +716,7 @@ uint32_t BPFAbstractMemberAccess::GetFieldInfo(uint32_t InfoKind,
     }
   }
 
-  if (InfoKind == BPFCoreSharedInfo::FIELD_SIGNEDNESS) {
+  if (InfoKind == BTF::FIELD_SIGNEDNESS) {
     const DIType *BaseTy;
     if (Tag == dwarf::DW_TAG_array_type) {
       // Signedness only checked when final array elements are accessed.
@@ -741,7 +742,7 @@ uint32_t BPFAbstractMemberAccess::GetFieldInfo(uint32_t InfoKind,
     return (Encoding == dwarf::DW_ATE_signed || Encoding == dwarf::DW_ATE_signed_char);
   }
 
-  if (InfoKind == BPFCoreSharedInfo::FIELD_LSHIFT_U64) {
+  if (InfoKind == BTF::FIELD_LSHIFT_U64) {
     // The value is loaded into a value with FIELD_BYTE_SIZE size,
     // and then zero or sign extended to U64.
     // FIELD_LSHIFT_U64 and FIELD_RSHIFT_U64 are operations
@@ -778,7 +779,7 @@ uint32_t BPFAbstractMemberAccess::GetFieldInfo(uint32_t InfoKind,
       return OffsetInBits + 64 - NextSBitOffset;
   }
 
-  if (InfoKind == BPFCoreSharedInfo::FIELD_RSHIFT_U64) {
+  if (InfoKind == BTF::FIELD_RSHIFT_U64) {
     DIDerivedType *MemberTy = nullptr;
     bool IsBitField = false;
     uint32_t SizeInBits;
@@ -849,7 +850,7 @@ Value *BPFAbstractMemberAccess::computeBaseAndAccessKey(CallInst *Call,
   // we will skip them.
   uint32_t FirstIndex = 0;
   uint32_t PatchImm = 0; // AccessOffset or the requested field info
-  uint32_t InfoKind = BPFCoreSharedInfo::FIELD_BYTE_OFFSET;
+  uint32_t InfoKind = BTF::FIELD_BYTE_OFFSET;
   while (CallStack.size()) {
     auto StackElem = CallStack.top();
     Call = StackElem.first;
@@ -939,7 +940,7 @@ Value *BPFAbstractMemberAccess::computeBaseAndAccessKey(CallInst *Call,
 
     if (CInfo.Kind == BPFPreserveFieldInfoAI) {
       InfoKind = CInfo.AccessIndex;
-      if (InfoKind == BPFCoreSharedInfo::FIELD_EXISTENCE)
+      if (InfoKind == BTF::FIELD_EXISTENCE)
         PatchImm = 1;
       break;
     }
@@ -987,10 +988,10 @@ MDNode *BPFAbstractMemberAccess::computeAccessKey(CallInst *Call,
 
   int64_t PatchImm;
   std::string AccessStr("0");
-  if (CInfo.AccessIndex == BPFCoreSharedInfo::TYPE_EXISTENCE ||
-      CInfo.AccessIndex == BPFCoreSharedInfo::TYPE_MATCH) {
+  if (CInfo.AccessIndex == BTF::TYPE_EXISTENCE ||
+      CInfo.AccessIndex == BTF::TYPE_MATCH) {
     PatchImm = 1;
-  } else if (CInfo.AccessIndex == BPFCoreSharedInfo::TYPE_SIZE) {
+  } else if (CInfo.AccessIndex == BTF::TYPE_SIZE) {
     // typedef debuginfo type has size 0, get the eventual base type.
     DIType *BaseTy = stripQualifiers(Ty, true);
     PatchImm = BaseTy->getSizeInBits() / 8;
@@ -1026,7 +1027,7 @@ MDNode *BPFAbstractMemberAccess::computeAccessKey(CallInst *Call,
       EnumIndex++;
     }
 
-    if (CInfo.AccessIndex == BPFCoreSharedInfo::ENUM_VALUE) {
+    if (CInfo.AccessIndex == BTF::ENUM_VALUE) {
       StringRef EValueStr = ValueStr.substr(Separator + 1);
       PatchImm = std::stoll(std::string(EValueStr));
     } else {

diff  --git a/llvm/lib/Target/BPF/BPFCORE.h b/llvm/lib/Target/BPF/BPFCORE.h
index c9aa135232c18b1..9a547a775c96024 100644
--- a/llvm/lib/Target/BPF/BPFCORE.h
+++ b/llvm/lib/Target/BPF/BPFCORE.h
@@ -19,24 +19,6 @@ class Module;
 
 class BPFCoreSharedInfo {
 public:
-  enum PatchableRelocKind : uint32_t {
-    FIELD_BYTE_OFFSET = 0,
-    FIELD_BYTE_SIZE,
-    FIELD_EXISTENCE,
-    FIELD_SIGNEDNESS,
-    FIELD_LSHIFT_U64,
-    FIELD_RSHIFT_U64,
-    BTF_TYPE_ID_LOCAL,
-    BTF_TYPE_ID_REMOTE,
-    TYPE_EXISTENCE,
-    TYPE_SIZE,
-    ENUM_VALUE_EXISTENCE,
-    ENUM_VALUE,
-    TYPE_MATCH,
-
-    MAX_FIELD_RELOC_KIND,
-  };
-
   enum BTFTypeIdFlag : uint32_t {
     BTF_TYPE_ID_LOCAL_RELOC = 0,
     BTF_TYPE_ID_REMOTE_RELOC,

diff  --git a/llvm/lib/Target/BPF/BPFPreserveDIType.cpp b/llvm/lib/Target/BPF/BPFPreserveDIType.cpp
index ec770eecb2e50f5..78e1bf90f1bd524 100644
--- a/llvm/lib/Target/BPF/BPFPreserveDIType.cpp
+++ b/llvm/lib/Target/BPF/BPFPreserveDIType.cpp
@@ -13,6 +13,7 @@
 #include "BPF.h"
 #include "BPFCORE.h"
 #include "llvm/BinaryFormat/Dwarf.h"
+#include "llvm/DebugInfo/BTF/BTF.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/GlobalVariable.h"
 #include "llvm/IR/Instruction.h"
@@ -82,9 +83,9 @@ static bool BPFPreserveDITypeImpl(Function &F) {
 
     uint32_t Reloc;
     if (FlagValue == BPFCoreSharedInfo::BTF_TYPE_ID_LOCAL_RELOC) {
-      Reloc = BPFCoreSharedInfo::BTF_TYPE_ID_LOCAL;
+      Reloc = BTF::BTF_TYPE_ID_LOCAL;
     } else {
-      Reloc = BPFCoreSharedInfo::BTF_TYPE_ID_REMOTE;
+      Reloc = BTF::BTF_TYPE_ID_REMOTE;
       DIType *Ty = cast<DIType>(MD);
       while (auto *DTy = dyn_cast<DIDerivedType>(Ty)) {
         unsigned Tag = DTy->getTag();

diff  --git a/llvm/lib/Target/BPF/BTFDebug.cpp b/llvm/lib/Target/BPF/BTFDebug.cpp
index 2fe4206b194c1c6..f91ce7f250351bb 100644
--- a/llvm/lib/Target/BPF/BTFDebug.cpp
+++ b/llvm/lib/Target/BPF/BTFDebug.cpp
@@ -1518,10 +1518,8 @@ bool BTFDebug::InstLower(const MachineInstr *MI, MCInst &OutMI) {
           return false;
         }
 
-        if (Reloc == BPFCoreSharedInfo::ENUM_VALUE_EXISTENCE ||
-            Reloc == BPFCoreSharedInfo::ENUM_VALUE ||
-            Reloc == BPFCoreSharedInfo::BTF_TYPE_ID_LOCAL ||
-            Reloc == BPFCoreSharedInfo::BTF_TYPE_ID_REMOTE)
+        if (Reloc == BTF::ENUM_VALUE_EXISTENCE || Reloc == BTF::ENUM_VALUE ||
+            Reloc == BTF::BTF_TYPE_ID_LOCAL || Reloc == BTF::BTF_TYPE_ID_REMOTE)
           OutMI.setOpcode(BPF::LD_imm64);
         else
           OutMI.setOpcode(BPF::MOV_ri);

diff  --git a/llvm/test/tools/llvm-objdump/BPF/core-relo-byte-offset.ll b/llvm/test/tools/llvm-objdump/BPF/core-relo-byte-offset.ll
new file mode 100644
index 000000000000000..320152d21e5751a
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-byte-offset.ll
@@ -0,0 +1,154 @@
+; REQUIRES: bpf-registered-target
+
+;; Verify that llvm-objdump can use .BTF.ext to show CO-RE relocation data.
+
+; RUN: llc --mtriple bpfel %s --filetype=obj -o - | \
+; RUN:   llvm-objdump --no-addresses --no-show-raw-insn -dr - | \
+; RUN:   FileCheck %s
+
+; RUN: llc --mtriple bpfeb %s --filetype=obj -o - | \
+; RUN:   llvm-objdump --no-addresses --no-show-raw-insn -dr - | \
+; RUN:   FileCheck %s
+
+;; Input generated from the following C code:
+;;
+;;  #define __pai __attribute__((preserve_access_index))
+;;
+;;  struct buz {
+;;    int a;
+;;    int b;
+;;  } __pai;
+;;
+;;  struct foo {
+;;    int :4;
+;;    int i;
+;;    struct buz k[10];
+;;  } __pai;
+;;
+;;  struct bar {
+;;    struct foo f;
+;;  } __pai;
+;;
+;;  void * volatile g;
+;;
+;;  void root(void) {
+;;    struct bar *bar = (void *)0;
+;;    g = &bar->f;
+;;    g = &bar->f.i;
+;;    g = &bar->f.k;
+;;    g = &bar->f.k[7].a;
+;;    g = &bar->f.k[7].b;
+;;    g = &bar[1].f.k[7].b;
+;;  }
+;;
+;; Using the following command:
+;;
+;;  clang --target=bpf -g -O2 -emit-llvm -S t.c
+
+; CHECK: CO-RE <byte_off> [[[#bar:]]] struct bar::f (0:0)
+; CHECK: CO-RE <byte_off> [[[#bar]]] struct bar::f.i (0:0:0)
+; CHECK: CO-RE <byte_off> [[[#bar]]] struct bar::f.k (0:0:1)
+; CHECK: CO-RE <byte_off> [[[#bar]]] struct bar::f.k[7].a (0:0:1:7:0)
+; CHECK: CO-RE <byte_off> [[[#bar]]] struct bar::f.k[7].b (0:0:1:7:1)
+; CHECK: CO-RE <byte_off> [[[#bar]]] struct bar::[1].f.k[7].b (1:0:1:7:1)
+
+ at g = dso_local global ptr null, align 8, !dbg !0
+@"llvm.bar:0:0$0:0" = external global i64, !llvm.preserve.access.index !14 #0
+@"llvm.bar:0:8$0:0:1" = external global i64, !llvm.preserve.access.index !14 #0
+@"llvm.bar:0:4$0:0:0" = external global i64, !llvm.preserve.access.index !14 #0
+@"llvm.bar:0:64$0:0:1:7:0" = external global i64, !llvm.preserve.access.index !14 #0
+@"llvm.bar:0:68$0:0:1:7:1" = external global i64, !llvm.preserve.access.index !14 #0
+@"llvm.bar:0:156$1:0:1:7:1" = external global i64, !llvm.preserve.access.index !14 #0
+
+; Function Attrs: nofree nounwind memory(readwrite, argmem: none)
+define dso_local void @root() local_unnamed_addr #1 !dbg !29 {
+entry:
+  call void @llvm.dbg.value(metadata ptr null, metadata !33, metadata !DIExpression()), !dbg !34
+  %0 = load i64, ptr @"llvm.bar:0:0$0:0", align 8
+  %1 = getelementptr i8, ptr null, i64 %0
+  %2 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 0, ptr %1)
+  store volatile ptr %2, ptr @g, align 8, !dbg !35, !tbaa !36
+  %3 = load i64, ptr @"llvm.bar:0:4$0:0:0", align 8
+  %4 = getelementptr i8, ptr null, i64 %3
+  %5 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 2, ptr %4)
+  store volatile ptr %5, ptr @g, align 8, !dbg !40, !tbaa !36
+  %6 = load i64, ptr @"llvm.bar:0:8$0:0:1", align 8
+  %7 = getelementptr i8, ptr null, i64 %6
+  %8 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 1, ptr %7)
+  store volatile ptr %8, ptr @g, align 8, !dbg !41, !tbaa !36
+  %9 = load i64, ptr @"llvm.bar:0:64$0:0:1:7:0", align 8
+  %10 = getelementptr i8, ptr null, i64 %9
+  %11 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 3, ptr %10)
+  store volatile ptr %11, ptr @g, align 8, !dbg !42, !tbaa !36
+  %12 = load i64, ptr @"llvm.bar:0:68$0:0:1:7:1", align 8
+  %13 = getelementptr i8, ptr null, i64 %12
+  %14 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 4, ptr %13)
+  store volatile ptr %14, ptr @g, align 8, !dbg !43, !tbaa !36
+  %15 = load i64, ptr @"llvm.bar:0:156$1:0:1:7:1", align 8
+  %16 = getelementptr i8, ptr null, i64 %15
+  %17 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 5, ptr %16)
+  store volatile ptr %17, ptr @g, align 8, !dbg !44, !tbaa !36
+  ret void, !dbg !45
+}
+
+; Function Attrs: nofree nosync nounwind memory(none)
+declare ptr @llvm.bpf.passthrough.p0.p0(i32, ptr) #2
+
+; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
+declare void @llvm.dbg.value(metadata, metadata, metadata) #3
+
+attributes #0 = { "btf_ama" }
+attributes #1 = { nofree nounwind memory(readwrite, argmem: none) "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
+attributes #2 = { nofree nosync nounwind memory(none) }
+attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!24, !25, !26, !27}
+!llvm.ident = !{!28}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 18, type: !22, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, retainedTypes: !4, globals: !21, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "t.c", directory: "/home/eddy/work/tmp", checksumkind: CSK_MD5, checksum: "f7c638151153f385e69bef98e88c80ef")
+!4 = !{!5, !13}
+!5 = !DICompositeType(tag: DW_TAG_array_type, baseType: !6, size: 640, elements: !11)
+!6 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "buz", file: !3, line: 3, size: 64, elements: !7)
+!7 = !{!8, !10}
+!8 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !6, file: !3, line: 4, baseType: !9, size: 32)
+!9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!10 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !6, file: !3, line: 5, baseType: !9, size: 32, offset: 32)
+!11 = !{!12}
+!12 = !DISubrange(count: 10)
+!13 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !14, size: 64)
+!14 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "bar", file: !3, line: 14, size: 704, elements: !15)
+!15 = !{!16}
+!16 = !DIDerivedType(tag: DW_TAG_member, name: "f", scope: !14, file: !3, line: 15, baseType: !17, size: 704)
+!17 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "foo", file: !3, line: 8, size: 704, elements: !18)
+!18 = !{!19, !20}
+!19 = !DIDerivedType(tag: DW_TAG_member, name: "i", scope: !17, file: !3, line: 10, baseType: !9, size: 32, offset: 32)
+!20 = !DIDerivedType(tag: DW_TAG_member, name: "k", scope: !17, file: !3, line: 11, baseType: !5, size: 640, offset: 64)
+!21 = !{!0}
+!22 = !DIDerivedType(tag: DW_TAG_volatile_type, baseType: !23)
+!23 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)
+!24 = !{i32 7, !"Dwarf Version", i32 5}
+!25 = !{i32 2, !"Debug Info Version", i32 3}
+!26 = !{i32 1, !"wchar_size", i32 4}
+!27 = !{i32 7, !"frame-pointer", i32 2}
+!28 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)"}
+!29 = distinct !DISubprogram(name: "root", scope: !3, file: !3, line: 20, type: !30, scopeLine: 20, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !32)
+!30 = !DISubroutineType(types: !31)
+!31 = !{null}
+!32 = !{!33}
+!33 = !DILocalVariable(name: "bar", scope: !29, file: !3, line: 21, type: !13)
+!34 = !DILocation(line: 0, scope: !29)
+!35 = !DILocation(line: 22, column: 5, scope: !29)
+!36 = !{!37, !37, i64 0}
+!37 = !{!"any pointer", !38, i64 0}
+!38 = !{!"omnipotent char", !39, i64 0}
+!39 = !{!"Simple C/C++ TBAA"}
+!40 = !DILocation(line: 23, column: 5, scope: !29)
+!41 = !DILocation(line: 24, column: 5, scope: !29)
+!42 = !DILocation(line: 25, column: 5, scope: !29)
+!43 = !DILocation(line: 26, column: 5, scope: !29)
+!44 = !DILocation(line: 27, column: 5, scope: !29)
+!45 = !DILocation(line: 28, column: 1, scope: !29)

diff  --git a/llvm/test/tools/llvm-objdump/BPF/core-relo-enum-value.ll b/llvm/test/tools/llvm-objdump/BPF/core-relo-enum-value.ll
new file mode 100644
index 000000000000000..2a3f3b19641a807
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-enum-value.ll
@@ -0,0 +1,86 @@
+; REQUIRES: bpf-registered-target
+
+;; Verify that llvm-objdump can use .BTF.ext to show CO-RE relocation data.
+
+; RUN: llc --mtriple bpfel %s --filetype=obj -o - | \
+; RUN:   llvm-objdump --no-addresses --no-show-raw-insn -dr - | \
+; RUN:   FileCheck %s
+
+; RUN: llc --mtriple bpfeb %s --filetype=obj -o - | \
+; RUN:   llvm-objdump --no-addresses --no-show-raw-insn -dr - | \
+; RUN:   FileCheck %s
+
+;; Input generated from the following C code:
+;;
+;;  #define __pai __attribute__((preserve_access_index))
+;;
+;;  enum bar { U, V };
+;;  volatile unsigned long g;
+;;  void root(void) {
+;;    g = __builtin_preserve_enum_value(*(enum bar *)U, 0);
+;;    g = __builtin_preserve_enum_value(*(enum bar *)V, 1);
+;;  }
+;;
+;; Using the following command:
+;;
+;;  clang --target=bpf -g -O2 -emit-llvm -S t.c
+
+; CHECK: CO-RE <enumval_exists> [[[#]]] enum bar::U = 0
+; CHECK: CO-RE <enumval_value> [[[#]]] enum bar::V = 1
+
+ at g = dso_local global i64 0, align 8, !dbg !0
+@"llvm.bar:11:1$1" = external global i64, !llvm.preserve.access.index !5 #0
+@"llvm.bar:10:1$0" = external global i64, !llvm.preserve.access.index !5 #0
+
+; Function Attrs: nofree nounwind memory(readwrite, argmem: none)
+define dso_local void @root() local_unnamed_addr #1 !dbg !18 {
+entry:
+  %0 = load i64, ptr @"llvm.bar:10:1$0", align 8
+  %1 = tail call i64 @llvm.bpf.passthrough.i64.i64(i32 1, i64 %0)
+  store volatile i64 %1, ptr @g, align 8, !dbg !22, !tbaa !23
+  %2 = load i64, ptr @"llvm.bar:11:1$1", align 8
+  %3 = tail call i64 @llvm.bpf.passthrough.i64.i64(i32 0, i64 %2)
+  store volatile i64 %3, ptr @g, align 8, !dbg !27, !tbaa !23
+  ret void, !dbg !28
+}
+
+; Function Attrs: nofree nosync nounwind memory(none)
+declare i64 @llvm.bpf.passthrough.i64.i64(i32, i64) #2
+
+attributes #0 = { "btf_ama" }
+attributes #1 = { nofree nounwind memory(readwrite, argmem: none) "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
+attributes #2 = { nofree nosync nounwind memory(none) }
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!13, !14, !15, !16}
+!llvm.ident = !{!17}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 4, type: !11, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !10, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "t.c", directory: "/home/eddy/work/tmp", checksumkind: CSK_MD5, checksum: "5423aa9ef48cb61e948b5c2bd75fd1df")
+!4 = !{!5}
+!5 = !DICompositeType(tag: DW_TAG_enumeration_type, name: "bar", file: !3, line: 3, baseType: !6, size: 32, elements: !7)
+!6 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned)
+!7 = !{!8, !9}
+!8 = !DIEnumerator(name: "U", value: 0)
+!9 = !DIEnumerator(name: "V", value: 1)
+!10 = !{!0}
+!11 = !DIDerivedType(tag: DW_TAG_volatile_type, baseType: !12)
+!12 = !DIBasicType(name: "unsigned long", size: 64, encoding: DW_ATE_unsigned)
+!13 = !{i32 7, !"Dwarf Version", i32 5}
+!14 = !{i32 2, !"Debug Info Version", i32 3}
+!15 = !{i32 1, !"wchar_size", i32 4}
+!16 = !{i32 7, !"frame-pointer", i32 2}
+!17 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)"}
+!18 = distinct !DISubprogram(name: "root", scope: !3, file: !3, line: 5, type: !19, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !21)
+!19 = !DISubroutineType(types: !20)
+!20 = !{null}
+!21 = !{}
+!22 = !DILocation(line: 6, column: 5, scope: !18)
+!23 = !{!24, !24, i64 0}
+!24 = !{!"long", !25, i64 0}
+!25 = !{!"omnipotent char", !26, i64 0}
+!26 = !{!"Simple C/C++ TBAA"}
+!27 = !DILocation(line: 7, column: 5, scope: !18)
+!28 = !DILocation(line: 8, column: 1, scope: !18)

diff  --git a/llvm/test/tools/llvm-objdump/BPF/core-relo-field-info.ll b/llvm/test/tools/llvm-objdump/BPF/core-relo-field-info.ll
new file mode 100644
index 000000000000000..7878e04c4b68465
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-field-info.ll
@@ -0,0 +1,124 @@
+; REQUIRES: bpf-registered-target
+
+;; Verify that llvm-objdump can use .BTF.ext to show CO-RE relocation data.
+
+; RUN: llc --mtriple bpfel %s --filetype=obj -o - | \
+; RUN:   llvm-objdump --no-addresses --no-show-raw-insn -dr - | \
+; RUN:   FileCheck %s
+
+; RUN: llc --mtriple bpfeb %s --filetype=obj -o - | \
+; RUN:   llvm-objdump --no-addresses --no-show-raw-insn -dr - | \
+; RUN:   FileCheck %s
+
+;; Input generated from the following C code:
+;;
+;;  #define __pai __attribute__((preserve_access_index))
+;;
+;;  struct bar { int a; } __pai;
+;;  volatile unsigned long g;
+;;  void root(void) {
+;;    struct bar *bar = (void *)0;
+;;    g = __builtin_preserve_field_info(bar->a, 1);
+;;    g = __builtin_preserve_field_info(bar->a, 2);
+;;    g = __builtin_preserve_field_info(bar->a, 3);
+;;    g = __builtin_preserve_field_info(bar->a, 4);
+;;    g = __builtin_preserve_field_info(bar->a, 5);
+;;  }
+;;
+;; Using the following command:
+;;
+;;  clang --target=bpf -g -O2 -emit-llvm -S t.c
+
+; CHECK: CO-RE <byte_sz> [[[#]]] struct bar::a
+; CHECK: CO-RE <field_exists> [[[#]]] struct bar::a
+; CHECK: CO-RE <signed> [[[#]]] struct bar::a
+; CHECK: CO-RE <lshift_u64> [[[#]]] struct bar::a
+; CHECK: CO-RE <rshift_u64> [[[#]]] struct bar::a
+
+ at g = dso_local global i64 0, align 8, !dbg !0
+@"llvm.bar:1:4$0:0" = external global i32, !llvm.preserve.access.index !7 #0
+@"llvm.bar:2:1$0:0" = external global i32, !llvm.preserve.access.index !7 #0
+@"llvm.bar:3:1$0:0" = external global i32, !llvm.preserve.access.index !7 #0
+@"llvm.bar:4:32$0:0" = external global i32, !llvm.preserve.access.index !7 #0
+@"llvm.bar:5:32$0:0" = external global i32, !llvm.preserve.access.index !7 #0
+
+; Function Attrs: nofree nounwind memory(readwrite, argmem: none)
+define dso_local void @root() local_unnamed_addr #1 !dbg !16 {
+entry:
+  call void @llvm.dbg.value(metadata ptr null, metadata !20, metadata !DIExpression()), !dbg !22
+  %0 = load i32, ptr @"llvm.bar:1:4$0:0", align 4
+  %1 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 0, i32 %0)
+  %conv = zext i32 %1 to i64, !dbg !23
+  store volatile i64 %conv, ptr @g, align 8, !dbg !24, !tbaa !25
+  %2 = load i32, ptr @"llvm.bar:2:1$0:0", align 4
+  %3 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 1, i32 %2)
+  %conv1 = zext i32 %3 to i64, !dbg !29
+  store volatile i64 %conv1, ptr @g, align 8, !dbg !30, !tbaa !25
+  %4 = load i32, ptr @"llvm.bar:3:1$0:0", align 4
+  %5 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 2, i32 %4)
+  %conv2 = zext i32 %5 to i64, !dbg !31
+  store volatile i64 %conv2, ptr @g, align 8, !dbg !32, !tbaa !25
+  %6 = load i32, ptr @"llvm.bar:4:32$0:0", align 4
+  %7 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 3, i32 %6)
+  %conv3 = zext i32 %7 to i64, !dbg !33
+  store volatile i64 %conv3, ptr @g, align 8, !dbg !34, !tbaa !25
+  %8 = load i32, ptr @"llvm.bar:5:32$0:0", align 4
+  %9 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 4, i32 %8)
+  %conv4 = zext i32 %9 to i64, !dbg !35
+  store volatile i64 %conv4, ptr @g, align 8, !dbg !36, !tbaa !25
+  ret void, !dbg !37
+}
+
+; Function Attrs: nofree nosync nounwind memory(none)
+declare i32 @llvm.bpf.passthrough.i32.i32(i32, i32) #2
+
+; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
+declare void @llvm.dbg.value(metadata, metadata, metadata) #3
+
+attributes #0 = { "btf_ama" }
+attributes #1 = { nofree nounwind memory(readwrite, argmem: none) "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
+attributes #2 = { nofree nosync nounwind memory(none) }
+attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!11, !12, !13, !14}
+!llvm.ident = !{!15}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 4, type: !5, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "t.c", directory: "/home/eddy/work/tmp", checksumkind: CSK_MD5, checksum: "ff78616039301f51cd56ee6ea1377b86")
+!4 = !{!0}
+!5 = !DIDerivedType(tag: DW_TAG_volatile_type, baseType: !6)
+!6 = !DIBasicType(name: "unsigned long", size: 64, encoding: DW_ATE_unsigned)
+!7 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "bar", file: !3, line: 3, size: 32, elements: !8)
+!8 = !{!9}
+!9 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !7, file: !3, line: 3, baseType: !10, size: 32)
+!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!11 = !{i32 7, !"Dwarf Version", i32 5}
+!12 = !{i32 2, !"Debug Info Version", i32 3}
+!13 = !{i32 1, !"wchar_size", i32 4}
+!14 = !{i32 7, !"frame-pointer", i32 2}
+!15 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)"}
+!16 = distinct !DISubprogram(name: "root", scope: !3, file: !3, line: 5, type: !17, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !19)
+!17 = !DISubroutineType(types: !18)
+!18 = !{null}
+!19 = !{!20}
+!20 = !DILocalVariable(name: "bar", scope: !16, file: !3, line: 6, type: !21)
+!21 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !7, size: 64)
+!22 = !DILocation(line: 0, scope: !16)
+!23 = !DILocation(line: 7, column: 7, scope: !16)
+!24 = !DILocation(line: 7, column: 5, scope: !16)
+!25 = !{!26, !26, i64 0}
+!26 = !{!"long", !27, i64 0}
+!27 = !{!"omnipotent char", !28, i64 0}
+!28 = !{!"Simple C/C++ TBAA"}
+!29 = !DILocation(line: 8, column: 7, scope: !16)
+!30 = !DILocation(line: 8, column: 5, scope: !16)
+!31 = !DILocation(line: 9, column: 7, scope: !16)
+!32 = !DILocation(line: 9, column: 5, scope: !16)
+!33 = !DILocation(line: 10, column: 7, scope: !16)
+!34 = !DILocation(line: 10, column: 5, scope: !16)
+!35 = !DILocation(line: 11, column: 7, scope: !16)
+!36 = !DILocation(line: 11, column: 5, scope: !16)
+!37 = !DILocation(line: 12, column: 1, scope: !16)

diff  --git a/llvm/test/tools/llvm-objdump/BPF/core-relo-formatting.s b/llvm/test/tools/llvm-objdump/BPF/core-relo-formatting.s
new file mode 100644
index 000000000000000..98cd31f6406a4f8
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-formatting.s
@@ -0,0 +1,614 @@
+# REQUIRES: bpf-registered-target
+
+## Verify that when llvm-objdump uses .BTF.ext to show CO-RE
+## relocations formatting options operate as expected.
+
+# RUN: llvm-mc --triple bpfel %s --filetype=obj | \
+# RUN:   llvm-objdump --no-addresses --no-show-raw-insn -dr - | \
+# RUN:   FileCheck --strict-whitespace --check-prefix=NOADDR %s
+
+# RUN: llvm-mc --triple bpfel %s --filetype=obj | \
+# RUN:   llvm-objdump --no-addresses --no-show-raw-insn -d - | \
+# RUN:   FileCheck --strict-whitespace --check-prefix=NORELO %s
+
+# RUN: llvm-mc --triple bpfel %s --filetype=obj | \
+# RUN:   llvm-objdump --no-show-raw-insn -dr - | \
+# RUN:   FileCheck --strict-whitespace --check-prefix=ADDR %s
+
+# RUN: llvm-mc --triple bpfel %s --filetype=obj | \
+# RUN:   llvm-objdump --adjust-vma=0x10 --no-show-raw-insn -dr - | \
+# RUN:   FileCheck --strict-whitespace --check-prefix=VMA %s
+
+## Input generated from the following C code:
+##
+##   #define __pai __attribute__((preserve_access_index))
+##   struct foo {
+##     int a;
+##   } __pai;
+##   enum bar { U, V };
+##   extern void consume(unsigned long);
+##   void root() {
+##     asm volatile("r0 = 42;":::);
+##     struct foo *foo = 0;
+##     consume(__builtin_preserve_type_info(*foo, 0));
+##     consume((unsigned long) &foo->a);
+##     consume(__builtin_preserve_enum_value(*(enum bar *)U, 0));
+##   }
+##
+## Using the following command:
+##
+##  clang -target bpf -g -O2 -S t.c
+
+# NOADDR:	r1 = 0x1
+# NOADDR-NEXT:		CO-RE <type_exists> [3] struct foo
+# NOADDR-NEXT:	call -0x1
+# NOADDR-NEXT:		R_BPF_64_32	consume
+# NOADDR-NEXT:	r1 = 0x0
+# NOADDR-NEXT:		CO-RE <byte_off> [3] struct foo::a (0:0)
+# NOADDR-NEXT:	call -0x1
+# NOADDR-NEXT:		R_BPF_64_32	consume
+# NOADDR-NEXT:	r1 = 0x1 ll
+# NOADDR-NEXT:		CO-RE <enumval_exists> [8] enum bar::U = 0
+# NOADDR-NEXT:	call -0x1
+# NOADDR-NEXT:		R_BPF_64_32	consume
+# NOADDR-NEXT:	exit
+
+# NORELO:	r1 = 0x1
+# NORELO-NEXT:	call -0x1
+# NORELO-NEXT:	r1 = 0x0
+# NORELO-NEXT:	call -0x1
+# NORELO-NEXT:	r1 = 0x1 ll
+# NORELO-NEXT:	call -0x1
+# NORELO-NEXT:	exit
+
+# ADDR:            1:	r1 = 0x1
+# ADDR-NEXT:		0000000000000008:  CO-RE <type_exists> [3] struct foo
+# ADDR-NEXT:       2:	call -0x1
+# ADDR-NEXT:		0000000000000010:  R_BPF_64_32	consume
+# ADDR-NEXT:       3:	r1 = 0x0
+# ADDR-NEXT:		0000000000000018:  CO-RE <byte_off> [3] struct foo::a (0:0)
+# ADDR-NEXT:       4:	call -0x1
+# ADDR-NEXT:		0000000000000020:  R_BPF_64_32	consume
+# ADDR-NEXT:       5:	r1 = 0x1 ll
+# ADDR-NEXT:		0000000000000028:  CO-RE <enumval_exists> [8] enum bar::U = 0
+# ADDR-NEXT:       7:	call -0x1
+# ADDR-NEXT:		0000000000000038:  R_BPF_64_32	consume
+# ADDR-NEXT:       8:	exit
+
+# VMA:            3:	r1 = 0x1
+# VMA-NEXT:		0000000000000018:  CO-RE <type_exists> [3] struct foo
+# VMA-NEXT:       4:	call -0x1
+# VMA-NEXT:		0000000000000010:  R_BPF_64_32	consume
+# VMA-NEXT:       5:	r1 = 0x0
+# VMA-NEXT:		0000000000000028:  CO-RE <byte_off> [3] struct foo::a (0:0)
+# VMA-NEXT:       6:	call -0x1
+# VMA-NEXT:		0000000000000020:  R_BPF_64_32	consume
+# VMA-NEXT:       7:	r1 = 0x1 ll
+# VMA-NEXT:		0000000000000038:  CO-RE <enumval_exists> [8] enum bar::U = 0
+# VMA-NEXT:       9:	call -0x1
+# VMA-NEXT:		0000000000000038:  R_BPF_64_32	consume
+# VMA-NEXT:      10:	exit
+
+	.text
+	.file	"t.c"
+	.file	0 "/home/eddy/work/tmp" "t.c" md5 0x7675be79a30f35c69b89cf826ff55a5f
+	.globl	root                    # -- Begin function root
+	.p2align	3
+	.type	root, at function
+root:                                   # @root
+.Lfunc_begin0:
+	.cfi_sections .debug_frame
+	.cfi_startproc
+# %bb.0:                                # %entry
+	.loc	0 8 3 prologue_end      # t.c:8:3
+.Ltmp0:
+	#APP
+	r0 = 42
+
+	#NO_APP
+.Ltmp1:
+.Ltmp2:
+	#DEBUG_VALUE: root:foo <- 0
+	.loc	0 10 3                  # t.c:10:3
+.Ltmp3:
+.Ltmp4:
+	r1 = 1
+	call consume
+.Ltmp5:
+	.loc	0 11 3                  # t.c:11:3
+.Ltmp6:
+.Ltmp7:
+	r1 = 0
+	call consume
+.Ltmp8:
+	.loc	0 12 3                  # t.c:12:3
+.Ltmp9:
+.Ltmp10:
+	r1 = 1 ll
+	call consume
+.Ltmp11:
+	.loc	0 13 1                  # t.c:13:1
+.Ltmp12:
+	exit
+.Ltmp13:
+.Ltmp14:
+.Lfunc_end0:
+	.size	root, .Lfunc_end0-root
+	.cfi_endproc
+                                        # -- End function
+	.section	.debug_loclists,"", at progbits
+	.long	.Ldebug_list_header_end0-.Ldebug_list_header_start0 # Length
+.Ldebug_list_header_start0:
+	.short	5                       # Version
+	.byte	8                       # Address size
+	.byte	0                       # Segment selector size
+	.long	1                       # Offset entry count
+.Lloclists_table_base0:
+	.long	.Ldebug_loc0-.Lloclists_table_base0
+.Ldebug_loc0:
+	.byte	4                       # DW_LLE_offset_pair
+	.uleb128 .Ltmp1-.Lfunc_begin0   #   starting offset
+	.uleb128 .Lfunc_end0-.Lfunc_begin0 #   ending offset
+	.byte	2                       # Loc expr size
+	.byte	48                      # DW_OP_lit0
+	.byte	159                     # DW_OP_stack_value
+	.byte	0                       # DW_LLE_end_of_list
+.Ldebug_list_header_end0:
+	.section	.debug_abbrev,"", at progbits
+	.byte	1                       # Abbreviation Code
+	.byte	17                      # DW_TAG_compile_unit
+	.byte	1                       # DW_CHILDREN_yes
+	.byte	37                      # DW_AT_producer
+	.byte	37                      # DW_FORM_strx1
+	.byte	19                      # DW_AT_language
+	.byte	5                       # DW_FORM_data2
+	.byte	3                       # DW_AT_name
+	.byte	37                      # DW_FORM_strx1
+	.byte	114                     # DW_AT_str_offsets_base
+	.byte	23                      # DW_FORM_sec_offset
+	.byte	16                      # DW_AT_stmt_list
+	.byte	23                      # DW_FORM_sec_offset
+	.byte	27                      # DW_AT_comp_dir
+	.byte	37                      # DW_FORM_strx1
+	.byte	17                      # DW_AT_low_pc
+	.byte	27                      # DW_FORM_addrx
+	.byte	18                      # DW_AT_high_pc
+	.byte	6                       # DW_FORM_data4
+	.byte	115                     # DW_AT_addr_base
+	.byte	23                      # DW_FORM_sec_offset
+	.ascii	"\214\001"              # DW_AT_loclists_base
+	.byte	23                      # DW_FORM_sec_offset
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	2                       # Abbreviation Code
+	.byte	4                       # DW_TAG_enumeration_type
+	.byte	1                       # DW_CHILDREN_yes
+	.byte	73                      # DW_AT_type
+	.byte	19                      # DW_FORM_ref4
+	.byte	3                       # DW_AT_name
+	.byte	37                      # DW_FORM_strx1
+	.byte	11                      # DW_AT_byte_size
+	.byte	11                      # DW_FORM_data1
+	.byte	58                      # DW_AT_decl_file
+	.byte	11                      # DW_FORM_data1
+	.byte	59                      # DW_AT_decl_line
+	.byte	11                      # DW_FORM_data1
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	3                       # Abbreviation Code
+	.byte	40                      # DW_TAG_enumerator
+	.byte	0                       # DW_CHILDREN_no
+	.byte	3                       # DW_AT_name
+	.byte	37                      # DW_FORM_strx1
+	.byte	28                      # DW_AT_const_value
+	.byte	15                      # DW_FORM_udata
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	4                       # Abbreviation Code
+	.byte	36                      # DW_TAG_base_type
+	.byte	0                       # DW_CHILDREN_no
+	.byte	3                       # DW_AT_name
+	.byte	37                      # DW_FORM_strx1
+	.byte	62                      # DW_AT_encoding
+	.byte	11                      # DW_FORM_data1
+	.byte	11                      # DW_AT_byte_size
+	.byte	11                      # DW_FORM_data1
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	5                       # Abbreviation Code
+	.byte	46                      # DW_TAG_subprogram
+	.byte	1                       # DW_CHILDREN_yes
+	.byte	17                      # DW_AT_low_pc
+	.byte	27                      # DW_FORM_addrx
+	.byte	18                      # DW_AT_high_pc
+	.byte	6                       # DW_FORM_data4
+	.byte	64                      # DW_AT_frame_base
+	.byte	24                      # DW_FORM_exprloc
+	.byte	122                     # DW_AT_call_all_calls
+	.byte	25                      # DW_FORM_flag_present
+	.byte	3                       # DW_AT_name
+	.byte	37                      # DW_FORM_strx1
+	.byte	58                      # DW_AT_decl_file
+	.byte	11                      # DW_FORM_data1
+	.byte	59                      # DW_AT_decl_line
+	.byte	11                      # DW_FORM_data1
+	.byte	63                      # DW_AT_external
+	.byte	25                      # DW_FORM_flag_present
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	6                       # Abbreviation Code
+	.byte	52                      # DW_TAG_variable
+	.byte	0                       # DW_CHILDREN_no
+	.byte	2                       # DW_AT_location
+	.byte	34                      # DW_FORM_loclistx
+	.byte	3                       # DW_AT_name
+	.byte	37                      # DW_FORM_strx1
+	.byte	58                      # DW_AT_decl_file
+	.byte	11                      # DW_FORM_data1
+	.byte	59                      # DW_AT_decl_line
+	.byte	11                      # DW_FORM_data1
+	.byte	73                      # DW_AT_type
+	.byte	19                      # DW_FORM_ref4
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	7                       # Abbreviation Code
+	.byte	72                      # DW_TAG_call_site
+	.byte	0                       # DW_CHILDREN_no
+	.byte	127                     # DW_AT_call_origin
+	.byte	19                      # DW_FORM_ref4
+	.byte	125                     # DW_AT_call_return_pc
+	.byte	27                      # DW_FORM_addrx
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	8                       # Abbreviation Code
+	.byte	46                      # DW_TAG_subprogram
+	.byte	1                       # DW_CHILDREN_yes
+	.byte	3                       # DW_AT_name
+	.byte	37                      # DW_FORM_strx1
+	.byte	58                      # DW_AT_decl_file
+	.byte	11                      # DW_FORM_data1
+	.byte	59                      # DW_AT_decl_line
+	.byte	11                      # DW_FORM_data1
+	.byte	39                      # DW_AT_prototyped
+	.byte	25                      # DW_FORM_flag_present
+	.byte	60                      # DW_AT_declaration
+	.byte	25                      # DW_FORM_flag_present
+	.byte	63                      # DW_AT_external
+	.byte	25                      # DW_FORM_flag_present
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	9                       # Abbreviation Code
+	.byte	5                       # DW_TAG_formal_parameter
+	.byte	0                       # DW_CHILDREN_no
+	.byte	73                      # DW_AT_type
+	.byte	19                      # DW_FORM_ref4
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	10                      # Abbreviation Code
+	.byte	15                      # DW_TAG_pointer_type
+	.byte	0                       # DW_CHILDREN_no
+	.byte	73                      # DW_AT_type
+	.byte	19                      # DW_FORM_ref4
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	11                      # Abbreviation Code
+	.byte	19                      # DW_TAG_structure_type
+	.byte	1                       # DW_CHILDREN_yes
+	.byte	3                       # DW_AT_name
+	.byte	37                      # DW_FORM_strx1
+	.byte	11                      # DW_AT_byte_size
+	.byte	11                      # DW_FORM_data1
+	.byte	58                      # DW_AT_decl_file
+	.byte	11                      # DW_FORM_data1
+	.byte	59                      # DW_AT_decl_line
+	.byte	11                      # DW_FORM_data1
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	12                      # Abbreviation Code
+	.byte	13                      # DW_TAG_member
+	.byte	0                       # DW_CHILDREN_no
+	.byte	3                       # DW_AT_name
+	.byte	37                      # DW_FORM_strx1
+	.byte	73                      # DW_AT_type
+	.byte	19                      # DW_FORM_ref4
+	.byte	58                      # DW_AT_decl_file
+	.byte	11                      # DW_FORM_data1
+	.byte	59                      # DW_AT_decl_line
+	.byte	11                      # DW_FORM_data1
+	.byte	56                      # DW_AT_data_member_location
+	.byte	11                      # DW_FORM_data1
+	.byte	0                       # EOM(1)
+	.byte	0                       # EOM(2)
+	.byte	0                       # EOM(3)
+	.section	.debug_info,"", at progbits
+.Lcu_begin0:
+	.long	.Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit
+.Ldebug_info_start0:
+	.short	5                       # DWARF version number
+	.byte	1                       # DWARF Unit Type
+	.byte	8                       # Address Size (in bytes)
+	.long	.debug_abbrev           # Offset Into Abbrev. Section
+	.byte	1                       # Abbrev [1] 0xc:0x7d DW_TAG_compile_unit
+	.byte	0                       # DW_AT_producer
+	.short	29                      # DW_AT_language
+	.byte	1                       # DW_AT_name
+	.long	.Lstr_offsets_base0     # DW_AT_str_offsets_base
+	.long	.Lline_table_start0     # DW_AT_stmt_list
+	.byte	2                       # DW_AT_comp_dir
+	.byte	0                       # DW_AT_low_pc
+	.long	.Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc
+	.long	.Laddr_table_base0      # DW_AT_addr_base
+	.long	.Lloclists_table_base0  # DW_AT_loclists_base
+	.byte	2                       # Abbrev [2] 0x27:0x10 DW_TAG_enumeration_type
+	.long	55                      # DW_AT_type
+	.byte	6                       # DW_AT_name
+	.byte	4                       # DW_AT_byte_size
+	.byte	0                       # DW_AT_decl_file
+	.byte	5                       # DW_AT_decl_line
+	.byte	3                       # Abbrev [3] 0x30:0x3 DW_TAG_enumerator
+	.byte	4                       # DW_AT_name
+	.byte	0                       # DW_AT_const_value
+	.byte	3                       # Abbrev [3] 0x33:0x3 DW_TAG_enumerator
+	.byte	5                       # DW_AT_name
+	.byte	1                       # DW_AT_const_value
+	.byte	0                       # End Of Children Mark
+	.byte	4                       # Abbrev [4] 0x37:0x4 DW_TAG_base_type
+	.byte	3                       # DW_AT_name
+	.byte	7                       # DW_AT_encoding
+	.byte	4                       # DW_AT_byte_size
+	.byte	4                       # Abbrev [4] 0x3b:0x4 DW_TAG_base_type
+	.byte	7                       # DW_AT_name
+	.byte	7                       # DW_AT_encoding
+	.byte	8                       # DW_AT_byte_size
+	.byte	5                       # Abbrev [5] 0x3f:0x27 DW_TAG_subprogram
+	.byte	0                       # DW_AT_low_pc
+	.long	.Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc
+	.byte	1                       # DW_AT_frame_base
+	.byte	90
+                                        # DW_AT_call_all_calls
+	.byte	9                       # DW_AT_name
+	.byte	0                       # DW_AT_decl_file
+	.byte	7                       # DW_AT_decl_line
+                                        # DW_AT_external
+	.byte	6                       # Abbrev [6] 0x4a:0x9 DW_TAG_variable
+	.byte	0                       # DW_AT_location
+	.byte	10                      # DW_AT_name
+	.byte	0                       # DW_AT_decl_file
+	.byte	9                       # DW_AT_decl_line
+	.long	112                     # DW_AT_type
+	.byte	7                       # Abbrev [7] 0x53:0x6 DW_TAG_call_site
+	.long	102                     # DW_AT_call_origin
+	.byte	1                       # DW_AT_call_return_pc
+	.byte	7                       # Abbrev [7] 0x59:0x6 DW_TAG_call_site
+	.long	102                     # DW_AT_call_origin
+	.byte	2                       # DW_AT_call_return_pc
+	.byte	7                       # Abbrev [7] 0x5f:0x6 DW_TAG_call_site
+	.long	102                     # DW_AT_call_origin
+	.byte	3                       # DW_AT_call_return_pc
+	.byte	0                       # End Of Children Mark
+	.byte	8                       # Abbrev [8] 0x66:0xa DW_TAG_subprogram
+	.byte	8                       # DW_AT_name
+	.byte	0                       # DW_AT_decl_file
+	.byte	6                       # DW_AT_decl_line
+                                        # DW_AT_prototyped
+                                        # DW_AT_declaration
+                                        # DW_AT_external
+	.byte	9                       # Abbrev [9] 0x6a:0x5 DW_TAG_formal_parameter
+	.long	59                      # DW_AT_type
+	.byte	0                       # End Of Children Mark
+	.byte	10                      # Abbrev [10] 0x70:0x5 DW_TAG_pointer_type
+	.long	117                     # DW_AT_type
+	.byte	11                      # Abbrev [11] 0x75:0xf DW_TAG_structure_type
+	.byte	10                      # DW_AT_name
+	.byte	4                       # DW_AT_byte_size
+	.byte	0                       # DW_AT_decl_file
+	.byte	2                       # DW_AT_decl_line
+	.byte	12                      # Abbrev [12] 0x7a:0x9 DW_TAG_member
+	.byte	11                      # DW_AT_name
+	.long	132                     # DW_AT_type
+	.byte	0                       # DW_AT_decl_file
+	.byte	3                       # DW_AT_decl_line
+	.byte	0                       # DW_AT_data_member_location
+	.byte	0                       # End Of Children Mark
+	.byte	4                       # Abbrev [4] 0x84:0x4 DW_TAG_base_type
+	.byte	12                      # DW_AT_name
+	.byte	5                       # DW_AT_encoding
+	.byte	4                       # DW_AT_byte_size
+	.byte	0                       # End Of Children Mark
+.Ldebug_info_end0:
+	.section	.debug_str_offsets,"", at progbits
+	.long	56                      # Length of String Offsets Set
+	.short	5
+	.short	0
+.Lstr_offsets_base0:
+	.section	.debug_str,"MS", at progbits,1
+.Linfo_string0:
+	.asciz	"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 76d673bb89f8ec8cf65a4294a98a83c9d6646b11)" # string offset=0
+.Linfo_string1:
+	.asciz	"t.c"                   # string offset=99
+.Linfo_string2:
+	.asciz	"/home/eddy/work/tmp"   # string offset=103
+.Linfo_string3:
+	.asciz	"unsigned int"          # string offset=123
+.Linfo_string4:
+	.asciz	"U"                     # string offset=136
+.Linfo_string5:
+	.asciz	"V"                     # string offset=138
+.Linfo_string6:
+	.asciz	"bar"                   # string offset=140
+.Linfo_string7:
+	.asciz	"unsigned long"         # string offset=144
+.Linfo_string8:
+	.asciz	"consume"               # string offset=158
+.Linfo_string9:
+	.asciz	"root"                  # string offset=166
+.Linfo_string10:
+	.asciz	"foo"                   # string offset=171
+.Linfo_string11:
+	.asciz	"a"                     # string offset=175
+.Linfo_string12:
+	.asciz	"int"                   # string offset=177
+	.section	.debug_str_offsets,"", at progbits
+	.long	.Linfo_string0
+	.long	.Linfo_string1
+	.long	.Linfo_string2
+	.long	.Linfo_string3
+	.long	.Linfo_string4
+	.long	.Linfo_string5
+	.long	.Linfo_string6
+	.long	.Linfo_string7
+	.long	.Linfo_string8
+	.long	.Linfo_string9
+	.long	.Linfo_string10
+	.long	.Linfo_string11
+	.long	.Linfo_string12
+	.section	.debug_addr,"", at progbits
+	.long	.Ldebug_addr_end0-.Ldebug_addr_start0 # Length of contribution
+.Ldebug_addr_start0:
+	.short	5                       # DWARF version number
+	.byte	8                       # Address size
+	.byte	0                       # Segment selector size
+.Laddr_table_base0:
+	.quad	.Lfunc_begin0
+	.quad	.Ltmp5
+	.quad	.Ltmp8
+	.quad	.Ltmp11
+.Ldebug_addr_end0:
+	.section	.BTF,"", at progbits
+	.short	60319                   # 0xeb9f
+	.byte	1
+	.byte	0
+	.long	24
+	.long	0
+	.long	140
+	.long	140
+	.long	262
+	.long	0                       # BTF_KIND_FUNC_PROTO(id = 1)
+	.long	218103808               # 0xd000000
+	.long	0
+	.long	1                       # BTF_KIND_FUNC(id = 2)
+	.long	201326593               # 0xc000001
+	.long	1
+	.long	67                      # BTF_KIND_STRUCT(id = 3)
+	.long	67108865                # 0x4000001
+	.long	4
+	.long	71
+	.long	4
+	.long	0                       # 0x0
+	.long	73                      # BTF_KIND_INT(id = 4)
+	.long	16777216                # 0x1000000
+	.long	4
+	.long	16777248                # 0x1000020
+	.long	0                       # BTF_KIND_FUNC_PROTO(id = 5)
+	.long	218103809               # 0xd000001
+	.long	0
+	.long	0
+	.long	6
+	.long	129                     # BTF_KIND_INT(id = 6)
+	.long	16777216                # 0x1000000
+	.long	8
+	.long	64                      # 0x40
+	.long	143                     # BTF_KIND_FUNC(id = 7)
+	.long	201326594               # 0xc000002
+	.long	5
+	.long	191                     # BTF_KIND_ENUM(id = 8)
+	.long	100663298               # 0x6000002
+	.long	4
+	.long	195
+	.long	0
+	.long	197
+	.long	1
+	.byte	0                       # string offset=0
+	.ascii	"root"                  # string offset=1
+	.byte	0
+	.ascii	".text"                 # string offset=6
+	.byte	0
+	.ascii	"/home/eddy/work/tmp/t.c" # string offset=12
+	.byte	0
+	.ascii	"  asm volatile(\"r0 = 42;\":::);" # string offset=36
+	.byte	0
+	.ascii	"foo"                   # string offset=67
+	.byte	0
+	.byte	97                      # string offset=71
+	.byte	0
+	.ascii	"int"                   # string offset=73
+	.byte	0
+	.byte	48                      # string offset=77
+	.byte	0
+	.ascii	"  consume(__builtin_preserve_type_info(*foo, 0));" # string offset=79
+	.byte	0
+	.ascii	"unsigned long"         # string offset=129
+	.byte	0
+	.ascii	"consume"               # string offset=143
+	.byte	0
+	.ascii	"0:0"                   # string offset=151
+	.byte	0
+	.ascii	"  consume((unsigned long) &foo->a);" # string offset=155
+	.byte	0
+	.ascii	"bar"                   # string offset=191
+	.byte	0
+	.byte	85                      # string offset=195
+	.byte	0
+	.byte	86                      # string offset=197
+	.byte	0
+	.ascii	"  consume(__builtin_preserve_enum_value(*(enum bar *)U, 0));" # string offset=199
+	.byte	0
+	.byte	125                     # string offset=260
+	.byte	0
+	.section	.BTF.ext,"", at progbits
+	.short	60319                   # 0xeb9f
+	.byte	1
+	.byte	0
+	.long	32
+	.long	0
+	.long	20
+	.long	20
+	.long	92
+	.long	112
+	.long	60
+	.long	8                       # FuncInfo
+	.long	6                       # FuncInfo section string offset=6
+	.long	1
+	.long	.Lfunc_begin0
+	.long	2
+	.long	16                      # LineInfo
+	.long	6                       # LineInfo section string offset=6
+	.long	5
+	.long	.Ltmp0
+	.long	12
+	.long	36
+	.long	8195                    # Line 8 Col 3
+	.long	.Ltmp4
+	.long	12
+	.long	79
+	.long	10243                   # Line 10 Col 3
+	.long	.Ltmp7
+	.long	12
+	.long	155
+	.long	11267                   # Line 11 Col 3
+	.long	.Ltmp10
+	.long	12
+	.long	199
+	.long	12291                   # Line 12 Col 3
+	.long	.Ltmp12
+	.long	12
+	.long	260
+	.long	13313                   # Line 13 Col 1
+	.long	16                      # FieldReloc
+	.long	6                       # Field reloc section string offset=6
+	.long	3
+	.long	.Ltmp3
+	.long	3
+	.long	77
+	.long	8
+	.long	.Ltmp6
+	.long	3
+	.long	151
+	.long	0
+	.long	.Ltmp9
+	.long	8
+	.long	77
+	.long	10
+	.addrsig
+	.section	.debug_line,"", at progbits
+.Lline_table_start0:

diff  --git a/llvm/test/tools/llvm-objdump/BPF/core-relo-type-id.ll b/llvm/test/tools/llvm-objdump/BPF/core-relo-type-id.ll
new file mode 100644
index 000000000000000..51b6d214582bb22
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-type-id.ll
@@ -0,0 +1,91 @@
+; REQUIRES: bpf-registered-target
+
+;; Verify that llvm-objdump can use .BTF.ext to show CO-RE relocation data.
+
+; RUN: llc --mtriple bpfel %s --filetype=obj -o - | \
+; RUN:   llvm-objdump --no-addresses --no-show-raw-insn -dr - | \
+; RUN:   FileCheck %s
+
+; RUN: llc --mtriple bpfeb %s --filetype=obj -o - | \
+; RUN:   llvm-objdump --no-addresses --no-show-raw-insn -dr - | \
+; RUN:   FileCheck %s
+
+;; Input generated from the following C code:
+;;
+;;  #define __pai __attribute__((preserve_access_index))
+;;
+;;  struct bar { } __pai;
+;;  volatile unsigned long g;
+;;  void root(void) {
+;;    struct bar *bar = (void *)0;
+;;    g = __builtin_btf_type_id(*bar, 0);
+;;    g = __builtin_btf_type_id(*bar, 1);
+;;  }
+;;
+;; Using the following command:
+;;
+;;  clang --target=bpf -g -O2 -emit-llvm -S t.c
+
+; CHECK: CO-RE <local_type_id> [[[#]]] struct bar
+; CHECK: CO-RE <target_type_id> [[[#]]] struct bar
+
+ at g = dso_local global i64 0, align 8, !dbg !0
+@"llvm.btf_type_id.0$6" = external global i64, !llvm.preserve.access.index !7 #0
+@"llvm.btf_type_id.1$7" = external global i64, !llvm.preserve.access.index !7 #0
+
+; Function Attrs: nofree nounwind memory(readwrite, argmem: none)
+define dso_local void @root() local_unnamed_addr #1 !dbg !14 {
+entry:
+  call void @llvm.dbg.value(metadata ptr null, metadata !18, metadata !DIExpression()), !dbg !20
+  %0 = load i64, ptr @"llvm.btf_type_id.0$6", align 8
+  %1 = tail call i64 @llvm.bpf.passthrough.i64.i64(i32 0, i64 %0)
+  store volatile i64 %1, ptr @g, align 8, !dbg !21, !tbaa !22
+  %2 = load i64, ptr @"llvm.btf_type_id.1$7", align 8
+  %3 = tail call i64 @llvm.bpf.passthrough.i64.i64(i32 1, i64 %2)
+  store volatile i64 %3, ptr @g, align 8, !dbg !26, !tbaa !22
+  ret void, !dbg !27
+}
+
+; Function Attrs: nofree nosync nounwind memory(none)
+declare i64 @llvm.bpf.passthrough.i64.i64(i32, i64) #2
+
+; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
+declare void @llvm.dbg.value(metadata, metadata, metadata) #3
+
+attributes #0 = { "btf_type_id" }
+attributes #1 = { nofree nounwind memory(readwrite, argmem: none) "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
+attributes #2 = { nofree nosync nounwind memory(none) }
+attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!9, !10, !11, !12}
+!llvm.ident = !{!13}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 4, type: !5, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "t.c", directory: "/home/eddy/work/tmp", checksumkind: CSK_MD5, checksum: "29efc9dba44aaba9e4b0c389bb8694ea")
+!4 = !{!0}
+!5 = !DIDerivedType(tag: DW_TAG_volatile_type, baseType: !6)
+!6 = !DIBasicType(name: "unsigned long", size: 64, encoding: DW_ATE_unsigned)
+!7 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "bar", file: !3, line: 3, elements: !8)
+!8 = !{}
+!9 = !{i32 7, !"Dwarf Version", i32 5}
+!10 = !{i32 2, !"Debug Info Version", i32 3}
+!11 = !{i32 1, !"wchar_size", i32 4}
+!12 = !{i32 7, !"frame-pointer", i32 2}
+!13 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)"}
+!14 = distinct !DISubprogram(name: "root", scope: !3, file: !3, line: 5, type: !15, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !17)
+!15 = !DISubroutineType(types: !16)
+!16 = !{null}
+!17 = !{!18}
+!18 = !DILocalVariable(name: "bar", scope: !14, file: !3, line: 6, type: !19)
+!19 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !7, size: 64)
+!20 = !DILocation(line: 0, scope: !14)
+!21 = !DILocation(line: 7, column: 5, scope: !14)
+!22 = !{!23, !23, i64 0}
+!23 = !{!"long", !24, i64 0}
+!24 = !{!"omnipotent char", !25, i64 0}
+!25 = !{!"Simple C/C++ TBAA"}
+!26 = !DILocation(line: 8, column: 5, scope: !14)
+!27 = !DILocation(line: 9, column: 1, scope: !14)

diff  --git a/llvm/test/tools/llvm-objdump/BPF/core-relo-type-info.ll b/llvm/test/tools/llvm-objdump/BPF/core-relo-type-info.ll
new file mode 100644
index 000000000000000..53775edd9d993f4
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-type-info.ll
@@ -0,0 +1,104 @@
+; REQUIRES: bpf-registered-target
+
+;; Verify that llvm-objdump can use .BTF.ext to show CO-RE relocation data.
+
+; RUN: llc --mtriple bpfel %s --filetype=obj -o - | \
+; RUN:   llvm-objdump --no-addresses --no-show-raw-insn -dr - | \
+; RUN:   FileCheck %s
+
+; RUN: llc --mtriple bpfeb %s --filetype=obj -o - | \
+; RUN:   llvm-objdump --no-addresses --no-show-raw-insn -dr - | \
+; RUN:   FileCheck %s
+
+;; Input generated from the following C code:
+;;
+;;  #define __pai __attribute__((preserve_access_index))
+;;
+;;  struct bar { } __pai;
+;;  volatile unsigned long g;
+;;  void root(void) {
+;;    struct bar *bar = (void *)0;
+;;    g = __builtin_preserve_type_info(*bar, 0);
+;;    g = __builtin_preserve_type_info(*bar, 1);
+;;    g = __builtin_preserve_type_info(*bar, 2);
+;;  }
+;;
+;; Using the following command:
+;;
+;;  clang --target=bpf -g -O2 -emit-llvm -S t.c
+
+; CHECK: CO-RE <type_exists> [[[#]]] struct bar
+; CHECK: CO-RE <type_size> [[[#]]] struct bar
+; CHECK: CO-RE <type_matches> [[[#]]] struct bar
+
+ at g = dso_local global i64 0, align 8, !dbg !0
+@"llvm.bar:8:1$0" = external global i32, !llvm.preserve.access.index !7 #0
+@"llvm.bar:9:0$0" = external global i32, !llvm.preserve.access.index !7 #0
+@"llvm.bar:12:1$0" = external global i32, !llvm.preserve.access.index !7 #0
+
+; Function Attrs: nofree nounwind memory(readwrite, argmem: none)
+define dso_local void @root() local_unnamed_addr #1 !dbg !14 {
+entry:
+  call void @llvm.dbg.value(metadata ptr null, metadata !18, metadata !DIExpression()), !dbg !20
+  %0 = load i32, ptr @"llvm.bar:8:1$0", align 4
+  %1 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 0, i32 %0)
+  %conv = zext i32 %1 to i64, !dbg !21
+  store volatile i64 %conv, ptr @g, align 8, !dbg !22, !tbaa !23
+  %2 = load i32, ptr @"llvm.bar:9:0$0", align 4
+  %3 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 1, i32 %2)
+  %conv1 = zext i32 %3 to i64, !dbg !27
+  store volatile i64 %conv1, ptr @g, align 8, !dbg !28, !tbaa !23
+  %4 = load i32, ptr @"llvm.bar:12:1$0", align 4
+  %5 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 2, i32 %4)
+  %conv2 = zext i32 %5 to i64, !dbg !29
+  store volatile i64 %conv2, ptr @g, align 8, !dbg !30, !tbaa !23
+  ret void, !dbg !31
+}
+
+; Function Attrs: nofree nosync nounwind memory(none)
+declare i32 @llvm.bpf.passthrough.i32.i32(i32, i32) #2
+
+; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
+declare void @llvm.dbg.value(metadata, metadata, metadata) #3
+
+attributes #0 = { "btf_ama" }
+attributes #1 = { nofree nounwind memory(readwrite, argmem: none) "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
+attributes #2 = { nofree nosync nounwind memory(none) }
+attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!9, !10, !11, !12}
+!llvm.ident = !{!13}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 4, type: !5, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "t.c", directory: "/home/eddy/work/tmp", checksumkind: CSK_MD5, checksum: "5bf218e82301e866fa302fd927913bcf")
+!4 = !{!0}
+!5 = !DIDerivedType(tag: DW_TAG_volatile_type, baseType: !6)
+!6 = !DIBasicType(name: "unsigned long", size: 64, encoding: DW_ATE_unsigned)
+!7 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "bar", file: !3, line: 3, elements: !8)
+!8 = !{}
+!9 = !{i32 7, !"Dwarf Version", i32 5}
+!10 = !{i32 2, !"Debug Info Version", i32 3}
+!11 = !{i32 1, !"wchar_size", i32 4}
+!12 = !{i32 7, !"frame-pointer", i32 2}
+!13 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)"}
+!14 = distinct !DISubprogram(name: "root", scope: !3, file: !3, line: 5, type: !15, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !17)
+!15 = !DISubroutineType(types: !16)
+!16 = !{null}
+!17 = !{!18}
+!18 = !DILocalVariable(name: "bar", scope: !14, file: !3, line: 6, type: !19)
+!19 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !7, size: 64)
+!20 = !DILocation(line: 0, scope: !14)
+!21 = !DILocation(line: 7, column: 7, scope: !14)
+!22 = !DILocation(line: 7, column: 5, scope: !14)
+!23 = !{!24, !24, i64 0}
+!24 = !{!"long", !25, i64 0}
+!25 = !{!"omnipotent char", !26, i64 0}
+!26 = !{!"Simple C/C++ TBAA"}
+!27 = !DILocation(line: 8, column: 7, scope: !14)
+!28 = !DILocation(line: 8, column: 5, scope: !14)
+!29 = !DILocation(line: 9, column: 7, scope: !14)
+!30 = !DILocation(line: 9, column: 5, scope: !14)
+!31 = !DILocation(line: 10, column: 1, scope: !14)

diff  --git a/llvm/tools/llvm-objdump/CMakeLists.txt b/llvm/tools/llvm-objdump/CMakeLists.txt
index 73c857085e65bf8..ae173957b226073 100644
--- a/llvm/tools/llvm-objdump/CMakeLists.txt
+++ b/llvm/tools/llvm-objdump/CMakeLists.txt
@@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS
   AllTargetsDisassemblers
   AllTargetsInfos
   BinaryFormat
+  DebugInfoBTF
   DebugInfoDWARF
   Demangle
   MC

diff  --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index e4e1ceb9b3f97f6..fcfce7764e012d0 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -31,6 +31,7 @@
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringSet.h"
 #include "llvm/ADT/Twine.h"
+#include "llvm/DebugInfo/BTF/BTFParser.h"
 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
 #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
 #include "llvm/DebugInfo/Symbolize/Symbolize.h"
@@ -535,6 +536,22 @@ static void printRelocation(formatted_raw_ostream &OS, StringRef FileName,
   OS << Name << "\t" << Val;
 }
 
+static void printBTFRelocation(formatted_raw_ostream &FOS, llvm::BTFParser &BTF,
+                               object::SectionedAddress Address,
+                               LiveVariablePrinter &LVP) {
+  const llvm::BTF::BPFFieldReloc *Reloc = BTF.findFieldReloc(Address);
+  if (!Reloc)
+    return;
+
+  SmallString<64> Val;
+  BTF.symbolize(Reloc, Val);
+  FOS << "\t\t";
+  if (LeadingAddr)
+    FOS << format("%016" PRIx64 ":  ", Address.Address + AdjustVMA);
+  FOS << "CO-RE " << Val;
+  LVP.printAfterOtherLine(FOS, true);
+}
+
 class PrettyPrinter {
 public:
   virtual ~PrettyPrinter() = default;
@@ -1626,6 +1643,16 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
   if (SymbolizeOperands && !Obj.isRelocatableObject())
     ReadBBAddrMap();
 
+  std::optional<llvm::BTFParser> BTF;
+  if (InlineRelocs && BTFParser::hasBTFSections(Obj)) {
+    BTF.emplace();
+    BTFParser::ParseOptions Opts = {};
+    Opts.LoadTypes = true;
+    Opts.LoadRelocs = true;
+    if (Error E = BTF->parse(Obj, Opts))
+      WithColor::defaultErrorHandler(std::move(E));
+  }
+
   for (const SectionRef &Section : ToolSectionFilter(Obj)) {
     if (FilterSections.empty() && !DisassembleAll &&
         (!Section.isText() || Section.isVirtual()))
@@ -2163,6 +2190,9 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
                                 *DT->SubtargetInfo, CommentStream.str(), LVP);
         Comments.clear();
 
+        if (BTF)
+          printBTFRelocation(FOS, *BTF, {Index, Section.getIndex()}, LVP);
+
         // Hexagon does this in pretty printer
         if (Obj.getArch() != Triple::hexagon) {
           // Print relocation for instruction and data.

diff  --git a/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp b/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp
index 64ba8222a267c40..7b4f7939e678304 100644
--- a/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp
+++ b/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp
@@ -16,6 +16,7 @@ using namespace llvm;
 using namespace llvm::object;
 
 #define LC(Line, Col) ((Line << 10u) | Col)
+#define ASSERT_SUCCEEDED(E) ASSERT_THAT_ERROR((E), Succeeded())
 
 const char BTFEndOfData[] =
     "error while reading .BTF section: unexpected end of data";
@@ -52,7 +53,7 @@ struct MockData1 {
 #pragma pack(push, 1)
   struct B {
     BTF::Header Header = {};
-    // no types
+    // No types.
     struct S {
       char Foo[4] = "foo";
       char Bar[4] = "bar";
@@ -74,7 +75,7 @@ struct MockData1 {
 
   struct E {
     BTF::ExtHeader Header = {};
-    // no func info
+    // No func info.
     struct {
       uint32_t LineRecSize = sizeof(BTF::BPFLineInfo);
       struct {
@@ -163,7 +164,7 @@ TEST(BTFParserTest, simpleCorrectInput) {
   EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, File1)), "a.c");
   EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, File2)), "b.c");
 
-  // Invalid offset
+  // Invalid offset.
   EXPECT_EQ(BTF.findString(sizeof(MockData1::B::S)), StringRef());
 
   const BTF::BPFLineInfo *I1 = BTF.findLineInfo({16, 1});
@@ -187,10 +188,10 @@ TEST(BTFParserTest, simpleCorrectInput) {
   EXPECT_EQ(BTF.findString(I3->FileNameOff), "b.c");
   EXPECT_EQ(BTF.findString(I3->LineOff), "first line");
 
-  // No info for insn address
+  // No info for insn address.
   EXPECT_FALSE(BTF.findLineInfo({24, 1}));
   EXPECT_FALSE(BTF.findLineInfo({8, 2}));
-  // No info for section number
+  // No info for section number.
   EXPECT_FALSE(BTF.findLineInfo({16, 3}));
 }
 
@@ -202,13 +203,13 @@ TEST(BTFParserTest, badSectionNameOffset) {
   Mock.Ext.Lines.Foo.Sec.SecNameOff = 100500;
   Error Err = BTF.parse(Mock.makeObj());
   EXPECT_FALSE(Err);
-  // "foo" line info should be corrupted
+  // "foo" line info should be corrupted.
   EXPECT_FALSE(BTF.findLineInfo({16, 1}));
-  // "bar" line info should be ok
+  // "bar" line info should be ok.
   EXPECT_TRUE(BTF.findLineInfo({0, 2}));
 }
 
-// Keep this as macro to preserve line number info
+// Keep this as macro to preserve line number info.
 #define EXPECT_PARSE_ERROR(Mock, Message)                                      \
   do {                                                                         \
     BTFParser BTF;                                                             \
@@ -237,12 +238,12 @@ TEST(BTFParserTest, badBTFHdrLen) {
 TEST(BTFParserTest, badBTFSectionLen) {
   MockData1 Mock1, Mock2;
 
-  // Cut-off string section by one byte
+  // Cut-off string section by one byte.
   Mock1.BTFSectionLen =
       offsetof(MockData1::B, Strings) + sizeof(MockData1::B::S) - 1;
   EXPECT_PARSE_ERROR(Mock1, "invalid .BTF section size");
 
-  // Cut-off header
+  // Cut-off header.
   Mock2.BTFSectionLen = offsetof(BTF::Header, StrOff);
   EXPECT_PARSE_ERROR(Mock2, BTFEndOfData);
 }
@@ -272,15 +273,15 @@ TEST(BTFParserTest, badBTFExtHdrLen) {
 TEST(BTFParserTest, badBTFExtSectionLen) {
   MockData1 Mock1, Mock2, Mock3;
 
-  // Cut-off header before HdrLen
+  // Cut-off header before HdrLen.
   Mock1.ExtSectionLen = offsetof(BTF::ExtHeader, HdrLen);
   EXPECT_PARSE_ERROR(Mock1, BTFExtEndOfData);
 
-  // Cut-off header before LineInfoLen
+  // Cut-off header before LineInfoLen.
   Mock2.ExtSectionLen = offsetof(BTF::ExtHeader, LineInfoLen);
   EXPECT_PARSE_ERROR(Mock2, BTFExtEndOfData);
 
-  // Cut-off line-info section somewhere in the middle
+  // Cut-off line-info section somewhere in the middle.
   Mock3.ExtSectionLen = offsetof(MockData1::E, Lines) + 4;
   EXPECT_PARSE_ERROR(Mock3, BTFExtEndOfData);
 }
@@ -355,4 +356,732 @@ TEST(BTFParserTest, btfContext) {
   EXPECT_EQ(I2.LineSource, std::nullopt);
 }
 
+static uint32_t mkInfo(uint32_t Kind) { return Kind << 24; }
+
+template <typename T> static void append(std::string &S, const T &What) {
+  S.append((const char *)&What, sizeof(What));
+}
+
+class MockData2 {
+  SmallString<0> ObjStorage;
+  std::unique_ptr<ObjectFile> Obj;
+  std::string Types;
+  std::string Strings;
+  std::string Relocs;
+  std::string Lines;
+  unsigned TotalTypes;
+  int LastRelocSecIdx;
+  unsigned NumRelocs;
+  int LastLineSecIdx;
+  unsigned NumLines;
+
+public:
+  MockData2() { reset(); }
+
+  unsigned totalTypes() const { return TotalTypes; }
+
+  uint32_t addString(StringRef S) {
+    uint32_t Off = Strings.size();
+    Strings.append(S.data(), S.size());
+    Strings.append("\0", 1);
+    return Off;
+  };
+
+  uint32_t addType(const BTF::CommonType &Tp) {
+    append(Types, Tp);
+    return ++TotalTypes;
+  }
+
+  template <typename T> void addTail(const T &Tp) { append(Types, Tp); }
+
+  void resetTypes() {
+    Types.resize(0);
+    TotalTypes = 0;
+  }
+
+  void reset() {
+    ObjStorage.clear();
+    Types.resize(0);
+    Strings.resize(0);
+    Relocs.resize(0);
+    Lines.resize(0);
+    TotalTypes = 0;
+    LastRelocSecIdx = -1;
+    NumRelocs = 0;
+    LastLineSecIdx = -1;
+    NumLines = 0;
+  }
+
+  void finishRelocSec() {
+    if (LastRelocSecIdx == -1)
+      return;
+
+    BTF::SecFieldReloc *SecInfo =
+        (BTF::SecFieldReloc *)&Relocs[LastRelocSecIdx];
+    SecInfo->NumFieldReloc = NumRelocs;
+    LastRelocSecIdx = -1;
+    NumRelocs = 0;
+  }
+
+  void finishLineSec() {
+    if (LastLineSecIdx == -1)
+      return;
+
+    BTF::SecLineInfo *SecInfo = (BTF::SecLineInfo *)&Lines[LastLineSecIdx];
+    SecInfo->NumLineInfo = NumLines;
+    NumLines = 0;
+    LastLineSecIdx = -1;
+  }
+
+  void addRelocSec(const BTF::SecFieldReloc &R) {
+    finishRelocSec();
+    LastRelocSecIdx = Relocs.size();
+    append(Relocs, R);
+  }
+
+  void addReloc(const BTF::BPFFieldReloc &R) {
+    append(Relocs, R);
+    ++NumRelocs;
+  }
+
+  void addLinesSec(const BTF::SecLineInfo &R) {
+    finishLineSec();
+    LastLineSecIdx = Lines.size();
+    append(Lines, R);
+  }
+
+  void addLine(const BTF::BPFLineInfo &R) {
+    append(Lines, R);
+    ++NumLines;
+  }
+
+  ObjectFile &makeObj() {
+    finishRelocSec();
+    finishLineSec();
+
+    BTF::Header BTFHeader = {};
+    BTFHeader.Magic = BTF::MAGIC;
+    BTFHeader.Version = 1;
+    BTFHeader.HdrLen = sizeof(BTFHeader);
+    BTFHeader.StrOff = 0;
+    BTFHeader.StrLen = Strings.size();
+    BTFHeader.TypeOff = Strings.size();
+    BTFHeader.TypeLen = Types.size();
+
+    std::string BTFSec;
+    append(BTFSec, BTFHeader);
+    BTFSec.append(Strings);
+    BTFSec.append(Types);
+
+    BTF::ExtHeader ExtHeader = {};
+    ExtHeader.Magic = BTF::MAGIC;
+    ExtHeader.Version = 1;
+    ExtHeader.HdrLen = sizeof(ExtHeader);
+    ExtHeader.FieldRelocOff = 0;
+    ExtHeader.FieldRelocLen = Relocs.size() + sizeof(uint32_t);
+    ExtHeader.LineInfoOff = ExtHeader.FieldRelocLen;
+    ExtHeader.LineInfoLen = Lines.size() + sizeof(uint32_t);
+
+    std::string ExtSec;
+    append(ExtSec, ExtHeader);
+    append(ExtSec, (uint32_t)sizeof(BTF::BPFFieldReloc));
+    ExtSec.append(Relocs);
+    append(ExtSec, (uint32_t)sizeof(BTF::BPFLineInfo));
+    ExtSec.append(Lines);
+
+    std::string YamlBuffer;
+    raw_string_ostream Yaml(YamlBuffer);
+    Yaml << R"(
+!ELF
+FileHeader:
+  Class:    ELFCLASS64)";
+    if (sys::IsBigEndianHost)
+      Yaml << "\n  Data:     ELFDATA2MSB";
+    else
+      Yaml << "\n  Data:     ELFDATA2LSB";
+    Yaml << R"(
+  Type:     ET_REL
+  Machine:  EM_BPF
+Sections:
+  - Name:     foo
+    Type:     SHT_PROGBITS
+    Size:     0x80
+  - Name:     bar
+    Type:     SHT_PROGBITS
+    Size:     0x80
+  - Name:     .BTF
+    Type:     SHT_PROGBITS
+    Content: )"
+         << makeBinRef(BTFSec.data(), BTFSec.size());
+    Yaml << R"(
+  - Name:     .BTF.ext
+    Type:     SHT_PROGBITS
+    Content: )"
+         << makeBinRef(ExtSec.data(), ExtSec.size());
+
+    Obj = yaml::yaml2ObjectFile(ObjStorage, YamlBuffer,
+                                [](const Twine &Err) { errs() << Err; });
+    return *Obj.get();
+  }
+};
+
+TEST(BTFParserTest, allTypeKinds) {
+  MockData2 D;
+  D.addType({D.addString("1"), mkInfo(BTF::BTF_KIND_INT), {4}});
+  D.addTail((uint32_t)0);
+  D.addType({D.addString("2"), mkInfo(BTF::BTF_KIND_PTR), {1}});
+  D.addType({D.addString("3"), mkInfo(BTF::BTF_KIND_ARRAY), {0}});
+  D.addTail(BTF::BTFArray({1, 1, 2}));
+  D.addType({D.addString("4"), mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}});
+  D.addTail(BTF::BTFMember({D.addString("a"), 1, 0}));
+  D.addTail(BTF::BTFMember({D.addString("b"), 1, 0}));
+  D.addType({D.addString("5"), mkInfo(BTF::BTF_KIND_UNION) | 3, {8}});
+  D.addTail(BTF::BTFMember({D.addString("a"), 1, 0}));
+  D.addTail(BTF::BTFMember({D.addString("b"), 1, 0}));
+  D.addTail(BTF::BTFMember({D.addString("c"), 1, 0}));
+  D.addType({D.addString("6"), mkInfo(BTF::BTF_KIND_ENUM) | 2, {4}});
+  D.addTail(BTF::BTFEnum({D.addString("U"), 1}));
+  D.addTail(BTF::BTFEnum({D.addString("V"), 2}));
+  D.addType({D.addString("7"), mkInfo(BTF::BTF_KIND_ENUM64) | 1, {4}});
+  D.addTail(BTF::BTFEnum64({D.addString("W"), 0, 1}));
+  D.addType(
+      {D.addString("8"), BTF::FWD_UNION_FLAG | mkInfo(BTF::BTF_KIND_FWD), {0}});
+  D.addType({D.addString("9"), mkInfo(BTF::BTF_KIND_TYPEDEF), {1}});
+  D.addType({D.addString("10"), mkInfo(BTF::BTF_KIND_VOLATILE), {1}});
+  D.addType({D.addString("11"), mkInfo(BTF::BTF_KIND_CONST), {1}});
+  D.addType({D.addString("12"), mkInfo(BTF::BTF_KIND_RESTRICT), {1}});
+  D.addType({D.addString("13"), mkInfo(BTF::BTF_KIND_FUNC_PROTO) | 1, {1}});
+  D.addTail(BTF::BTFParam({D.addString("P"), 2}));
+  D.addType({D.addString("14"), mkInfo(BTF::BTF_KIND_FUNC), {13}});
+  D.addType({D.addString("15"), mkInfo(BTF::BTF_KIND_VAR), {2}});
+  D.addTail((uint32_t)0);
+  D.addType({D.addString("16"), mkInfo(BTF::BTF_KIND_DATASEC) | 3, {0}});
+  D.addTail(BTF::BTFDataSec({1, 0, 4}));
+  D.addTail(BTF::BTFDataSec({1, 4, 4}));
+  D.addTail(BTF::BTFDataSec({1, 8, 4}));
+  D.addType({D.addString("17"), mkInfo(BTF::BTF_KIND_FLOAT), {4}});
+  D.addType({D.addString("18"), mkInfo(BTF::BTF_KIND_DECL_TAG), {0}});
+  D.addTail((uint32_t)-1);
+  D.addType({D.addString("19"), mkInfo(BTF::BTF_KIND_TYPE_TAG), {0}});
+
+  BTFParser BTF;
+  Error Err = BTF.parse(D.makeObj());
+  EXPECT_FALSE(Err);
+
+  EXPECT_EQ(D.totalTypes() + 1 /* +1 for void */, BTF.typesCount());
+  for (unsigned Id = 1; Id < D.totalTypes() + 1; ++Id) {
+    const BTF::CommonType *Tp = BTF.findType(Id);
+    ASSERT_TRUE(Tp);
+    std::string IdBuf;
+    raw_string_ostream IdBufStream(IdBuf);
+    IdBufStream << Id;
+    EXPECT_EQ(BTF.findString(Tp->NameOff), IdBuf);
+  }
+}
+
+TEST(BTFParserTest, bigStruct) {
+  const uint32_t N = 1000u;
+  MockData2 D;
+  uint32_t FStr = D.addString("f");
+  D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_INT), {4}});
+  D.addTail((uint32_t)0);
+  D.addType({D.addString("big"), mkInfo(BTF::BTF_KIND_STRUCT) | N, {8}});
+  for (unsigned I = 0; I < N; ++I)
+    D.addTail(BTF::BTFMember({FStr, 1, 0}));
+  D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_INT), {4}});
+  D.addTail((uint32_t)0);
+
+  BTFParser BTF;
+  ASSERT_SUCCEEDED(BTF.parse(D.makeObj()));
+  ASSERT_EQ(BTF.typesCount(), 4u /* +1 for void */);
+  const BTF::CommonType *Foo = BTF.findType(1);
+  const BTF::CommonType *Big = BTF.findType(2);
+  const BTF::CommonType *Bar = BTF.findType(3);
+  ASSERT_TRUE(Foo);
+  ASSERT_TRUE(Big);
+  ASSERT_TRUE(Bar);
+  EXPECT_EQ(BTF.findString(Foo->NameOff), "foo");
+  EXPECT_EQ(BTF.findString(Big->NameOff), "big");
+  EXPECT_EQ(BTF.findString(Bar->NameOff), "bar");
+  EXPECT_EQ(Big->getVlen(), N);
+}
+
+TEST(BTFParserTest, incompleteTypes) {
+  MockData2 D;
+  auto IncompleteType = [&](const BTF::CommonType &Tp) {
+    D.resetTypes();
+    D.addType(Tp);
+    EXPECT_PARSE_ERROR(D, "incomplete type definition in .BTF section");
+  };
+
+  // All kinds that need tail.
+  IncompleteType({D.addString("a"), mkInfo(BTF::BTF_KIND_INT), {4}});
+  IncompleteType({D.addString("b"), mkInfo(BTF::BTF_KIND_ARRAY), {0}});
+  IncompleteType({D.addString("c"), mkInfo(BTF::BTF_KIND_VAR), {0}});
+  IncompleteType({D.addString("d"), mkInfo(BTF::BTF_KIND_DECL_TAG), {0}});
+
+  // All kinds with vlen.
+  IncompleteType({D.addString("a"), mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}});
+  IncompleteType({D.addString("b"), mkInfo(BTF::BTF_KIND_UNION) | 3, {8}});
+  IncompleteType({D.addString("c"), mkInfo(BTF::BTF_KIND_ENUM) | 2, {4}});
+  IncompleteType({D.addString("d"), mkInfo(BTF::BTF_KIND_ENUM64) | 1, {4}});
+  IncompleteType({D.addString("e"), mkInfo(BTF::BTF_KIND_FUNC_PROTO) | 1, {1}});
+  IncompleteType({D.addString("f"), mkInfo(BTF::BTF_KIND_DATASEC) | 3, {0}});
+
+  // An unexpected tail.
+  D.resetTypes();
+  D.addTail((uint32_t)0);
+  EXPECT_PARSE_ERROR(D, "incomplete type definition in .BTF section");
+}
+
+// Use macro to preserve line number in error message.
+#define SYMBOLIZE(SecAddr, Expected)                                           \
+  do {                                                                         \
+    const BTF::BPFFieldReloc *R = BTF.findFieldReloc((SecAddr));               \
+    ASSERT_TRUE(R);                                                            \
+    SmallString<64> Symbolized;                                                \
+    BTF.symbolize(R, Symbolized);                                              \
+    EXPECT_EQ(Symbolized, (Expected));                                         \
+  } while (false)
+
+// Shorter name for initializers below.
+using SA = SectionedAddress;
+
+TEST(BTFParserTest, typeRelocs) {
+  MockData2 D;
+  uint32_t Zero = D.addString("0");
+  // id 1: struct foo {}
+  // id 2: union bar;
+  // id 3: struct buz;
+  D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_STRUCT), {0}});
+  D.addType({D.addString("bar"),
+             mkInfo(BTF::BTF_KIND_FWD) | BTF::FWD_UNION_FLAG,
+             {0}});
+  D.addType({D.addString("buz"), mkInfo(BTF::BTF_KIND_FWD), {0}});
+  D.addRelocSec({D.addString("foo"), 7});
+  // List of all possible correct type relocations for type id #1.
+  D.addReloc({0, 1, Zero, BTF::BTF_TYPE_ID_LOCAL});
+  D.addReloc({8, 1, Zero, BTF::BTF_TYPE_ID_REMOTE});
+  D.addReloc({16, 1, Zero, BTF::TYPE_EXISTENCE});
+  D.addReloc({24, 1, Zero, BTF::TYPE_MATCH});
+  D.addReloc({32, 1, Zero, BTF::TYPE_SIZE});
+  // Forward declarations.
+  D.addReloc({40, 2, Zero, BTF::TYPE_SIZE});
+  D.addReloc({48, 3, Zero, BTF::TYPE_SIZE});
+  // Incorrect type relocation: bad type id.
+  D.addReloc({56, 42, Zero, BTF::TYPE_SIZE});
+  // Incorrect type relocation: spec should be '0'.
+  D.addReloc({64, 1, D.addString("10"), BTF::TYPE_SIZE});
+
+  BTFParser BTF;
+  Error E = BTF.parse(D.makeObj());
+  EXPECT_FALSE(E);
+
+  SYMBOLIZE(SA({0, 1}), "<local_type_id> [1] struct foo");
+  SYMBOLIZE(SA({8, 1}), "<target_type_id> [1] struct foo");
+  SYMBOLIZE(SA({16, 1}), "<type_exists> [1] struct foo");
+  SYMBOLIZE(SA({24, 1}), "<type_matches> [1] struct foo");
+  SYMBOLIZE(SA({32, 1}), "<type_size> [1] struct foo");
+  SYMBOLIZE(SA({40, 1}), "<type_size> [2] fwd union bar");
+  SYMBOLIZE(SA({48, 1}), "<type_size> [3] fwd struct buz");
+  SYMBOLIZE(SA({56, 1}), "<type_size> [42] '0' <unknown type id: 42>");
+  SYMBOLIZE(SA({64, 1}),
+            "<type_size> [1] '10' "
+            "<unexpected type-based relocation spec: should be '0'>");
+}
+
+TEST(BTFParserTest, enumRelocs) {
+  MockData2 D;
+  // id 1: enum { U, V }
+  D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_ENUM) | 2, {4}});
+  D.addTail(BTF::BTFEnum({D.addString("U"), 1}));
+  D.addTail(BTF::BTFEnum({D.addString("V"), 2}));
+  // id 2: int
+  D.addType({D.addString("int"), mkInfo(BTF::BTF_KIND_INT), {4}});
+  D.addTail((uint32_t)0);
+  // id 3: enum: uint64_t { A, B }
+  D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_ENUM64) | 2, {8}});
+  D.addTail(BTF::BTFEnum64({D.addString("A"), 1, 0}));
+  D.addTail(BTF::BTFEnum64({D.addString("B"), 2, 0}));
+
+  D.addRelocSec({D.addString("foo"), 5});
+  // An ok relocation accessing value #1: U.
+  D.addReloc({0, 1, D.addString("0"), BTF::ENUM_VALUE_EXISTENCE});
+  // An ok relocation accessing value #2: V.
+  D.addReloc({8, 1, D.addString("1"), BTF::ENUM_VALUE});
+  // Incorrect relocation: too many elements in string "1:0".
+  D.addReloc({16, 1, D.addString("1:0"), BTF::ENUM_VALUE});
+  // Incorrect relocation: type id "2" is not an enum.
+  D.addReloc({24, 2, D.addString("1"), BTF::ENUM_VALUE});
+  // Incorrect relocation: value #42 does not exist for enum "foo".
+  D.addReloc({32, 1, D.addString("42"), BTF::ENUM_VALUE});
+  // An ok relocation accessing value #1: A.
+  D.addReloc({40, 3, D.addString("0"), BTF::ENUM_VALUE_EXISTENCE});
+  // An ok relocation accessing value #2: B.
+  D.addReloc({48, 3, D.addString("1"), BTF::ENUM_VALUE});
+
+  BTFParser BTF;
+  Error E = BTF.parse(D.makeObj());
+  EXPECT_FALSE(E);
+
+  SYMBOLIZE(SA({0, 1}), "<enumval_exists> [1] enum foo::U = 1");
+  SYMBOLIZE(SA({8, 1}), "<enumval_value> [1] enum foo::V = 2");
+  SYMBOLIZE(
+      SA({16, 1}),
+      "<enumval_value> [1] '1:0' <unexpected enumval relocation spec size>");
+  SYMBOLIZE(
+      SA({24, 1}),
+      "<enumval_value> [2] '1' <unexpected type kind for enum relocation: 1>");
+  SYMBOLIZE(SA({32, 1}), "<enumval_value> [1] '42' <bad value index: 42>");
+  SYMBOLIZE(SA({40, 1}), "<enumval_exists> [3] enum bar::A = 1");
+  SYMBOLIZE(SA({48, 1}), "<enumval_value> [3] enum bar::B = 2");
+}
+
+TEST(BTFParserTest, enumRelocsMods) {
+  MockData2 D;
+  // id 1: enum { U, V }
+  D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_ENUM) | 2, {4}});
+  D.addTail(BTF::BTFEnum({D.addString("U"), 1}));
+  D.addTail(BTF::BTFEnum({D.addString("V"), 2}));
+  // id 2: typedef enum foo a;
+  D.addType({D.addString("a"), mkInfo(BTF::BTF_KIND_TYPEDEF), {1}});
+  // id 3: const enum foo;
+  D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_CONST), {1}});
+
+  D.addRelocSec({D.addString("foo"), 0});
+  D.addReloc({0, 2, D.addString("0"), BTF::ENUM_VALUE});
+  D.addReloc({8, 3, D.addString("1"), BTF::ENUM_VALUE});
+
+  BTFParser BTF;
+  Error E = BTF.parse(D.makeObj());
+  EXPECT_FALSE(E);
+
+  SYMBOLIZE(SA({0, 1}), "<enumval_value> [2] typedef a::U = 1");
+  SYMBOLIZE(SA({8, 1}), "<enumval_value> [3] const enum foo::V = 2");
+}
+
+TEST(BTFParserTest, fieldRelocs) {
+  MockData2 D;
+  // id 1: int
+  D.addType({D.addString("int"), mkInfo(BTF::BTF_KIND_INT), {4}});
+  D.addTail((uint32_t)0);
+  // id 2: struct foo { int a; int b; }
+  D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}});
+  D.addTail(BTF::BTFMember({D.addString("a"), 1, 0}));
+  D.addTail(BTF::BTFMember({D.addString("b"), 1, 0}));
+  // id 3: array of struct foo.
+  D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_ARRAY), {0}});
+  D.addTail(BTF::BTFArray({2, 1, 2}));
+  // id 4: struct bar { struct foo u[2]; int v; }
+  D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}});
+  D.addTail(BTF::BTFMember({D.addString("u"), 3, 0}));
+  D.addTail(BTF::BTFMember({D.addString("v"), 1, 0}));
+  // id 5: array with bad element type id.
+  D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_ARRAY), {0}});
+  D.addTail(BTF::BTFArray({42, 1, 2}));
+  // id 6: struct buz { <bad type> u[2]; <bad type> v; }
+  D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}});
+  D.addTail(BTF::BTFMember({D.addString("u"), 5, 0}));
+  D.addTail(BTF::BTFMember({D.addString("v"), 42, 0}));
+
+  D.addRelocSec({D.addString("foo"), 0 /* patched automatically */});
+  // All field relocations kinds for struct bar::v.
+  D.addReloc({0, 4, D.addString("0:1"), BTF::FIELD_BYTE_OFFSET});
+  D.addReloc({8, 4, D.addString("0:1"), BTF::FIELD_BYTE_SIZE});
+  D.addReloc({16, 4, D.addString("0:1"), BTF::FIELD_EXISTENCE});
+  D.addReloc({24, 4, D.addString("0:1"), BTF::FIELD_SIGNEDNESS});
+  D.addReloc({32, 4, D.addString("0:1"), BTF::FIELD_LSHIFT_U64});
+  D.addReloc({40, 4, D.addString("0:1"), BTF::FIELD_RSHIFT_U64});
+  // Non-zero first idx.
+  D.addReloc({48, 4, D.addString("7:1"), BTF::FIELD_BYTE_OFFSET});
+  // Access through array and struct: struct bar::u[1].a.
+  D.addReloc({56, 4, D.addString("0:0:1:0"), BTF::FIELD_BYTE_OFFSET});
+  // Access through array and struct: struct bar::u[1].b.
+  D.addReloc({64, 4, D.addString("0:0:1:1"), BTF::FIELD_BYTE_OFFSET});
+  // Incorrect relocation: empty access string.
+  D.addReloc({72, 4, D.addString(""), BTF::FIELD_BYTE_OFFSET});
+  // Incorrect relocation: member index out of range (only two members in bar).
+  D.addReloc({80, 4, D.addString("0:2"), BTF::FIELD_BYTE_OFFSET});
+  // Incorrect relocation: unknown element type id (buz::u[0] access).
+  D.addReloc({88, 6, D.addString("0:0:0"), BTF::FIELD_BYTE_OFFSET});
+  // Incorrect relocation: unknown member type id (buz::v access).
+  D.addReloc({96, 6, D.addString("0:1:0"), BTF::FIELD_BYTE_OFFSET});
+  // Incorrect relocation: non structural type in the middle of access string
+  //   struct bar::v.<something>.
+  D.addReloc({104, 4, D.addString("0:1:0"), BTF::FIELD_BYTE_OFFSET});
+
+  BTFParser BTF;
+  Error E = BTF.parse(D.makeObj());
+  EXPECT_FALSE(E);
+
+  SYMBOLIZE(SA({0, 1}), "<byte_off> [4] struct bar::v (0:1)");
+  SYMBOLIZE(SA({8, 1}), "<byte_sz> [4] struct bar::v (0:1)");
+  SYMBOLIZE(SA({16, 1}), "<field_exists> [4] struct bar::v (0:1)");
+  SYMBOLIZE(SA({24, 1}), "<signed> [4] struct bar::v (0:1)");
+  SYMBOLIZE(SA({32, 1}), "<lshift_u64> [4] struct bar::v (0:1)");
+  SYMBOLIZE(SA({40, 1}), "<rshift_u64> [4] struct bar::v (0:1)");
+  SYMBOLIZE(SA({48, 1}), "<byte_off> [4] struct bar::[7].v (7:1)");
+  SYMBOLIZE(SA({56, 1}), "<byte_off> [4] struct bar::u[1].a (0:0:1:0)");
+  SYMBOLIZE(SA({64, 1}), "<byte_off> [4] struct bar::u[1].b (0:0:1:1)");
+  SYMBOLIZE(SA({72, 1}), "<byte_off> [4] '' <field spec too short>");
+  SYMBOLIZE(SA({80, 1}),
+            "<byte_off> [4] '0:2' "
+            "<member index 2 for spec sub-string 1 is out of range>");
+  SYMBOLIZE(SA({88, 1}), "<byte_off> [6] '0:0:0' "
+                         "<unknown element type id 42 for spec sub-string 2>");
+  SYMBOLIZE(SA({96, 1}), "<byte_off> [6] '0:1:0' "
+                         "<unknown member type id 42 for spec sub-string 1>");
+  SYMBOLIZE(SA({104, 1}), "<byte_off> [4] '0:1:0' "
+                          "<unexpected type kind 1 for spec sub-string 2>");
+}
+
+TEST(BTFParserTest, fieldRelocsMods) {
+  MockData2 D;
+  // struct foo {
+  //   int u;
+  // }
+  // typedef struct foo bar;
+  // struct buz {
+  //   const bar v;
+  // }
+  // typedef buz quux;
+  // const volatile restrict quux <some-var>;
+  uint32_t Int =
+      D.addType({D.addString("int"), mkInfo(BTF::BTF_KIND_INT), {4}});
+  D.addTail((uint32_t)0);
+  uint32_t Foo =
+      D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}});
+  D.addTail(BTF::BTFMember({D.addString("u"), Int, 0}));
+  uint32_t Bar =
+      D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_TYPEDEF), {Foo}});
+  uint32_t CBar =
+      D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_CONST), {Bar}});
+  uint32_t Buz =
+      D.addType({D.addString("buz"), mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}});
+  D.addTail(BTF::BTFMember({D.addString("v"), CBar, 0}));
+  uint32_t Quux =
+      D.addType({D.addString("quux"), mkInfo(BTF::BTF_KIND_TYPEDEF), {Buz}});
+  uint32_t RQuux =
+      D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_RESTRICT), {Quux}});
+  uint32_t VRQuux =
+      D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_VOLATILE), {RQuux}});
+  uint32_t CVRQuux =
+      D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_CONST), {VRQuux}});
+  uint32_t CUnknown =
+      D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_CONST), {77}});
+  uint32_t CVUnknown =
+      D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_VOLATILE), {CUnknown}});
+
+  D.addRelocSec({D.addString("foo"), 0});
+  D.addReloc({0, Bar, D.addString("0:0"), BTF::FIELD_BYTE_OFFSET});
+  D.addReloc({8, CVRQuux, D.addString("0:0:0"), BTF::FIELD_BYTE_OFFSET});
+  D.addReloc({16, CVUnknown, D.addString("0:1:2"), BTF::FIELD_BYTE_OFFSET});
+
+  BTFParser BTF;
+  Error E = BTF.parse(D.makeObj());
+  EXPECT_FALSE(E);
+
+  // Should show modifiers / name of typedef.
+  SYMBOLIZE(SA({0, 1}), "<byte_off> [3] typedef bar::u (0:0)");
+  SYMBOLIZE(SA({8, 1}),
+            "<byte_off> [9] const volatile restrict typedef quux::v.u (0:0:0)");
+  SYMBOLIZE(SA({16, 1}),
+            "<byte_off> [11] '0:1:2' <unknown type id: 77 in modifiers chain>");
+}
+
+TEST(BTFParserTest, relocTypeTagAndVoid) {
+  MockData2 D;
+  // __attribute__((type_tag("tag"))) void
+  uint32_t Tag =
+      D.addType({D.addString("tag"), mkInfo(BTF::BTF_KIND_TYPE_TAG), {0}});
+
+  D.addRelocSec({D.addString("foo"), 0});
+  D.addReloc({0, Tag, D.addString("0"), BTF::TYPE_EXISTENCE});
+  D.addReloc({8, 0 /* void */, D.addString("0"), BTF::TYPE_EXISTENCE});
+
+  BTFParser BTF;
+  Error E = BTF.parse(D.makeObj());
+  EXPECT_FALSE(E);
+
+  SYMBOLIZE(SA({0, 1}), "<type_exists> [1] type_tag(\"tag\") void");
+  SYMBOLIZE(SA({8, 1}), "<type_exists> [0] void");
+}
+
+TEST(BTFParserTest, longRelocModifiersCycle) {
+  MockData2 D;
+
+  D.addType(
+      {D.addString(""), mkInfo(BTF::BTF_KIND_CONST), {1 /* ourselves */}});
+  D.addRelocSec({D.addString("foo"), 0});
+  D.addReloc({0, 1, D.addString(""), BTF::TYPE_EXISTENCE});
+
+  BTFParser BTF;
+  Error E = BTF.parse(D.makeObj());
+  EXPECT_FALSE(E);
+
+  SYMBOLIZE(SA({0, 1}), "<type_exists> [1] '' <modifiers chain is too long>");
+}
+
+TEST(BTFParserTest, relocAnonFieldsAndTypes) {
+  MockData2 D;
+
+  // struct {
+  //   int :32;
+  // } v;
+  uint32_t Int =
+      D.addType({D.addString("int"), mkInfo(BTF::BTF_KIND_INT), {4}});
+  D.addTail((uint32_t)0);
+  uint32_t Anon =
+      D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}});
+  D.addTail(BTF::BTFMember({D.addString(""), Int, 0}));
+
+  D.addRelocSec({D.addString("foo"), 0});
+  D.addReloc({0, Anon, D.addString("0"), BTF::TYPE_EXISTENCE});
+  D.addReloc({8, Anon, D.addString("0:0"), BTF::FIELD_BYTE_OFFSET});
+
+  BTFParser BTF;
+  Error E = BTF.parse(D.makeObj());
+  EXPECT_FALSE(E);
+
+  SYMBOLIZE(SA({0, 1}), "<type_exists> [2] struct <anon 2>");
+  SYMBOLIZE(SA({8, 1}), "<byte_off> [2] struct <anon 2>::<anon 0> (0:0)");
+}
+
+TEST(BTFParserTest, miscBadRelos) {
+  MockData2 D;
+
+  uint32_t S = D.addType({D.addString("S"), mkInfo(BTF::BTF_KIND_STRUCT), {0}});
+
+  D.addRelocSec({D.addString("foo"), 0});
+  D.addReloc({0, 0, D.addString(""), 777});
+  D.addReloc({8, S, D.addString("abc"), BTF::FIELD_BYTE_OFFSET});
+  D.addReloc({16, S, D.addString("0#"), BTF::FIELD_BYTE_OFFSET});
+
+  BTFParser BTF;
+  Error E = BTF.parse(D.makeObj());
+  EXPECT_FALSE(E);
+
+  SYMBOLIZE(SA({0, 1}),
+            "<reloc kind #777> [0] '' <unknown relocation kind: 777>");
+  SYMBOLIZE(SA({8, 1}), "<byte_off> [1] 'abc' <spec string is not a number>");
+  SYMBOLIZE(SA({16, 1}),
+            "<byte_off> [1] '0#' <unexpected spec string delimiter: '#'>");
+}
+
+TEST(BTFParserTest, relocsMultipleSections) {
+  MockData2 D;
+
+  uint32_t S = D.addType({D.addString("S"), mkInfo(BTF::BTF_KIND_STRUCT), {0}});
+  uint32_t T = D.addType({D.addString("T"), mkInfo(BTF::BTF_KIND_STRUCT), {0}});
+
+  D.addRelocSec({D.addString("foo"), 0});
+  D.addReloc({0, S, D.addString(""), BTF::TYPE_EXISTENCE});
+  D.addReloc({8, S, D.addString(""), BTF::TYPE_EXISTENCE});
+
+  D.addRelocSec({D.addString("bar"), 0});
+  D.addReloc({8, T, D.addString(""), BTF::TYPE_EXISTENCE});
+  D.addReloc({16, T, D.addString(""), BTF::TYPE_EXISTENCE});
+
+  BTFParser BTF;
+  Error E = BTF.parse(D.makeObj());
+  EXPECT_FALSE(E);
+
+  EXPECT_TRUE(BTF.findFieldReloc({0, 1}));
+  EXPECT_TRUE(BTF.findFieldReloc({8, 1}));
+  EXPECT_FALSE(BTF.findFieldReloc({16, 1}));
+
+  EXPECT_FALSE(BTF.findFieldReloc({0, 2}));
+  EXPECT_TRUE(BTF.findFieldReloc({8, 2}));
+  EXPECT_TRUE(BTF.findFieldReloc({16, 2}));
+
+  EXPECT_FALSE(BTF.findFieldReloc({0, 3}));
+  EXPECT_FALSE(BTF.findFieldReloc({8, 3}));
+  EXPECT_FALSE(BTF.findFieldReloc({16, 3}));
+
+  auto AssertReloType = [&](const SectionedAddress &A, const char *Name) {
+    const BTF::BPFFieldReloc *Relo = BTF.findFieldReloc(A);
+    ASSERT_TRUE(Relo);
+    const BTF::CommonType *Type = BTF.findType(Relo->TypeID);
+    ASSERT_TRUE(Type);
+    EXPECT_EQ(BTF.findString(Type->NameOff), Name);
+  };
+
+  AssertReloType({8, 1}, "S");
+  AssertReloType({8, 2}, "T");
+}
+
+TEST(BTFParserTest, parserResetReloAndTypes) {
+  BTFParser BTF;
+  MockData2 D;
+
+  // First time: two types, two relocations.
+  D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_STRUCT), {0}});
+  D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_STRUCT), {0}});
+  D.addRelocSec({D.addString("foo"), 0});
+  D.addReloc({0, 1, D.addString(""), BTF::TYPE_EXISTENCE});
+  D.addReloc({8, 2, D.addString(""), BTF::TYPE_EXISTENCE});
+
+  Error E1 = BTF.parse(D.makeObj());
+  EXPECT_FALSE(E1);
+
+  ASSERT_TRUE(BTF.findType(1));
+  EXPECT_EQ(BTF.findString(BTF.findType(1)->NameOff), "foo");
+  EXPECT_TRUE(BTF.findType(2));
+  EXPECT_TRUE(BTF.findFieldReloc({0, 1}));
+  EXPECT_TRUE(BTF.findFieldReloc({8, 1}));
+
+  // Second time: one type, one relocation.
+  D.reset();
+  D.addType({D.addString("buz"), mkInfo(BTF::BTF_KIND_STRUCT), {0}});
+  D.addRelocSec({D.addString("foo"), 0});
+  D.addReloc({0, 1, D.addString(""), BTF::TYPE_EXISTENCE});
+
+  Error E2 = BTF.parse(D.makeObj());
+  EXPECT_FALSE(E2);
+
+  ASSERT_TRUE(BTF.findType(1));
+  EXPECT_EQ(BTF.findString(BTF.findType(1)->NameOff), "buz");
+  EXPECT_FALSE(BTF.findType(2));
+  EXPECT_TRUE(BTF.findFieldReloc({0, 1}));
+  EXPECT_FALSE(BTF.findFieldReloc({8, 1}));
+}
+
+TEST(BTFParserTest, selectiveLoad) {
+  BTFParser BTF1, BTF2, BTF3;
+  MockData2 D;
+
+  D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_STRUCT), {0}});
+  D.addRelocSec({D.addString("foo"), 0});
+  D.addReloc({0, 1, D.addString(""), BTF::TYPE_EXISTENCE});
+  D.addLinesSec({D.addString("foo"), 0});
+  D.addLine({0, D.addString("file.c"), D.addString("some line"), LC(2, 3)});
+
+  BTFParser::ParseOptions Opts;
+
+  ObjectFile &Obj1 = D.makeObj();
+  Opts = {};
+  Opts.LoadLines = true;
+  ASSERT_SUCCEEDED(BTF1.parse(Obj1, Opts));
+
+  Opts = {};
+  Opts.LoadTypes = true;
+  ASSERT_SUCCEEDED(BTF2.parse(Obj1, Opts));
+
+  Opts = {};
+  Opts.LoadRelocs = true;
+  ASSERT_SUCCEEDED(BTF3.parse(Obj1, Opts));
+
+  EXPECT_TRUE(BTF1.findLineInfo({0, 1}));
+  EXPECT_FALSE(BTF2.findLineInfo({0, 1}));
+  EXPECT_FALSE(BTF3.findLineInfo({0, 1}));
+
+  EXPECT_FALSE(BTF1.findType(1));
+  EXPECT_TRUE(BTF2.findType(1));
+  EXPECT_FALSE(BTF3.findType(1));
+
+  EXPECT_FALSE(BTF1.findFieldReloc({0, 1}));
+  EXPECT_FALSE(BTF2.findFieldReloc({0, 1}));
+  EXPECT_TRUE(BTF3.findFieldReloc({0, 1}));
+}
+
 } // namespace


        


More information about the llvm-commits mailing list