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() { return LineCol >> 10; } + uint32_t getCol() { 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,56 @@ +//===- BTFContext.h -------------------------------------------------------===// +// +// 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" +#include + +namespace llvm { + +class BTFContext : public DIContext { + BTFParser BTF; + + BTFContext(); + +public: + void dump(raw_ostream &OS, DIDumpOptions DumpOpts) override; + + 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.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 .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/DebugInfo/BTF/BTF.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/DataExtractor.h" +#include + +namespace llvm { +using object::ObjectFile; +using object::SectionedAddress; +using object::SectionRef; + +using BTFLinesVector = std::vector; + +class BTFParser { + // 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. + std::map> 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); + + // 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. + BTF::BPFLineInfo *findLineInfo(SectionedAddress Address); + + // 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); + + // Returns 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,81 @@ +//===- 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" +#include "llvm/ADT/StringRef.h" +#include "llvm/DebugInfo/BTF/BTFParser.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/Debug.h" +#include + +#define DEBUG_TYPE "debug-info-btf-context" + +using namespace llvm; +using object::ObjectFile; +using object::SectionedAddress; + +BTFContext::BTFContext() : DIContext(CK_BTF) {} + +void BTFContext::dump(raw_ostream &OS, DIDumpOptions DumpOpts) { + // This function is called from objdump when --dwarf=? option is set. + // BTF is no DWARF, so ignore this operation for now. +} + +DILineInfo BTFContext::getLineInfoForAddress(SectionedAddress Address, + DILineInfoSpecifier Specifier) { + auto *LineInfo = BTF.findLineInfo(Address); + if (!LineInfo) + return {}; + + DILineInfo 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 DILineInfo(); +} + +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 DILineInfoTable(); +} + +DIInliningInfo +BTFContext::getInliningInfoForAddress(SectionedAddress Address, + DILineInfoSpecifier Specifier) { + // BTF does not convey such information + return DIInliningInfo(); +} + +std::vector BTFContext::getLocalsForAddress(SectionedAddress Address) { + // BTF does not convey such information + return std::vector(); +} + +std::unique_ptr +BTFContext::create(const ObjectFile &Obj, + std::function ErrorHandler) { + auto Ctx = std::unique_ptr(new BTFContext()); + if (auto 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,303 @@ +//===- 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/ADT/StringRef.h" +#include "llvm/DebugInfo/BTF/BTF.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/DataExtractor.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include +#include +#include +#include +#include + +#define DEBUG_TYPE "debug-info-btf-parser" +#define BTF_SECTION_NAME ".BTF" +#define BTF_EXT_SECTION_NAME ".BTF.ext" + +using namespace llvm; +using object::ObjectFile; +using object::SectionedAddress; +using object::SectionRef; + +// 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; +// ... +// } +// +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); + } +}; + +// The section name to SectionRef mapping is only needed while +// ObjectFile is parsed, it is not needed for queries against +// BTFParser. ParseContext wraps this information and is used by +// BTFParser::parse* auxiliary functions. +struct BTFParser::ParseContext { + const ObjectFile &Obj; + std::map Sections; + +public: + ParseContext(const ObjectFile &Obj) : Obj(Obj) {} + + Error init() { + for (SectionRef Sec : Obj.sections()) { + Expected MaybeName = Sec.getName(); + if (!MaybeName) + return MaybeName.takeError(); + + Sections[*MaybeName] = Sec; + } + + return Error::success(); + } + + Expected makeExtractor(SectionRef Sec) { + auto Contents = Sec.getContents(); + if (!Contents) + return Contents.takeError(); + + return DataExtractor(Contents.get(), Obj.isLittleEndian(), + Obj.getBytesInAddress()); + } + + std::optional findSection(StringRef Name) { + auto It = Sections.find(Name); + if (It != Sections.end()) + return It->second; + return std::nullopt; + } +}; + +Error BTFParser::parseBTF(ParseContext &Ctx, SectionRef BTF) { + auto MaybeExtractor = Ctx.makeExtractor(BTF); + if (!MaybeExtractor) + return MaybeExtractor.takeError(); + + auto Extractor = MaybeExtractor.get(); + DataExtractor::Cursor C = DataExtractor::Cursor(0); + + auto Magic = Extractor.getU16(C); + if (!C) + return Err(".BTF", C); + if (Magic != BTF::MAGIC) + return Err("Invalid .BTF magic: ").write_hex(Magic); + + Extractor.getU8(C); // version + Extractor.getU8(C); // flags + auto HdrLen = Extractor.getU32(C); + if (!C) + return Err(".BTF", C); + if (HdrLen < 8) + return Err("Unexpected .BTF header length: ") << HdrLen; + + Extractor.getU32(C); // type_off + Extractor.getU32(C); // type_len + auto StrOff = Extractor.getU32(C); + auto StrLen = Extractor.getU32(C); + auto StrStart = HdrLen + StrOff; + auto 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().slice(StrStart, StrEnd); + + return Error::success(); +} + +Error BTFParser::parseBTFExt(ParseContext &Ctx, SectionRef BTFExt) { + auto MaybeExtractor = Ctx.makeExtractor(BTFExt); + if (!MaybeExtractor) + return MaybeExtractor.takeError(); + + auto Extractor = MaybeExtractor.get(); + DataExtractor::Cursor C = DataExtractor::Cursor(0); + + auto Magic = Extractor.getU16(C); + if (!C) + return Err(".BTF.ext", C); + if (Magic != BTF::MAGIC) + return Err("Invalid .BTF.ext magic: ").write_hex(Magic); + + Extractor.getU8(C); // version + Extractor.getU8(C); // flags + auto HdrLen = Extractor.getU32(C); + if (!C) + return Err(".BTF.ext", C); + if (HdrLen < 8) + return Err("Unexpected .BTF.ext header length: ") << HdrLen; + + Extractor.getU32(C); // func_info_off + Extractor.getU32(C); // func_info_len + auto LineInfoOff = Extractor.getU32(C); + auto LineInfoLen = Extractor.getU32(C); + + if (!C) + return Err(".BTF.ext", C); + + auto LineInfoStart = HdrLen + LineInfoOff; + auto LineInfoEnd = LineInfoStart + LineInfoLen; + + if (auto 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); + auto 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) { + std::unique_ptr Lines(new BTFLinesVector()); + + auto SecNameOff = Extractor.getU32(C); + auto NumInfo = Extractor.getU32(C); + + auto SecName = findString(SecNameOff); + auto Sec = Ctx.findSection(SecName); + + for (uint32_t I = 0; C && I < NumInfo; ++I) { + auto RecStart = C.tell(); + + auto InsnOff = Extractor.getU32(C); + auto FileNameOff = Extractor.getU32(C); + auto LineOff = Extractor.getU32(C); + auto LineCol = Extractor.getU32(C); + + Lines->push_back({InsnOff, FileNameOff, LineOff, LineCol}); + + C.seek(RecStart + RecSize); + } + + std::sort(Lines->begin(), Lines->end(), [](const auto &L, const auto &R) { + return L.InsnOffset < R.InsnOffset; + }); + + if (Sec) + SectionLines[Sec->getIndex()] = std::move(Lines); + } + + if (!C) + return Err(".BTF.ext", C); + + return Error::success(); +} + +Error BTFParser::parse(const ObjectFile &Obj) { + StringsTable = StringRef(); + SectionLines.clear(); + + ParseContext Ctx(Obj); + + if (auto E = Ctx.init()) + return E; + + auto BTF = Ctx.findSection(BTF_SECTION_NAME); + auto BTFExt = Ctx.findSection(BTF_EXT_SECTION_NAME); + + if (!BTF) + return Err("Can't find .BTF section"); + + if (!BTFExt) + return Err("Can't find .BTF.ext section"); + + if (auto E = parseBTF(Ctx, *BTF)) + return E; + + if (auto E = parseBTFExt(Ctx, *BTFExt)) + return E; + + return Error::success(); +} + +bool BTFParser::hasBTFSections(const ObjectFile &Obj) { + bool HasBTF = false; + bool HasBTFExt = false; + for (auto Sec : Obj.sections()) { + if (auto Name = Sec.getName()) { + HasBTF |= *Name == BTF_SECTION_NAME; + HasBTFExt |= *Name == BTF_EXT_SECTION_NAME; + if (HasBTF && HasBTFExt) + return true; + } + } + return false; +} + +StringRef BTFParser::findString(uint32_t Offset) { + return StringsTable.slice(Offset, StringsTable.find(0, Offset)); +} + +BTF::BPFLineInfo *BTFParser::findLineInfo(SectionedAddress Address) { + auto MaybeSecInfo = SectionLines.find(Address.SectionIndex); + if (MaybeSecInfo == SectionLines.end()) + return nullptr; + + auto &SecInfo = MaybeSecInfo->second; + auto LineInfo = std::lower_bound( + SecInfo->begin(), SecInfo->end(), Address.Address, + [](auto &Line, uint64_t Addr) { return Line.InsnOffset < Addr; }); + 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,19 @@ 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) { + auto Arch = Obj.getArch(); + if (Arch != Triple::ArchType::bpfel && Arch != Triple::ArchType::bpfeb) + return false; + + if (Obj.hasDebugInfo()) + return false; + + return BTFParser::hasBTFSections(Obj); +} + Expected LLVMSymbolizer::getOrCreateModuleInfo(const ObjectFile &Obj) { StringRef ObjName = Obj.getFileName(); @@ -622,7 +636,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,16 @@ +extern int consume(int); + +void foo(void) { + consume(1); + consume(2); +} + +__attribute__((section("a"))) +void bar(void) { + consume(3); +} + +__attribute__((section("b"))) +void buz(void) { + consume(4); +} 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,118 @@ +; 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): +; +; RUN: llc --mtriple bpfel %t.ll --filetype=obj -o %t +; RUN: llvm-strip --strip-debug %t +; RUN: llvm-objdump --section-headers %t | FileCheck --check-prefix=SECTIONS %s +; RUN: llvm-objdump --no-addresses --no-show-raw-insn -Sd %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 --section-headers %t | FileCheck --check-prefix=SECTIONS %s +; RUN: llvm-objdump -Sd %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. + +; Make sure that BTF is present and DWARF is stripped: +; +; SECTIONS: .BTF +; SECTIONS: .BTF.ext +; SECTIONS-NOT: .debug_line +; SECTIONS-NOT: .debug_line_str +; +; Check source code in disassembly: +; +; CHECK: Disassembly of section .text: +; CHECK-EMPTY: +; CHECK-NEXT: : +; CHECK-NEXT: ; consume(1); +; CHECK-NEXT: r1 = 0x1 +; CHECK-NEXT: call -0x1 +; CHECK-NEXT: ; consume(2); +; CHECK-NEXT: r1 = 0x2 +; CHECK-NEXT: call -0x1 +; CHECK-NEXT: ; } +; CHECK-NEXT: exit +; CHECK-EMPTY: +; CHECK-NEXT: Disassembly of section a: +; CHECK-EMPTY: +; CHECK-NEXT: : +; CHECK-NEXT: ; consume(3); +; CHECK-NEXT: r1 = 0x3 +; CHECK-NEXT: call -0x1 +; CHECK-NEXT: ; } +; CHECK-NEXT: exit +; CHECK-EMPTY: +; CHECK-NEXT: Disassembly of section b: +; CHECK-EMPTY: +; CHECK-NEXT: : +; CHECK-NEXT: ; consume(4); +; CHECK-NEXT: r1 = 0x4 +; CHECK-NEXT: call -0x1 +; CHECK-NEXT: ; } +; CHECK-NEXT: exit + +; Function Attrs: noinline nounwind optnone +define dso_local void @foo() #0 !dbg !7 { + %1 = call i32 @consume(i32 noundef 1), !dbg !12 + %2 = call i32 @consume(i32 noundef 2), !dbg !13 + ret void, !dbg !14 +} + +declare dso_local i32 @consume(i32 noundef) #1 + +; Function Attrs: noinline nounwind optnone +define dso_local void @bar() #0 section "a" !dbg !15 { + %1 = call i32 @consume(i32 noundef 3), !dbg !16 + ret void, !dbg !17 +} + +; Function Attrs: noinline nounwind optnone +define dso_local void @buz() #0 section "b" !dbg !18 { + %1 = call i32 @consume(i32 noundef 4), !dbg !19 + ret void, !dbg !20 +} + +attributes #0 = { noinline nounwind optnone "frame-pointer"="all" "min-legal-vector-width"="0" "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_C99, file: !1, producer: "Ubuntu clang version 14.0.0-1ubuntu1", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "/home/eddy/work/llvm-project/llvm/test/tools/llvm-objdump/BPF/Inputs/test.c", directory: "/home/eddy", checksumkind: CSK_MD5, checksum: "73d9dbf2a4ebbbb25f026b623953473b") +!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 = !{!"Ubuntu clang version 14.0.0-1ubuntu1"} +!7 = distinct !DISubprogram(name: "foo", scope: !8, file: !8, line: 3, type: !9, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !11) +!8 = !DIFile(filename: "test.c", directory: "SRC_COMPDIR", checksumkind: CSK_MD5, checksum: "73d9dbf2a4ebbbb25f026b623953473b") +!9 = !DISubroutineType(types: !10) +!10 = !{null} +!11 = !{} +!12 = !DILocation(line: 4, column: 3, scope: !7) +!13 = !DILocation(line: 5, column: 3, scope: !7) +!14 = !DILocation(line: 6, column: 1, scope: !7) +!15 = distinct !DISubprogram(name: "bar", scope: !8, file: !8, line: 9, type: !9, scopeLine: 9, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !11) +!16 = !DILocation(line: 10, column: 3, scope: !15) +!17 = !DILocation(line: 11, column: 1, scope: !15) +!18 = distinct !DISubprogram(name: "buz", scope: !8, file: !8, line: 14, type: !9, scopeLine: 14, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !11) +!19 = !DILocation(line: 15, column: 3, scope: !18) +!20 = !DILocation(line: 16, column: 1, scope: !18) 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,9 @@ StringRef ObjectFilename, StringRef Delimiter, LiveVariablePrinter &LVP); + std::optional 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 @@ -452,6 +452,34 @@ } } +// Get the source line text for LineInfo: +// - use LineCache if LineInfo::Source is available; +// - use LineInfo::LineSource otherwise. +std::optional SourcePrinter::getLine(const DILineInfo &LineInfo, + StringRef ObjectFilename) { + if (LineInfo.LineSource) + return LineInfo.LineSource; + + if (SourceCache.find(LineInfo.FileName) == SourceCache.end()) + if (!cacheSource(LineInfo)) + return std::nullopt; + + auto LineBuffer = LineCache.find(LineInfo.FileName); + if (LineBuffer == LineCache.end()) + return std::nullopt; + + 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 std::nullopt; + } + + // 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, @@ -461,21 +489,8 @@ 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]; + if (auto Line = getLine(LineInfo, ObjectFilename)) { + 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,350 @@ +//===-- 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/BTFParser.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/DebugInfo/BTF/BTF.h" +#include "llvm/DebugInfo/BTF/BTFContext.h" +#include "llvm/DebugInfo/DIContext.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/ObjectYAML/YAML.h" +#include "llvm/ObjectYAML/yaml2obj.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Testing/Support/Error.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include + +#define __packed __attribute__((packed)) +#define LC(Line, Col) ((Line << 10u) | Col) +#define BTF_END_OF_DATA \ + "Error while reading .BTF section: unexpected end of data" +#define BTF_EXT_END_OF_DATA \ + "Error while reading .BTF.ext section: unexpected end of data" + +namespace { + +using namespace llvm; +using namespace object; + +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)); +} + +// 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 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); + + std::unique_ptr makeObj() { + SmallString<0> Storage; + 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); + + return yaml::yaml2ObjectFile(Storage, Buffer, + [](const Twine &Err) { errs() << Err; }); + } +}; + +#pragma pack(pop) + +TEST(BTFParserTest, simpleCorrectInput) { + BTFParser BTF; + MockData1 Mock; + auto Obj = Mock.makeObj(); + auto Err = BTF.parse(*Obj); + 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()); + + auto *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"); + + auto *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"); + + auto *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; + auto Obj = Mock.makeObj(); + auto Err = BTF.parse(*Obj); + 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})); +} + +static void parseError(MockData1 &Mock, const char *Message) { + BTFParser BTF; + auto Obj = Mock.makeObj(); + EXPECT_THAT_ERROR(BTF.parse(*Obj), + FailedWithMessage(testing::HasSubstr(Message))); +} + +TEST(BTFParserTest, badBTFMagic) { + MockData1 Mock; + Mock.BTF.Header.Magic = 42; + parseError(Mock, "Invalid .BTF magic: 2a"); +} + +TEST(BTFParserTest, badBTFHdrLen) { + MockData1 Mock; + Mock.BTF.Header.HdrLen = 5; + parseError(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; + parseError(Mock1, "Invalid .BTF section size"); + + // Cut-off header + Mock2.BTFSectionLen = offsetof(BTF::Header, StrOff); + parseError(Mock2, BTF_END_OF_DATA); +} + +TEST(BTFParserTest, badBTFExtMagic) { + MockData1 Mock; + Mock.Ext.Header.Magic = 42; + parseError(Mock, "Invalid .BTF.ext magic: 2a"); +} + +TEST(BTFParserTest, badBTFExtHdrLen) { + MockData1 Mock1, Mock2; + + Mock1.Ext.Header.HdrLen = 5; + parseError(Mock1, "Unexpected .BTF.ext header length: 5"); + + Mock2.Ext.Header.HdrLen = sizeof(Mock2.Ext); + parseError(Mock2, BTF_EXT_END_OF_DATA); +} + +TEST(BTFParserTest, badBTFExtSectionLen) { + MockData1 Mock1, Mock2, Mock3; + + // Cut-off header before HdrLen + Mock1.ExtSectionLen = offsetof(BTF::ExtHeader, HdrLen); + parseError(Mock1, BTF_EXT_END_OF_DATA); + + // Cut-off header before LineInfoLen + Mock2.ExtSectionLen = offsetof(BTF::ExtHeader, LineInfoLen); + parseError(Mock2, BTF_EXT_END_OF_DATA); + + // Cut-off line-info section somewhere in the middle + Mock3.ExtSectionLen = offsetof(MockData1::E, Lines) + 4; + parseError(Mock3, BTF_EXT_END_OF_DATA); +} + +TEST(BTFParserTest, badBTFExtLineInfoRecSize) { + MockData1 Mock1, Mock2; + + Mock1.Ext.Lines.LineRecSize = 2; + parseError(Mock1, "Unexpected .BTF.ext line info record length: 2"); + + Mock2.Ext.Lines.LineRecSize = sizeof(Mock2.Ext.Lines.Foo.Lines[0]) + 1; + parseError(Mock2, BTF_EXT_END_OF_DATA); +} + +TEST(BTFParserTest, missingSections) { + MockData1 Mock1, Mock2, Mock3; + + Mock1.BTFSectionLen = -1; + parseError(Mock1, "Can't find .BTF section"); + auto Obj1 = Mock1.makeObj(); + EXPECT_FALSE(BTFParser::hasBTFSections(*Obj1)); + + Mock2.ExtSectionLen = -1; + parseError(Mock2, "Can't find .BTF.ext section"); + auto Obj2 = Mock2.makeObj(); + EXPECT_FALSE(BTFParser::hasBTFSections(*Obj2)); + + auto Obj3 = Mock3.makeObj(); + EXPECT_TRUE(BTFParser::hasBTFSections(*Obj3)); +} + +// Check that BTFParser instance is reset when BTFParser::parse() is +// called several times. +TEST(BTFParserTest, parserReset) { + BTFParser BTF; + MockData1 Mock1, Mock2; + + auto Obj1 = Mock1.makeObj(); + EXPECT_FALSE(BTF.parse(*Obj1)); + 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; + + auto Obj2 = Mock2.makeObj(); + EXPECT_FALSE(BTF.parse(*Obj2)); + 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; + auto Obj = Mock.makeObj(); + auto Ctx = BTFContext::create(*Obj); + + 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)