[llvm] c8e055d - [BPF][DebugInfo] Use .BPF.ext for line info when DWARF is not available

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 12 09:51:13 PDT 2023


Author: Eduard Zingerman
Date: 2023-07-12T09:51:09-07:00
New Revision: c8e055d485eabf1c8830d77797e3686ced0f7754

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

LOG: [BPF][DebugInfo] Use .BPF.ext for line info when DWARF is not available

"BTF" is a debug information format used by LLVM's BPF backend.
The format is much smaller in scope than DWARF, the following info is
available:
- full set of C types used in the binary file;
- types for global values;
- line number / line source code information .

BTF information is embedded in ELF as .BTF and .BTF.ext sections.
Detailed format description could be found as a part of Linux Source
tree, e.g. here: [1].

This commit modifies `llvm-objdump` utility to use line number
information provided by BTF if DWARF information is not available.
E.g., the goal is to make the following to print source code lines,
interleaved with disassembly:

    $ clang --target=bpf -g test.c -o test.o
    $ llvm-strip --strip-debug test.o
    $ llvm-objdump -Sd test.o

    test.o:	file format elf64-bpf

    Disassembly of section .text:

    <foo>:
    ; void foo(void) {
    	r1 = 0x1
    ;   consume(1);
    	call -0x1
    	r1 = 0x2
    ;   consume(2);
    	call -0x1
    ; }
    	exit

A common production use case for BPF programs is to:
- compile separate object files using clang with `-g -c` flags;
- link these files as a final "static" binary using bpftool linker ([2]).
The bpftool linker discards most of the DWARF sections
(line information sections as well) but merges .BTF and .BTF.ext sections.
Hence, having `llvm-objdump` capable to print source code using .BTF.ext
is valuable.

The commit consists of the following modifications:

- llvm/lib/DebugInfo/BTF aka `DebugInfoBTF` component is added to host
  the code needed to process BTF (with assumption that BTF support
  would be added to some other tools as well, e.g. `llvm-readelf`):
  - `DebugInfoBTF` provides `llvm::BTFParser` class, that loads information
    from `.BTF` and `.BTF.ext` sections of a given `object::ObjectFile`
    instance and allows to query this information.
    Currently only line number information is loaded.

  - `DebugInfoBTF` also provides `llvm::BTFContext` class, which is an
    implementation of `DIContext` interface, used by `llvm-objdump` to
    query information about line numbers corresponding to specific
    instructions.

- Structure `DILineInfo` is modified with field `LineSource`.

  `DIContext` interface uses `DILineInfo` structure to communicate
  line number and source code information.
  Specifically, `DILineInfo::Source` field encodes full file source code,
  if available. BTF only stores source code for selected lines of the
  file, not a complete source file. Moreover, stored lines are not
  guaranteed to be sorted in a specific order.

  To avoid reconstruction of a file source code from a set of
  available lines, this commit adds `LineSource` field instead.

- `Symbolize` class is modified to use `BTFContext` instead of
  `DWARFContext` when DWARF sections are not available but BTF
  sections are present in the object file.
  (`Symbolize` is instantiated by `llvm-objdump`).

- Integration and unit tests.

Note, that DWARF has a notion of "instruction sequence".
DWARF implementation of `DIContext::getLineInfoForAddress()` provides
inexact responses if exact address information is not available but
address falls within "instruction sequence" with some known line
information (see `DWARFDebugLine::LineTable::findRowInSeq()`).

BTF does not provide instruction sequence groupings, thus
`getLineInfoForAddress()` queries only return exact matches.
This does not seem to be a big issue in practice, but output
of the `llvm-objdump -Sd` might differ slightly when BTF
is used instead of DWARF.

[1] https://www.kernel.org/doc/html/latest/bpf/btf.html
[2] https://github.com/libbpf/bpftool

Depends on https://reviews.llvm.org/D149501

Reviewed By: MaskRay, yonghong-song, nickdesaulniers, #debug-info

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

Added: 
    llvm/include/llvm/DebugInfo/BTF/BTFContext.h
    llvm/include/llvm/DebugInfo/BTF/BTFParser.h
    llvm/lib/DebugInfo/BTF/BTFContext.cpp
    llvm/lib/DebugInfo/BTF/BTFParser.cpp
    llvm/lib/DebugInfo/BTF/CMakeLists.txt
    llvm/test/tools/llvm-objdump/BPF/Inputs/test.c
    llvm/test/tools/llvm-objdump/BPF/interleaved-source-test.ll
    llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp
    llvm/unittests/DebugInfo/BTF/CMakeLists.txt

Modified: 
    llvm/include/llvm/DebugInfo/BTF/BTF.h
    llvm/include/llvm/DebugInfo/DIContext.h
    llvm/lib/DebugInfo/CMakeLists.txt
    llvm/lib/DebugInfo/Symbolize/CMakeLists.txt
    llvm/lib/DebugInfo/Symbolize/Symbolize.cpp
    llvm/tools/llvm-objdump/SourcePrinter.cpp
    llvm/tools/llvm-objdump/SourcePrinter.h
    llvm/unittests/DebugInfo/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/DebugInfo/BTF/BTF.h b/llvm/include/llvm/DebugInfo/BTF/BTF.h
index a4ba7c955dc8f1..c1c77cd447d6f6 100644
--- a/llvm/include/llvm/DebugInfo/BTF/BTF.h
+++ b/llvm/include/llvm/DebugInfo/BTF/BTF.h
@@ -245,6 +245,8 @@ struct BPFLineInfo {
   uint32_t LineOff;     ///< Line index in the .BTF string table
   uint32_t LineCol;     ///< Line num: line_col >> 10,
                         ///  col num: line_col & 0x3ff
+  uint32_t getLine() const { return LineCol >> 10; }
+  uint32_t getCol() const { return LineCol & 0x3ff; }
 };
 
 /// Specifying line info's in one section.

diff  --git a/llvm/include/llvm/DebugInfo/BTF/BTFContext.h b/llvm/include/llvm/DebugInfo/BTF/BTFContext.h
new file mode 100644
index 00000000000000..c16bee6133220d
--- /dev/null
+++ b/llvm/include/llvm/DebugInfo/BTF/BTFContext.h
@@ -0,0 +1,58 @@
+//===- BTFContext.h ---------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// BTFContext interface is used by llvm-objdump tool to print source
+// code alongside disassembly.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_DEBUGINFO_BTF_BTFCONTEXT_H
+#define LLVM_DEBUGINFO_BTF_BTFCONTEXT_H
+
+#include "llvm/DebugInfo/BTF/BTFParser.h"
+#include "llvm/DebugInfo/DIContext.h"
+
+namespace llvm {
+
+class BTFContext final : public DIContext {
+  BTFParser BTF;
+
+public:
+  BTFContext() : DIContext(CK_BTF) {}
+
+  void dump(raw_ostream &OS, DIDumpOptions DumpOpts) override {
+    // This function is called from objdump when --dwarf=? option is set.
+    // BTF is no DWARF, so ignore this operation for now.
+  }
+
+  DILineInfo getLineInfoForAddress(
+      object::SectionedAddress Address,
+      DILineInfoSpecifier Specifier = DILineInfoSpecifier()) override;
+
+  DILineInfo
+  getLineInfoForDataAddress(object::SectionedAddress Address) override;
+
+  DILineInfoTable getLineInfoForAddressRange(
+      object::SectionedAddress Address, uint64_t Size,
+      DILineInfoSpecifier Specifier = DILineInfoSpecifier()) override;
+
+  DIInliningInfo getInliningInfoForAddress(
+      object::SectionedAddress Address,
+      DILineInfoSpecifier Specifier = DILineInfoSpecifier()) override;
+
+  std::vector<DILocal>
+  getLocalsForAddress(object::SectionedAddress Address) override;
+
+  static std::unique_ptr<BTFContext> create(
+      const object::ObjectFile &Obj,
+      std::function<void(Error)> ErrorHandler = WithColor::defaultErrorHandler);
+};
+
+} // end namespace llvm
+
+#endif // LLVM_DEBUGINFO_BTF_BTFCONTEXT_H

diff  --git a/llvm/include/llvm/DebugInfo/BTF/BTFParser.h b/llvm/include/llvm/DebugInfo/BTF/BTFParser.h
new file mode 100644
index 00000000000000..33d0f32c7c55f8
--- /dev/null
+++ b/llvm/include/llvm/DebugInfo/BTF/BTFParser.h
@@ -0,0 +1,81 @@
+//===- BTFParser.h ----------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// BTFParser reads .BTF and .BTF.ext ELF sections generated by LLVM
+// BPF backend and provides introspection for the stored information.
+// Currently the following information is accessible:
+// - string table;
+// - instruction offset to line information mapping.
+//
+// See llvm/DebugInfo/BTF/BTF.h for some details about binary format
+// and links to Linux Kernel documentation.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_DEBUGINFO_BTF_BTFPARSER_H
+#define LLVM_DEBUGINFO_BTF_BTFPARSER_H
+
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/DebugInfo/BTF/BTF.h"
+#include "llvm/Object/ObjectFile.h"
+#include "llvm/Support/DataExtractor.h"
+
+namespace llvm {
+using object::ObjectFile;
+using object::SectionedAddress;
+using object::SectionRef;
+
+class BTFParser {
+  using BTFLinesVector = SmallVector<BTF::BPFLineInfo, 0>;
+
+  // In BTF strings are stored as a continuous memory region with
+  // individual strings separated by 0 bytes. Strings are identified
+  // by an offset in such region.
+  // The `StringsTable` points to this region in the parsed ObjectFile.
+  StringRef StringsTable;
+
+  // Maps ELF section number to instruction line number information.
+  // Each BTFLinesVector is sorted by `InsnOffset` to allow fast lookups.
+  DenseMap<uint64_t, BTFLinesVector> SectionLines;
+
+  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);
+
+public:
+  // Looks-up a string in the .BTF section's string table.
+  // Offset is relative to string table start.
+  StringRef findString(uint32_t Offset) const;
+
+  // Search for line information for a specific address,
+  // address match is exact (contrary to DWARFContext).
+  // Return nullptr if no information found.
+  // If information is present, return a pointer to object
+  // owned by this class.
+  const BTF::BPFLineInfo *findLineInfo(SectionedAddress Address) const;
+
+  // 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.
+  //
+  // If information cannot be parsed:
+  // - return an error describing the failure;
+  // - 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);
+
+  // Return true if `Obj` has .BTF and .BTF.ext sections.
+  static bool hasBTFSections(const ObjectFile &Obj);
+};
+
+} // namespace llvm
+
+#endif // LLVM_DEBUGINFO_BTF_BTFPARSER_H

diff  --git a/llvm/include/llvm/DebugInfo/DIContext.h b/llvm/include/llvm/DebugInfo/DIContext.h
index 6866a6614b56bc..9ad27033ec110c 100644
--- a/llvm/include/llvm/DebugInfo/DIContext.h
+++ b/llvm/include/llvm/DebugInfo/DIContext.h
@@ -37,7 +37,11 @@ struct DILineInfo {
   std::string FileName;
   std::string FunctionName;
   std::string StartFileName;
+  // Full source corresponding to `FileName`
   std::optional<StringRef> Source;
+  // Source code for this particular line
+  // (in case if `Source` is not available)
+  std::optional<StringRef> LineSource;
   uint32_t Line = 0;
   uint32_t Column = 0;
   uint32_t StartLine = 0;
@@ -228,7 +232,7 @@ struct DIDumpOptions {
 
 class DIContext {
 public:
-  enum DIContextKind { CK_DWARF, CK_PDB };
+  enum DIContextKind { CK_DWARF, CK_PDB, CK_BTF };
 
   DIContext(DIContextKind K) : Kind(K) {}
   virtual ~DIContext() = default;

diff  --git a/llvm/lib/DebugInfo/BTF/BTFContext.cpp b/llvm/lib/DebugInfo/BTF/BTFContext.cpp
new file mode 100644
index 00000000000000..24898739b8241a
--- /dev/null
+++ b/llvm/lib/DebugInfo/BTF/BTFContext.cpp
@@ -0,0 +1,69 @@
+//===- BTFContext.cpp ---------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Implementation of the BTFContext interface, this is used by
+// llvm-objdump tool to print source code alongside disassembly.
+// In fact, currently it is a simple wrapper for BTFParser instance.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/DebugInfo/BTF/BTFContext.h"
+
+#define DEBUG_TYPE "debug-info-btf-context"
+
+using namespace llvm;
+using object::ObjectFile;
+using object::SectionedAddress;
+
+DILineInfo BTFContext::getLineInfoForAddress(SectionedAddress Address,
+                                             DILineInfoSpecifier Specifier) {
+  const BTF::BPFLineInfo *LineInfo = BTF.findLineInfo(Address);
+  DILineInfo Result;
+  if (!LineInfo)
+    return Result;
+
+  Result.LineSource = BTF.findString(LineInfo->LineOff);
+  Result.FileName = BTF.findString(LineInfo->FileNameOff);
+  Result.Line = LineInfo->getLine();
+  Result.Column = LineInfo->getCol();
+  return Result;
+}
+
+DILineInfo BTFContext::getLineInfoForDataAddress(SectionedAddress Address) {
+  // BTF does not convey such information.
+  return {};
+}
+
+DILineInfoTable
+BTFContext::getLineInfoForAddressRange(SectionedAddress Address, uint64_t Size,
+                                       DILineInfoSpecifier Specifier) {
+  // This function is used only from llvm-rtdyld utility and a few
+  // JITEventListener implementations. Ignore it for now.
+  return {};
+}
+
+DIInliningInfo
+BTFContext::getInliningInfoForAddress(SectionedAddress Address,
+                                      DILineInfoSpecifier Specifier) {
+  // BTF does not convey such information
+  return {};
+}
+
+std::vector<DILocal> BTFContext::getLocalsForAddress(SectionedAddress Address) {
+  // BTF does not convey such information
+  return {};
+}
+
+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))
+    ErrorHandler(std::move(E));
+  return Ctx;
+}

diff  --git a/llvm/lib/DebugInfo/BTF/BTFParser.cpp b/llvm/lib/DebugInfo/BTF/BTFParser.cpp
new file mode 100644
index 00000000000000..6151e1b15cbbf5
--- /dev/null
+++ b/llvm/lib/DebugInfo/BTF/BTFParser.cpp
@@ -0,0 +1,283 @@
+//===- BTFParser.cpp ------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// BTFParser reads/interprets .BTF and .BTF.ext ELF sections.
+// Refer to BTFParser.h for API description.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/DebugInfo/BTF/BTFParser.h"
+#include "llvm/Support/Errc.h"
+
+#define DEBUG_TYPE "debug-info-btf-parser"
+
+using namespace llvm;
+using object::ObjectFile;
+using object::SectionedAddress;
+using object::SectionRef;
+
+const char BTFSectionName[] = ".BTF";
+const char BTFExtSectionName[] = ".BTF.ext";
+
+// Utility class with API similar to raw_ostream but can be cast
+// to Error, e.g.:
+//
+// Error foo(...) {
+//   ...
+//   if (Error E = bar(...))
+//     return Err("error while foo(): ") << E;
+//   ...
+// }
+//
+namespace {
+class Err {
+  std::string Buffer;
+  raw_string_ostream Stream;
+
+public:
+  Err(const char *InitialMsg) : Buffer(InitialMsg), Stream(Buffer) {}
+  Err(const char *SectionName, DataExtractor::Cursor &C)
+      : Buffer(), Stream(Buffer) {
+    *this << "error while reading " << SectionName
+          << " section: " << C.takeError();
+  };
+
+  template <typename T> Err &operator<<(T Val) {
+    Stream << Val;
+    return *this;
+  }
+
+  Err &write_hex(unsigned long long Val) {
+    Stream.write_hex(Val);
+    return *this;
+  }
+
+  Err &operator<<(Error Val) {
+    handleAllErrors(std::move(Val),
+                    [=](ErrorInfoBase &Info) { Stream << Info.message(); });
+    return *this;
+  }
+
+  operator Error() const {
+    return make_error<StringError>(Buffer, errc::invalid_argument);
+  }
+};
+} // anonymous namespace
+
+// ParseContext wraps information that is only necessary while parsing
+// ObjectFile and can be discarded once parsing is done.
+// Used by BTFParser::parse* auxiliary functions.
+struct BTFParser::ParseContext {
+  const ObjectFile &Obj;
+  // Map from ELF section name to SectionRef
+  DenseMap<StringRef, SectionRef> Sections;
+
+public:
+  ParseContext(const ObjectFile &Obj) : Obj(Obj) {}
+
+  Expected<DataExtractor> makeExtractor(SectionRef Sec) {
+    Expected<StringRef> Contents = Sec.getContents();
+    if (!Contents)
+      return Contents.takeError();
+    return DataExtractor(Contents.get(), Obj.isLittleEndian(),
+                         Obj.getBytesInAddress());
+  }
+
+  std::optional<SectionRef> findSection(StringRef Name) const {
+    auto It = Sections.find(Name);
+    if (It != Sections.end())
+      return It->second;
+    return std::nullopt;
+  }
+};
+
+Error BTFParser::parseBTF(ParseContext &Ctx, SectionRef BTF) {
+  Expected<DataExtractor> MaybeExtractor = Ctx.makeExtractor(BTF);
+  if (!MaybeExtractor)
+    return MaybeExtractor.takeError();
+
+  DataExtractor &Extractor = MaybeExtractor.get();
+  DataExtractor::Cursor C = DataExtractor::Cursor(0);
+  uint16_t Magic = Extractor.getU16(C);
+  if (!C)
+    return Err(".BTF", C);
+  if (Magic != BTF::MAGIC)
+    return Err("invalid .BTF magic: ").write_hex(Magic);
+  uint8_t Version = Extractor.getU8(C);
+  if (!C)
+    return Err(".BTF", C);
+  if (Version != 1)
+    return Err("unsupported .BTF version: ") << (unsigned)Version;
+  (void)Extractor.getU8(C); // flags
+  uint32_t HdrLen = Extractor.getU32(C);
+  if (!C)
+    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 StrOff = Extractor.getU32(C);
+  uint32_t StrLen = Extractor.getU32(C);
+  uint32_t StrStart = HdrLen + StrOff;
+  uint32_t StrEnd = StrStart + StrLen;
+  if (!C)
+    return Err(".BTF", C);
+  if (Extractor.getData().size() < StrEnd)
+    return Err("invalid .BTF section size, expecting at-least ")
+           << StrEnd << " bytes";
+
+  StringsTable = Extractor.getData().substr(StrStart, StrLen);
+  return Error::success();
+}
+
+Error BTFParser::parseBTFExt(ParseContext &Ctx, SectionRef BTFExt) {
+  Expected<DataExtractor> MaybeExtractor = Ctx.makeExtractor(BTFExt);
+  if (!MaybeExtractor)
+    return MaybeExtractor.takeError();
+
+  DataExtractor &Extractor = MaybeExtractor.get();
+  DataExtractor::Cursor C = DataExtractor::Cursor(0);
+  uint16_t Magic = Extractor.getU16(C);
+  if (!C)
+    return Err(".BTF.ext", C);
+  if (Magic != BTF::MAGIC)
+    return Err("invalid .BTF.ext magic: ").write_hex(Magic);
+  uint8_t Version = Extractor.getU8(C);
+  if (!C)
+    return Err(".BTF", C);
+  if (Version != 1)
+    return Err("unsupported .BTF.ext version: ") << (unsigned)Version;
+  (void)Extractor.getU8(C); // flags
+  uint32_t HdrLen = Extractor.getU32(C);
+  if (!C)
+    return Err(".BTF.ext", C);
+  if (HdrLen < 8)
+    return Err("unexpected .BTF.ext header length: ") << HdrLen;
+  (void)Extractor.getU32(C); // func_info_off
+  (void)Extractor.getU32(C); // func_info_len
+  uint32_t LineInfoOff = Extractor.getU32(C);
+  uint32_t LineInfoLen = 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;
+
+  return Error::success();
+}
+
+Error BTFParser::parseLineInfo(ParseContext &Ctx, DataExtractor &Extractor,
+                               uint64_t LineInfoStart, uint64_t LineInfoEnd) {
+  DataExtractor::Cursor C = DataExtractor::Cursor(LineInfoStart);
+  uint32_t RecSize = Extractor.getU32(C);
+  if (!C)
+    return Err(".BTF.ext", C);
+  if (RecSize < 16)
+    return Err("unexpected .BTF.ext line info record length: ") << RecSize;
+
+  while (C && C.tell() < LineInfoEnd) {
+    uint32_t SecNameOff = Extractor.getU32(C);
+    uint32_t NumInfo = Extractor.getU32(C);
+    StringRef SecName = findString(SecNameOff);
+    std::optional<SectionRef> Sec = Ctx.findSection(SecName);
+    if (!C)
+      return Err(".BTF.ext", C);
+    if (!Sec)
+      return Err("") << "can't find section '" << SecName
+                     << "' while parsing .BTF.ext line info";
+    BTFLinesVector &Lines = SectionLines[Sec->getIndex()];
+    for (uint32_t I = 0; C && I < NumInfo; ++I) {
+      uint64_t RecStart = C.tell();
+      uint32_t InsnOff = Extractor.getU32(C);
+      uint32_t FileNameOff = Extractor.getU32(C);
+      uint32_t LineOff = Extractor.getU32(C);
+      uint32_t LineCol = Extractor.getU32(C);
+      if (!C)
+        return Err(".BTF.ext", C);
+      Lines.push_back({InsnOff, FileNameOff, LineOff, LineCol});
+      C.seek(RecStart + RecSize);
+    }
+    llvm::stable_sort(Lines,
+                      [](const BTF::BPFLineInfo &L, const BTF::BPFLineInfo &R) {
+                        return L.InsnOffset < R.InsnOffset;
+                      });
+  }
+  if (!C)
+    return Err(".BTF.ext", C);
+
+  return Error::success();
+}
+
+Error BTFParser::parse(const ObjectFile &Obj) {
+  StringsTable = StringRef();
+  SectionLines.clear();
+
+  ParseContext Ctx(Obj);
+  std::optional<SectionRef> BTF;
+  std::optional<SectionRef> BTFExt;
+  for (SectionRef Sec : Obj.sections()) {
+    Expected<StringRef> MaybeName = Sec.getName();
+    if (!MaybeName)
+      return Err("error while reading section name: ") << MaybeName.takeError();
+    Ctx.Sections[*MaybeName] = Sec;
+    if (*MaybeName == BTFSectionName)
+      BTF = Sec;
+    if (*MaybeName == BTFExtSectionName)
+      BTFExt = Sec;
+  }
+  if (!BTF)
+    return Err("can't find .BTF section");
+  if (!BTFExt)
+    return Err("can't find .BTF.ext section");
+  if (Error E = parseBTF(Ctx, *BTF))
+    return E;
+  if (Error E = parseBTFExt(Ctx, *BTFExt))
+    return E;
+
+  return Error::success();
+}
+
+bool BTFParser::hasBTFSections(const ObjectFile &Obj) {
+  bool HasBTF = false;
+  bool HasBTFExt = false;
+  for (SectionRef Sec : Obj.sections()) {
+    Expected<StringRef> Name = Sec.getName();
+    if (Error E = Name.takeError()) {
+      logAllUnhandledErrors(std::move(E), errs());
+      continue;
+    }
+    HasBTF |= *Name == BTFSectionName;
+    HasBTFExt |= *Name == BTFExtSectionName;
+    if (HasBTF && HasBTFExt)
+      return true;
+  }
+  return false;
+}
+
+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())
+    return nullptr;
+
+  const BTFLinesVector &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)
+    return nullptr;
+
+  return LineInfo;
+}

diff  --git a/llvm/lib/DebugInfo/BTF/CMakeLists.txt b/llvm/lib/DebugInfo/BTF/CMakeLists.txt
new file mode 100644
index 00000000000000..e8857b77bd455a
--- /dev/null
+++ b/llvm/lib/DebugInfo/BTF/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_llvm_component_library(LLVMDebugInfoBTF
+  BTFParser.cpp
+  BTFContext.cpp
+  ADDITIONAL_HEADER_DIRS
+  "${LLVM_MAIN_INCLUDE_DIR}/llvm/DebugInfo/BTF"
+
+  LINK_COMPONENTS
+  Support
+  )

diff  --git a/llvm/lib/DebugInfo/CMakeLists.txt b/llvm/lib/DebugInfo/CMakeLists.txt
index d004981fb087a9..0d72630f09ab66 100644
--- a/llvm/lib/DebugInfo/CMakeLists.txt
+++ b/llvm/lib/DebugInfo/CMakeLists.txt
@@ -5,3 +5,4 @@ add_subdirectory(MSF)
 add_subdirectory(CodeView)
 add_subdirectory(PDB)
 add_subdirectory(Symbolize)
+add_subdirectory(BTF)

diff  --git a/llvm/lib/DebugInfo/Symbolize/CMakeLists.txt b/llvm/lib/DebugInfo/Symbolize/CMakeLists.txt
index 8c166999c7a2f8..29f62bf6156fc6 100644
--- a/llvm/lib/DebugInfo/Symbolize/CMakeLists.txt
+++ b/llvm/lib/DebugInfo/Symbolize/CMakeLists.txt
@@ -11,6 +11,7 @@ add_llvm_component_library(LLVMSymbolize
   LINK_COMPONENTS
   DebugInfoDWARF
   DebugInfoPDB
+  DebugInfoBTF
   Object
   Support
   Demangle

diff  --git a/llvm/lib/DebugInfo/Symbolize/Symbolize.cpp b/llvm/lib/DebugInfo/Symbolize/Symbolize.cpp
index 459baf7b836403..517f1e7dc284f9 100644
--- a/llvm/lib/DebugInfo/Symbolize/Symbolize.cpp
+++ b/llvm/lib/DebugInfo/Symbolize/Symbolize.cpp
@@ -13,6 +13,7 @@
 #include "llvm/DebugInfo/Symbolize/Symbolize.h"
 
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/DebugInfo/BTF/BTFContext.h"
 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
 #include "llvm/DebugInfo/PDB/PDB.h"
 #include "llvm/DebugInfo/PDB/PDBContext.h"
@@ -615,6 +616,13 @@ LLVMSymbolizer::getOrCreateModuleInfo(const std::string &ModuleName) {
   return ModuleOrErr;
 }
 
+// For BPF programs .BTF.ext section contains line numbers information,
+// use it if regular DWARF is not available (e.g. for stripped binary).
+static bool useBTFContext(const ObjectFile &Obj) {
+  return Obj.makeTriple().isBPF() && !Obj.hasDebugInfo() &&
+         BTFParser::hasBTFSections(Obj);
+}
+
 Expected<SymbolizableModule *>
 LLVMSymbolizer::getOrCreateModuleInfo(const ObjectFile &Obj) {
   StringRef ObjName = Obj.getFileName();
@@ -622,7 +630,11 @@ LLVMSymbolizer::getOrCreateModuleInfo(const ObjectFile &Obj) {
   if (I != Modules.end())
     return I->second.get();
 
-  std::unique_ptr<DIContext> Context = DWARFContext::create(Obj);
+  std::unique_ptr<DIContext> Context;
+  if (useBTFContext(Obj))
+    Context = BTFContext::create(Obj);
+  else
+    Context = DWARFContext::create(Obj);
   // FIXME: handle COFF object with PDB info to use PDBContext
   return createModuleInfo(&Obj, std::move(Context), ObjName);
 }

diff  --git a/llvm/test/tools/llvm-objdump/BPF/Inputs/test.c b/llvm/test/tools/llvm-objdump/BPF/Inputs/test.c
new file mode 100644
index 00000000000000..bf7b3cec0b16d0
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/BPF/Inputs/test.c
@@ -0,0 +1,20 @@
+extern int consume(int);
+
+void foo(void) {
+  consume(1);
+  consume(2);
+}
+
+void bar(void) {
+  consume(3);
+}
+
+__attribute__((section("a")))
+void buz(void) {
+  consume(4);
+}
+
+__attribute__((section("b")))
+void quux(void) {
+  consume(5);
+}

diff  --git a/llvm/test/tools/llvm-objdump/BPF/interleaved-source-test.ll b/llvm/test/tools/llvm-objdump/BPF/interleaved-source-test.ll
new file mode 100644
index 00000000000000..fb5ad51d1ea661
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/BPF/interleaved-source-test.ll
@@ -0,0 +1,139 @@
+; REQUIRES: bpf-registered-target
+
+;; Verify that llvm-objdump can use .BTF.ext to extract line number
+;; information in disassembly when DWARF is not available.
+
+;; The 'sed' part is needed because llc would look for source file in
+;; order to embed line info when BPF is compiled.
+
+; RUN: sed -e "s,SRC_COMPDIR,%/p/Inputs,g" %s > %t.ll
+
+;; First, check bpfel (little endian):
+;; - compile %t.o
+;; - check llvm-objdump output when both DWARF and BTF are present
+;; - strip debug info from %t.o
+;; - make sure that there are BTF but no DWARF sections in %t.o
+;; - check llvm-objdump output when only BTF is present
+
+; RUN: llc --mtriple bpfel %t.ll --filetype=obj -o %t
+; RUN: llvm-objdump --no-show-raw-insn -S %t | FileCheck %s
+; RUN: llvm-strip --strip-debug %t
+; RUN: llvm-objdump --section-headers %t \
+; RUN:   | FileCheck --implicit-check-not=.debug_ --check-prefix=SECTIONS %s
+; RUN: llvm-objdump --no-show-raw-insn -S %t | FileCheck %s
+
+;; Next, check bpfeb (big endian):
+
+; RUN: llc --mtriple bpfeb %t.ll --filetype=obj -o %t
+; RUN: llvm-strip --strip-debug %t
+; RUN: llvm-objdump --no-show-raw-insn -S %t | FileCheck %s
+
+;; Test case adapted from output of the following command:
+;;
+;;  clang -g --target=bpf -emit-llvm -S ./Inputs/test.c
+;;
+;; DIFile::directory is changed to SRC_COMPDIR.
+
+; SECTIONS: .BTF
+; SECTIONS: .BTF.ext
+
+;; Check inlined source code in disassembly:
+
+; CHECK:      Disassembly of section .text:
+; CHECK-EMPTY:
+; CHECK-NEXT: [[#%x,]] <foo>:
+; CHECK-NEXT: ;   consume(1);
+; CHECK-NEXT:        0:	r1 = 0x1
+; CHECK-NEXT:        1:	call -0x1
+; CHECK-NEXT: ;   consume(2);
+; CHECK-NEXT:        2:	r1 = 0x2
+; CHECK-NEXT:        3:	call -0x1
+; CHECK-NEXT: ; }
+; CHECK-NEXT:        4:	exit
+; CHECK-EMPTY:
+; CHECK-NEXT: [[#%x,]] <bar>:
+; CHECK-NEXT: ;   consume(3);
+; CHECK-NEXT:        5:	r1 = 0x3
+; CHECK-NEXT:        6:	call -0x1
+; CHECK-NEXT: ; }
+; CHECK-NEXT:        7:	exit
+; CHECK-EMPTY:
+; CHECK-NEXT: Disassembly of section a:
+; CHECK-EMPTY:
+; CHECK-NEXT: [[#%x,]] <buz>:
+; CHECK-NEXT: ;   consume(4);
+; CHECK-NEXT:        0:	r1 = 0x4
+; CHECK-NEXT:        1:	call -0x1
+; CHECK-NEXT: ; }
+; CHECK-NEXT:        2:	exit
+; CHECK-EMPTY:
+; CHECK-NEXT: Disassembly of section b:
+; CHECK-EMPTY:
+; CHECK-NEXT: [[#%x,]] <quux>:
+; CHECK-NEXT: ;   consume(5);
+; CHECK-NEXT:        0:	r1 = 0x5
+; CHECK-NEXT:        1:	call -0x1
+; CHECK-NEXT: ; }
+; CHECK-NEXT:        2:	exit
+
+; Function Attrs: noinline nounwind optnone
+define dso_local void @foo() #0 !dbg !7 {
+entry:
+  %call = call i32 @consume(i32 noundef 1), !dbg !11
+  %call1 = call i32 @consume(i32 noundef 2), !dbg !12
+  ret void, !dbg !13
+}
+
+declare dso_local i32 @consume(i32 noundef) #1
+
+; Function Attrs: noinline nounwind optnone
+define dso_local void @bar() #0 !dbg !14 {
+entry:
+  %call = call i32 @consume(i32 noundef 3), !dbg !15
+  ret void, !dbg !16
+}
+
+; Function Attrs: noinline nounwind optnone
+define dso_local void @buz() #0 section "a" !dbg !17 {
+entry:
+  %call = call i32 @consume(i32 noundef 4), !dbg !18
+  ret void, !dbg !19
+}
+
+; Function Attrs: noinline nounwind optnone
+define dso_local void @quux() #0 section "b" !dbg !20 {
+entry:
+  %call = call i32 @consume(i32 noundef 5), !dbg !21
+  ret void, !dbg !22
+}
+
+attributes #0 = { noinline nounwind optnone "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
+attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5}
+!llvm.ident = !{!6}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 81674c88f80fa7d9c55d4aee945f844b67f03267)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.c", directory: "SRC_COMPDIR", checksumkind: CSK_MD5, checksum: "292d67837b080844462efb2a6b004f09")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{i32 7, !"frame-pointer", i32 2}
+!6 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 81674c88f80fa7d9c55d4aee945f844b67f03267)"}
+!7 = distinct !DISubprogram(name: "foo", scope: !8, file: !8, line: 3, type: !9, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DIFile(filename: "test.c", directory: "SRC_COMPDIR", checksumkind: CSK_MD5, checksum: "292d67837b080844462efb2a6b004f09")
+!9 = !DISubroutineType(types: !10)
+!10 = !{null}
+!11 = !DILocation(line: 4, column: 3, scope: !7)
+!12 = !DILocation(line: 5, column: 3, scope: !7)
+!13 = !DILocation(line: 6, column: 1, scope: !7)
+!14 = distinct !DISubprogram(name: "bar", scope: !8, file: !8, line: 8, type: !9, scopeLine: 8, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!15 = !DILocation(line: 9, column: 3, scope: !14)
+!16 = !DILocation(line: 10, column: 1, scope: !14)
+!17 = distinct !DISubprogram(name: "buz", scope: !8, file: !8, line: 13, type: !9, scopeLine: 13, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!18 = !DILocation(line: 14, column: 3, scope: !17)
+!19 = !DILocation(line: 15, column: 1, scope: !17)
+!20 = distinct !DISubprogram(name: "quux", scope: !8, file: !8, line: 18, type: !9, scopeLine: 18, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!21 = !DILocation(line: 19, column: 3, scope: !20)
+!22 = !DILocation(line: 20, column: 1, scope: !20)

diff  --git a/llvm/tools/llvm-objdump/SourcePrinter.cpp b/llvm/tools/llvm-objdump/SourcePrinter.cpp
index 04c767680d55a3..b2fe56cf2e1cc0 100644
--- a/llvm/tools/llvm-objdump/SourcePrinter.cpp
+++ b/llvm/tools/llvm-objdump/SourcePrinter.cpp
@@ -448,6 +448,34 @@ void SourcePrinter::printLines(formatted_raw_ostream &OS,
   }
 }
 
+// Get the source line text for LineInfo:
+// - use LineInfo::LineSource if available;
+// - use LineCache if LineInfo::Source otherwise.
+StringRef SourcePrinter::getLine(const DILineInfo &LineInfo,
+                                 StringRef ObjectFilename) {
+  if (LineInfo.LineSource)
+    return LineInfo.LineSource.value();
+
+  if (SourceCache.find(LineInfo.FileName) == SourceCache.end())
+    if (!cacheSource(LineInfo))
+      return {};
+
+  auto LineBuffer = LineCache.find(LineInfo.FileName);
+  if (LineBuffer == LineCache.end())
+    return {};
+
+  if (LineInfo.Line > LineBuffer->second.size()) {
+    reportWarning(
+        formatv("debug info line number {0} exceeds the number of lines in {1}",
+                LineInfo.Line, LineInfo.FileName),
+        ObjectFilename);
+    return {};
+  }
+
+  // Vector begins at 0, line numbers are non-zero
+  return LineBuffer->second[LineInfo.Line - 1];
+}
+
 void SourcePrinter::printSources(formatted_raw_ostream &OS,
                                  const DILineInfo &LineInfo,
                                  StringRef ObjectFilename, StringRef Delimiter,
@@ -457,21 +485,9 @@ void SourcePrinter::printSources(formatted_raw_ostream &OS,
        OldLineInfo.FileName == LineInfo.FileName))
     return;
 
-  if (SourceCache.find(LineInfo.FileName) == SourceCache.end())
-    if (!cacheSource(LineInfo))
-      return;
-  auto LineBuffer = LineCache.find(LineInfo.FileName);
-  if (LineBuffer != LineCache.end()) {
-    if (LineInfo.Line > LineBuffer->second.size()) {
-      reportWarning(
-          formatv(
-              "debug info line number {0} exceeds the number of lines in {1}",
-              LineInfo.Line, LineInfo.FileName),
-          ObjectFilename);
-      return;
-    }
-    // Vector begins at 0, line numbers are non-zero
-    OS << Delimiter << LineBuffer->second[LineInfo.Line - 1];
+  StringRef Line = getLine(LineInfo, ObjectFilename);
+  if (!Line.empty()) {
+    OS << Delimiter << Line;
     LVP.printBetweenInsts(OS, true);
   }
 }

diff  --git a/llvm/tools/llvm-objdump/SourcePrinter.h b/llvm/tools/llvm-objdump/SourcePrinter.h
index 6209bb0e43e409..fc67fc65074441 100644
--- a/llvm/tools/llvm-objdump/SourcePrinter.h
+++ b/llvm/tools/llvm-objdump/SourcePrinter.h
@@ -151,6 +151,10 @@ class SourcePrinter {
                     StringRef ObjectFilename, StringRef Delimiter,
                     LiveVariablePrinter &LVP);
 
+  // Returns line source code corresponding to `LineInfo`.
+  // Returns empty string if source code cannot be found.
+  StringRef getLine(const DILineInfo &LineInfo, StringRef ObjectFilename);
+
 public:
   SourcePrinter() = default;
   SourcePrinter(const object::ObjectFile *Obj, StringRef DefaultArch);

diff  --git a/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp b/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp
new file mode 100644
index 00000000000000..097d6205adecd5
--- /dev/null
+++ b/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp
@@ -0,0 +1,353 @@
+//===-- SourcePrinter.cpp -  source interleaving utilities ----------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/DebugInfo/BTF/BTFContext.h"
+#include "llvm/ObjectYAML/YAML.h"
+#include "llvm/ObjectYAML/yaml2obj.h"
+#include "llvm/Testing/Support/Error.h"
+
+using namespace llvm;
+using namespace llvm::object;
+
+#define LC(Line, Col) ((Line << 10u) | Col)
+
+const char BTFEndOfData[] =
+    "error while reading .BTF section: unexpected end of data";
+const char BTFExtEndOfData[] =
+    "error while reading .BTF.ext section: unexpected end of data";
+
+static raw_ostream &operator<<(raw_ostream &OS, const yaml::BinaryRef &Ref) {
+  Ref.writeAsHex(OS);
+  return OS;
+}
+
+template <typename T>
+static yaml::BinaryRef makeBinRef(const T *Ptr, size_t Size = sizeof(T)) {
+  return yaml::BinaryRef(ArrayRef<uint8_t>((const uint8_t *)Ptr, Size));
+}
+
+namespace {
+// This is a mockup for an ELF file containing .BTF and .BTF.ext sections.
+// Binary content of these sections corresponds to the value of
+// MockData1::BTF and MockData1::Ext fields.
+//
+// The yaml::yaml2ObjectFile() is used to generate actual ELF,
+// see MockData1::makeObj().
+//
+// The `BTF` and `Ext` fields are initialized with correct values
+// valid for a small example with a few sections, fields could be
+// modified before a call to `makeObj()` to test parser with invalid
+// input, etc.
+
+// Use "pragma pack" to model .BTF & .BTF.ext sections content using
+// 'struct' objects. This pragma is supported by CLANG, GCC & MSVC,
+// which matters for LLVM CI.
+#pragma pack(push, 1)
+struct MockData1 {
+  struct B {
+    BTF::Header Header = {};
+    // no types
+    struct S {
+      char Foo[4] = "foo";
+      char Bar[4] = "bar";
+      char Buz[4] = "buz";
+      char Line1[11] = "first line";
+      char Line2[12] = "second line";
+      char File1[4] = "a.c";
+      char File2[4] = "b.c";
+    } Strings;
+
+    B() {
+      Header.Magic = BTF::MAGIC;
+      Header.Version = 1;
+      Header.HdrLen = sizeof(Header);
+      Header.StrOff = offsetof(B, Strings) - sizeof(Header);
+      Header.StrLen = sizeof(Strings);
+    }
+  } BTF;
+
+  struct E {
+    BTF::ExtHeader Header = {};
+    // no func info
+    struct {
+      uint32_t LineRecSize = sizeof(BTF::BPFLineInfo);
+      struct {
+        BTF::SecLineInfo Sec = {offsetof(B::S, Foo), 2};
+        BTF::BPFLineInfo Lines[2] = {
+            {16, offsetof(B::S, File1), offsetof(B::S, Line1), LC(7, 1)},
+            {32, offsetof(B::S, File1), offsetof(B::S, Line2), LC(14, 5)},
+        };
+      } Foo;
+      struct {
+        BTF::SecLineInfo Sec = {offsetof(B::S, Bar), 1};
+        BTF::BPFLineInfo Lines[1] = {
+            {0, offsetof(B::S, File2), offsetof(B::S, Line1), LC(42, 4)},
+        };
+      } Bar;
+    } Lines;
+
+    E() {
+      Header.Magic = BTF::MAGIC;
+      Header.Version = 1;
+      Header.HdrLen = sizeof(Header);
+      Header.LineInfoOff = offsetof(E, Lines) - sizeof(Header);
+      Header.LineInfoLen = sizeof(Lines);
+    }
+  } Ext;
+
+  int BTFSectionLen = sizeof(BTF);
+  int ExtSectionLen = sizeof(Ext);
+
+  SmallString<0> Storage;
+  std::unique_ptr<ObjectFile> Obj;
+
+  ObjectFile &makeObj() {
+    std::string Buffer;
+    raw_string_ostream Yaml(Buffer);
+    Yaml << R"(
+!ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_BPF
+Sections:
+  - Name:     foo
+    Type:     SHT_PROGBITS
+    Size:     0x0
+  - Name:     bar
+    Type:     SHT_PROGBITS
+    Size:     0x0)";
+
+    if (BTFSectionLen >= 0)
+      Yaml << R"(
+  - Name:     .BTF
+    Type:     SHT_PROGBITS
+    Content: )"
+           << makeBinRef(&BTF, BTFSectionLen);
+
+    if (ExtSectionLen >= 0)
+      Yaml << R"(
+  - Name:     .BTF.ext
+    Type:     SHT_PROGBITS
+    Content: )"
+           << makeBinRef(&Ext, ExtSectionLen);
+
+    Obj = yaml::yaml2ObjectFile(Storage, Buffer,
+                                [](const Twine &Err) { errs() << Err; });
+    return *Obj.get();
+  }
+};
+#pragma pack(pop)
+
+TEST(BTFParserTest, simpleCorrectInput) {
+  BTFParser BTF;
+  MockData1 Mock;
+  Error Err = BTF.parse(Mock.makeObj());
+  EXPECT_FALSE(Err);
+
+  EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, Foo)), "foo");
+  EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, Bar)), "bar");
+  EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, Line1)), "first line");
+  EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, Line2)), "second line");
+  EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, File1)), "a.c");
+  EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, File2)), "b.c");
+
+  // Invalid offset
+  EXPECT_EQ(BTF.findString(sizeof(MockData1::B::S)), StringRef());
+
+  const BTF::BPFLineInfo *I1 = BTF.findLineInfo({16, 1});
+  EXPECT_TRUE(I1);
+  EXPECT_EQ(I1->getLine(), 7u);
+  EXPECT_EQ(I1->getCol(), 1u);
+  EXPECT_EQ(BTF.findString(I1->FileNameOff), "a.c");
+  EXPECT_EQ(BTF.findString(I1->LineOff), "first line");
+
+  const BTF::BPFLineInfo *I2 = BTF.findLineInfo({32, 1});
+  EXPECT_TRUE(I2);
+  EXPECT_EQ(I2->getLine(), 14u);
+  EXPECT_EQ(I2->getCol(), 5u);
+  EXPECT_EQ(BTF.findString(I2->FileNameOff), "a.c");
+  EXPECT_EQ(BTF.findString(I2->LineOff), "second line");
+
+  const BTF::BPFLineInfo *I3 = BTF.findLineInfo({0, 2});
+  EXPECT_TRUE(I3);
+  EXPECT_EQ(I3->getLine(), 42u);
+  EXPECT_EQ(I3->getCol(), 4u);
+  EXPECT_EQ(BTF.findString(I3->FileNameOff), "b.c");
+  EXPECT_EQ(BTF.findString(I3->LineOff), "first line");
+
+  // No info for insn address
+  EXPECT_FALSE(BTF.findLineInfo({24, 1}));
+  EXPECT_FALSE(BTF.findLineInfo({8, 2}));
+  // No info for section number
+  EXPECT_FALSE(BTF.findLineInfo({16, 3}));
+}
+
+TEST(BTFParserTest, badSectionNameOffset) {
+  BTFParser BTF;
+  MockData1 Mock;
+  // "foo" is section #1, corrupting it's name offset will make impossible
+  // to match section name with section index when BTF is parsed.
+  Mock.Ext.Lines.Foo.Sec.SecNameOff = 100500;
+  Error Err = BTF.parse(Mock.makeObj());
+  EXPECT_FALSE(Err);
+  // "foo" line info should be corrupted
+  EXPECT_FALSE(BTF.findLineInfo({16, 1}));
+  // "bar" line info should be ok
+  EXPECT_TRUE(BTF.findLineInfo({0, 2}));
+}
+
+// Keep this as macro to preserve line number info
+#define EXPECT_PARSE_ERROR(Mock, Message)                                      \
+  do {                                                                         \
+    BTFParser BTF;                                                             \
+    EXPECT_THAT_ERROR(BTF.parse((Mock).makeObj()),                             \
+                      FailedWithMessage(testing::HasSubstr(Message)));         \
+  } while (false)
+
+TEST(BTFParserTest, badBTFMagic) {
+  MockData1 Mock;
+  Mock.BTF.Header.Magic = 42;
+  EXPECT_PARSE_ERROR(Mock, "invalid .BTF magic: 2a");
+}
+
+TEST(BTFParserTest, badBTFVersion) {
+  MockData1 Mock;
+  Mock.BTF.Header.Version = 42;
+  EXPECT_PARSE_ERROR(Mock, "unsupported .BTF version: 42");
+}
+
+TEST(BTFParserTest, badBTFHdrLen) {
+  MockData1 Mock;
+  Mock.BTF.Header.HdrLen = 5;
+  EXPECT_PARSE_ERROR(Mock, "unexpected .BTF header length: 5");
+}
+
+TEST(BTFParserTest, badBTFSectionLen) {
+  MockData1 Mock1, Mock2;
+
+  // 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
+  Mock2.BTFSectionLen = offsetof(BTF::Header, StrOff);
+  EXPECT_PARSE_ERROR(Mock2, BTFEndOfData);
+}
+
+TEST(BTFParserTest, badBTFExtMagic) {
+  MockData1 Mock;
+  Mock.Ext.Header.Magic = 42;
+  EXPECT_PARSE_ERROR(Mock, "invalid .BTF.ext magic: 2a");
+}
+
+TEST(BTFParserTest, badBTFExtVersion) {
+  MockData1 Mock;
+  Mock.Ext.Header.Version = 42;
+  EXPECT_PARSE_ERROR(Mock, "unsupported .BTF.ext version: 42");
+}
+
+TEST(BTFParserTest, badBTFExtHdrLen) {
+  MockData1 Mock1, Mock2;
+
+  Mock1.Ext.Header.HdrLen = 5;
+  EXPECT_PARSE_ERROR(Mock1, "unexpected .BTF.ext header length: 5");
+
+  Mock2.Ext.Header.HdrLen = sizeof(Mock2.Ext);
+  EXPECT_PARSE_ERROR(Mock2, BTFExtEndOfData);
+}
+
+TEST(BTFParserTest, badBTFExtSectionLen) {
+  MockData1 Mock1, Mock2, Mock3;
+
+  // Cut-off header before HdrLen
+  Mock1.ExtSectionLen = offsetof(BTF::ExtHeader, HdrLen);
+  EXPECT_PARSE_ERROR(Mock1, BTFExtEndOfData);
+
+  // 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
+  Mock3.ExtSectionLen = offsetof(MockData1::E, Lines) + 4;
+  EXPECT_PARSE_ERROR(Mock3, BTFExtEndOfData);
+}
+
+TEST(BTFParserTest, badBTFExtLineInfoRecSize) {
+  MockData1 Mock1, Mock2;
+
+  Mock1.Ext.Lines.LineRecSize = 2;
+  EXPECT_PARSE_ERROR(Mock1, "unexpected .BTF.ext line info record length: 2");
+
+  Mock2.Ext.Lines.LineRecSize = sizeof(Mock2.Ext.Lines.Foo.Lines[0]) + 1;
+  EXPECT_PARSE_ERROR(Mock2, BTFExtEndOfData);
+}
+
+TEST(BTFParserTest, badBTFExtLineSectionName) {
+  MockData1 Mock1;
+
+  Mock1.Ext.Lines.Foo.Sec.SecNameOff = offsetof(MockData1::B::S, Buz);
+  EXPECT_PARSE_ERROR(
+      Mock1, "can't find section 'buz' while parsing .BTF.ext line info");
+}
+
+TEST(BTFParserTest, missingSections) {
+  MockData1 Mock1, Mock2, Mock3;
+
+  Mock1.BTFSectionLen = -1;
+  EXPECT_PARSE_ERROR(Mock1, "can't find .BTF section");
+  EXPECT_FALSE(BTFParser::hasBTFSections(Mock1.makeObj()));
+
+  Mock2.ExtSectionLen = -1;
+  EXPECT_PARSE_ERROR(Mock2, "can't find .BTF.ext section");
+  EXPECT_FALSE(BTFParser::hasBTFSections(Mock2.makeObj()));
+
+  EXPECT_TRUE(BTFParser::hasBTFSections(Mock3.makeObj()));
+}
+
+// Check that BTFParser instance is reset when BTFParser::parse() is
+// called several times.
+TEST(BTFParserTest, parserReset) {
+  BTFParser BTF;
+  MockData1 Mock1, Mock2;
+
+  EXPECT_FALSE(BTF.parse(Mock1.makeObj()));
+  EXPECT_TRUE(BTF.findLineInfo({16, 1}));
+  EXPECT_TRUE(BTF.findLineInfo({0, 2}));
+
+  // Break the reference to "bar" section name, thus making
+  // information about "bar" line numbers unavailable.
+  Mock2.Ext.Lines.Bar.Sec.SecNameOff = 100500;
+
+  EXPECT_FALSE(BTF.parse(Mock2.makeObj()));
+  EXPECT_TRUE(BTF.findLineInfo({16, 1}));
+  // Make sure that "bar" no longer available (its index is 2).
+  EXPECT_FALSE(BTF.findLineInfo({0, 2}));
+}
+
+TEST(BTFParserTest, btfContext) {
+  MockData1 Mock;
+  BTFParser BTF;
+  std::unique_ptr<BTFContext> Ctx = BTFContext::create(Mock.makeObj());
+
+  DILineInfo I1 = Ctx->getLineInfoForAddress({16, 1});
+  EXPECT_EQ(I1.Line, 7u);
+  EXPECT_EQ(I1.Column, 1u);
+  EXPECT_EQ(I1.FileName, "a.c");
+  EXPECT_EQ(I1.LineSource, "first line");
+
+  DILineInfo I2 = Ctx->getLineInfoForAddress({24, 1});
+  EXPECT_EQ(I2.Line, 0u);
+  EXPECT_EQ(I2.Column, 0u);
+  EXPECT_EQ(I2.FileName, DILineInfo::BadString);
+  EXPECT_EQ(I2.LineSource, std::nullopt);
+}
+
+} // namespace

diff  --git a/llvm/unittests/DebugInfo/BTF/CMakeLists.txt b/llvm/unittests/DebugInfo/BTF/CMakeLists.txt
new file mode 100644
index 00000000000000..b425e46b9f0cb3
--- /dev/null
+++ b/llvm/unittests/DebugInfo/BTF/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(LLVM_LINK_COMPONENTS
+  ${LLVM_TARGETS_TO_BUILD}
+  DebugInfoBTF
+  ObjectYAML
+  )
+
+add_llvm_unittest(DebugInfoBTFTests
+  BTFParserTest.cpp
+  )
+
+target_link_libraries(DebugInfoBTFTests PRIVATE LLVMTestingSupport)
+
+set_property(TARGET DebugInfoBTFTests PROPERTY FOLDER "Tests/UnitTests/DebugInfoTests")

diff  --git a/llvm/unittests/DebugInfo/CMakeLists.txt b/llvm/unittests/DebugInfo/CMakeLists.txt
index 8e28a90b45beb5..afcfc113d76752 100644
--- a/llvm/unittests/DebugInfo/CMakeLists.txt
+++ b/llvm/unittests/DebugInfo/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_subdirectory(CodeView)
+add_subdirectory(BTF)
 add_subdirectory(DWARF)
 add_subdirectory(GSYM)
 add_subdirectory(LogicalView)


        


More information about the llvm-commits mailing list