diff --git a/llvm/include/llvm/DebugInfo/BTF/BTF.h b/llvm/include/llvm/DebugInfo/BTF/BTF.h --- a/llvm/include/llvm/DebugInfo/BTF/BTF.h +++ b/llvm/include/llvm/DebugInfo/BTF/BTF.h @@ -245,6 +245,8 @@ 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 --- /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 + getLocalsForAddress(object::SectionedAddress Address) override; + + static std::unique_ptr create( + const object::ObjectFile &Obj, + std::function 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 --- /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; + + // 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 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 --- a/llvm/include/llvm/DebugInfo/DIContext.h +++ b/llvm/include/llvm/DebugInfo/DIContext.h @@ -37,7 +37,11 @@ std::string FileName; std::string FunctionName; std::string StartFileName; + // Full source corresponding to `FileName` std::optional Source; + // Source code for this particular line + // (in case if `Source` is not available) + std::optional LineSource; uint32_t Line = 0; uint32_t Column = 0; uint32_t StartLine = 0; @@ -228,7 +232,7 @@ 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 --- /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 BTFContext::getLocalsForAddress(SectionedAddress Address) { + // BTF does not convey such information + return {}; +} + +std::unique_ptr +BTFContext::create(const ObjectFile &Obj, + std::function ErrorHandler) { + auto Ctx = std::make_unique(); + 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 --- /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 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(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 Sections; + +public: + ParseContext(const ObjectFile &Obj) : Obj(Obj) {} + + Expected makeExtractor(SectionRef Sec) { + Expected Contents = Sec.getContents(); + if (!Contents) + return Contents.takeError(); + return DataExtractor(Contents.get(), Obj.isLittleEndian(), + Obj.getBytesInAddress()); + } + + std::optional 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 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 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 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 BTF; + std::optional BTFExt; + for (SectionRef Sec : Obj.sections()) { + Expected 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 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 --- /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 --- a/llvm/lib/DebugInfo/CMakeLists.txt +++ b/llvm/lib/DebugInfo/CMakeLists.txt @@ -5,3 +5,4 @@ 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 --- a/llvm/lib/DebugInfo/Symbolize/CMakeLists.txt +++ b/llvm/lib/DebugInfo/Symbolize/CMakeLists.txt @@ -11,6 +11,7 @@ LINK_COMPONENTS DebugInfoDWARF DebugInfoPDB + DebugInfoBTF Object Support Demangle diff --git a/llvm/lib/DebugInfo/Symbolize/Symbolize.cpp b/llvm/lib/DebugInfo/Symbolize/Symbolize.cpp --- 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 @@ 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 LLVMSymbolizer::getOrCreateModuleInfo(const ObjectFile &Obj) { StringRef ObjName = Obj.getFileName(); @@ -622,7 +630,11 @@ if (I != Modules.end()) return I->second.get(); - std::unique_ptr Context = DWARFContext::create(Obj); + std::unique_ptr 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 --- /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 --- /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,]] : +; 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,]] : +; 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,]] : +; 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,]] : +; 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.h b/llvm/tools/llvm-objdump/SourcePrinter.h --- a/llvm/tools/llvm-objdump/SourcePrinter.h +++ b/llvm/tools/llvm-objdump/SourcePrinter.h @@ -151,6 +151,10 @@ 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/tools/llvm-objdump/SourcePrinter.cpp b/llvm/tools/llvm-objdump/SourcePrinter.cpp --- a/llvm/tools/llvm-objdump/SourcePrinter.cpp +++ b/llvm/tools/llvm-objdump/SourcePrinter.cpp @@ -448,6 +448,34 @@ } } +// 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 @@ 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/unittests/DebugInfo/BTF/BTFParserTest.cpp b/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp new file mode 100644 --- /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 +static yaml::BinaryRef makeBinRef(const T *Ptr, size_t Size = sizeof(T)) { + return yaml::BinaryRef(ArrayRef((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 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 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 --- /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 --- 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)