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 @@ -48,6 +48,7 @@ #ifndef LLVM_LIB_TARGET_BPF_BTF_H #define LLVM_LIB_TARGET_BPF_BTF_H +#include "llvm/ADT/ArrayRef.h" #include namespace llvm { @@ -97,6 +98,9 @@ #include "BTF.def" }; +// Constants for CommonType::Info field +enum : uint32_t { FWD_UNION_FLAG = (1u << 31) }; + /// The BTF common type definition. Different kinds may have /// additional information after this structure data. struct CommonType { @@ -106,8 +110,8 @@ /// "Info" bits arrangement: /// Bits 0-15: vlen (e.g. # of struct's members) /// Bits 16-23: unused - /// Bits 24-27: kind (e.g. int, ptr, array...etc) - /// Bits 28-30: unused + /// Bits 24-28: kind (e.g. int, ptr, array...etc) + /// Bits 29-30: unused /// Bit 31: kind_flag, currently used by /// struct, union and fwd uint32_t Info; @@ -122,6 +126,9 @@ uint32_t Size; uint32_t Type; }; + + uint32_t getKind() const { return Info >> 24 & 0x1f; } + uint32_t getVlen() const { return Info & 0xff; } }; // For some specific BTF_KIND, "struct CommonType" is immediately @@ -269,6 +276,110 @@ uint32_t NumFieldReloc; ///< Number of offset reloc's in this section }; +enum PatchableRelocKind : uint32_t { +#define HANDLE_RELO_KIND(Val, Name) Name = Val, +#include "CoreRelo.def" +#undef HANDLE_RELO_KIND + MAX_FIELD_RELOC_KIND, +}; + +// Define a number of sub-types for CommonType, each with: +// - An accessor for a relevant "tail" information (data fields that +// follow the CommonType record in binary format). +// - A classof() definition based on CommonType::getKind() value to +// allow use with dyn_cast<>() function. + +// For CommonType sub-types that are followed by a single entry of +// some type in the binary format. +#define DEFINE_TAIL(Type, Accessor) \ + const Type &Accessor() const { \ + return *((const Type *)((const uint8_t *)this + sizeof(*this))); \ + } + +// For CommonType sub-types that are followed by CommonType::getVlen() +// number of entries of some type in the binary format. +#define DEFINE_TAIL_ARR(Type, Accessor) \ + ArrayRef Accessor() const { \ + return ArrayRef( \ + (const Type *)((const uint8_t *)this + sizeof(*this)), \ + this->getVlen()); \ + } + +struct IntType : CommonType { + DEFINE_TAIL(uint32_t, getBitsInfo) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_INT; + } +}; + +struct VarType : CommonType { + DEFINE_TAIL(uint32_t, getLinkage) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_VAR; + } +}; + +struct DeclTagType : CommonType { + DEFINE_TAIL(uint32_t, getComponentIdx) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_DECL_TAG; + } +}; + +struct ArrayType : CommonType { + DEFINE_TAIL(BTFArray, getArray) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_ARRAY; + } +}; + +struct StructType : CommonType { + DEFINE_TAIL_ARR(BTFMember, members) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_STRUCT || V->getKind() == BTF_KIND_UNION; + } +}; + +struct EnumType : CommonType { + DEFINE_TAIL_ARR(BTFEnum, values) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_ENUM; + } +}; + +struct Enum64Type : CommonType { + DEFINE_TAIL_ARR(BTFEnum64, values) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_ENUM64; + } +}; + +struct FuncProtoType : CommonType { + DEFINE_TAIL_ARR(BTFParam, params) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_FUNC_PROTO; + } +}; + +struct DataSecType : CommonType { + DEFINE_TAIL_ARR(BTFDataSec, vars) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_DATASEC; + } +}; + +#undef DEFINE_TAIL +#undef DEFINE_TAIL_ARR + } // End namespace BTF. } // End namespace llvm. diff --git a/llvm/include/llvm/DebugInfo/BTF/BTFParser.h b/llvm/include/llvm/DebugInfo/BTF/BTFParser.h --- a/llvm/include/llvm/DebugInfo/BTF/BTFParser.h +++ b/llvm/include/llvm/DebugInfo/BTF/BTFParser.h @@ -10,7 +10,9 @@ // BPF backend and provides introspection for the stored information. // Currently the following information is accessible: // - string table; -// - instruction offset to line information mapping. +// - instruction offset to line information mapping; +// - types table; +// - CO-RE relocations table. // // See llvm/DebugInfo/BTF/BTF.h for some details about binary format // and links to Linux Kernel documentation. @@ -20,10 +22,14 @@ #ifndef LLVM_DEBUGINFO_BTF_BTFPARSER_H #define LLVM_DEBUGINFO_BTF_BTFPARSER_H +#include "llvm/ADT/ArrayRef.h" #include "llvm/DebugInfo/BTF/BTF.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Support/DataExtractor.h" +#include "llvm/Support/Error.h" +#include #include +#include namespace llvm { using object::ObjectFile; @@ -31,6 +37,7 @@ using object::SectionRef; using BTFLinesVector = std::vector; +using BTFRelocVector = std::vector; class BTFParser { // In BTF strings are stored as a continuous memory region with @@ -39,15 +46,34 @@ // The `StringsTable` points to this region in the parsed ObjectFile. StringRef StringsTable; + // A copy of types table from the object file but using native byte + // order. Should not be too big in practice, e.g. for ~250Mb vmlinux + // image it is ~4Mb. + OwningArrayRef TypesBuffer; + // Maps ELF section number to instruction line number information. // Each BTFLinesVector is sorted by `InsnOffset` to allow fast lookups. std::map> SectionLines; + // Maps ELF section number to CO-RE relocation information. + // Each BTFRelocVector is sorted by `InsnOffset` to allow fast lookups. + std::map> SectionRelocs; + + // Vector of pointers to all known types, index in this vector + // equals to logical type BTF id. + // Pointers point to memory owned by `TypesBuffer` + // (except pointer at index 0, which is statically allocated). + std::vector Types; + struct ParseContext; Error parseBTF(ParseContext &Ctx, SectionRef BTF); Error parseBTFExt(ParseContext &Ctx, SectionRef BTFExt); Error parseLineInfo(ParseContext &Ctx, DataExtractor &Extractor, uint64_t LineInfoStart, uint64_t LineInfoEnd); + Error parseRelocInfo(ParseContext &Ctx, DataExtractor &Extractor, + uint64_t RelocInfoStart, uint64_t RelocInfoEnd); + Error parseTypesInfo(ParseContext &Ctx, uint64_t TypesInfoStart, + StringRef RawData); public: // Looks-up a string in the .BTF section's string table. @@ -61,6 +87,33 @@ // owned by this class. BTF::BPFLineInfo *findLineInfo(SectionedAddress Address); + // Search for CO-RE relocation information for a specific address. + // Return nullptr if no information found. + // If information is present, return a pointer to object + // owned by this class. + BTF::BPFFieldReloc *findFieldReloc(SectionedAddress Address); + + // Return a human readable representation of the CO-RE relocation + // record, this is for display purpose only. + // See implementation for details. + std::string symbolize(BTF::BPFFieldReloc *Reloc); + + // Lookup BTF type definition with a specific index. + // Return nullptr if no information found. + // If information is present, return a pointer to object + // owned by this class. + const BTF::CommonType *findType(uint32_t Id); + + // Return total number of known BTF types. + size_t typesCount() { return Types.size(); } + + // Allow to selectively load BTF information + struct ParseOptions { + bool LoadLines; + bool LoadTypes; + bool LoadRelocs; + }; + // Fills instance of BTFParser with information stored in .BTF and // .BTF.ext sections of the `Obj`. If this instance was already // filled, old data is discarded. @@ -70,7 +123,8 @@ // - state of the BTFParser might be incomplete but is not invalid, // queries might be run against it, but some (or all) information // might be unavailable; - Error parse(const ObjectFile &Obj); + Error parse(const ObjectFile &Obj, + const ParseOptions &Opts = {true, true, true}); // Returns true if `Obj` has .BTF and .BTF.ext sections static bool hasBTFSections(const ObjectFile &Obj); diff --git a/llvm/include/llvm/DebugInfo/BTF/CoreRelo.def b/llvm/include/llvm/DebugInfo/BTF/CoreRelo.def new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/DebugInfo/BTF/CoreRelo.def @@ -0,0 +1,13 @@ +HANDLE_RELO_KIND(0, FIELD_BYTE_OFFSET) +HANDLE_RELO_KIND(1, FIELD_BYTE_SIZE) +HANDLE_RELO_KIND(2, FIELD_EXISTENCE) +HANDLE_RELO_KIND(3, FIELD_SIGNEDNESS) +HANDLE_RELO_KIND(4, FIELD_LSHIFT_U64) +HANDLE_RELO_KIND(5, FIELD_RSHIFT_U64) +HANDLE_RELO_KIND(6, BTF_TYPE_ID_LOCAL) +HANDLE_RELO_KIND(7, BTF_TYPE_ID_REMOTE) +HANDLE_RELO_KIND(8, TYPE_EXISTENCE) +HANDLE_RELO_KIND(9, TYPE_SIZE) +HANDLE_RELO_KIND(10, ENUM_VALUE_EXISTENCE) +HANDLE_RELO_KIND(11, ENUM_VALUE) +HANDLE_RELO_KIND(12, TYPE_MATCH) diff --git a/llvm/lib/DebugInfo/BTF/BTFContext.cpp b/llvm/lib/DebugInfo/BTF/BTFContext.cpp --- a/llvm/lib/DebugInfo/BTF/BTFContext.cpp +++ b/llvm/lib/DebugInfo/BTF/BTFContext.cpp @@ -75,7 +75,9 @@ BTFContext::create(const ObjectFile &Obj, std::function ErrorHandler) { auto Ctx = std::unique_ptr(new BTFContext()); - if (auto E = Ctx->BTF.parse(Obj)) + BTFParser::ParseOptions Opts = {}; + Opts.LoadLines = true; + if (auto E = Ctx->BTF.parse(Obj, Opts)) ErrorHandler(std::move(E)); return Ctx; } diff --git a/llvm/lib/DebugInfo/BTF/BTFParser.cpp b/llvm/lib/DebugInfo/BTF/BTFParser.cpp --- a/llvm/lib/DebugInfo/BTF/BTFParser.cpp +++ b/llvm/lib/DebugInfo/BTF/BTFParser.cpp @@ -12,18 +12,24 @@ //===----------------------------------------------------------------------===// #include "llvm/DebugInfo/BTF/BTFParser.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.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/Endian.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include #include +#include #include #include #include +#include +#include #define DEBUG_TYPE "debug-info-btf-parser" #define BTF_SECTION_NAME ".BTF" @@ -83,10 +89,12 @@ // BTFParser::parse* auxiliary functions. struct BTFParser::ParseContext { const ObjectFile &Obj; + const ParseOptions &Opts; std::map Sections; public: - ParseContext(const ObjectFile &Obj) : Obj(Obj) {} + ParseContext(const ObjectFile &Obj, const ParseOptions &Opts) + : Obj(Obj), Opts(Opts) {} Error init() { for (SectionRef Sec : Obj.sections()) { @@ -139,22 +147,138 @@ if (HdrLen < 8) return Err("Unexpected .BTF header length: ") << HdrLen; - Extractor.getU32(C); // type_off - Extractor.getU32(C); // type_len + auto TypeOff = Extractor.getU32(C); + auto TypeLen = Extractor.getU32(C); auto StrOff = Extractor.getU32(C); auto StrLen = Extractor.getU32(C); auto StrStart = HdrLen + StrOff; auto StrEnd = StrStart + StrLen; + auto TypesInfoStart = HdrLen + TypeOff; + auto TypesInfoEnd = TypesInfoStart + TypeLen; + auto BytesExpected = std::max(StrEnd, TypesInfoEnd); if (!C) return Err(".BTF", C); - if (Extractor.getData().size() < StrEnd) + if (Extractor.getData().size() < BytesExpected) return Err("Invalid .BTF section size, expecting at-least ") - << StrEnd << " bytes"; + << BytesExpected << " bytes"; StringsTable = Extractor.getData().slice(StrStart, StrEnd); + if (TypeLen > 0 && Ctx.Opts.LoadTypes) { + StringRef RawData = Extractor.getData().slice(TypesInfoStart, TypesInfoEnd); + if (auto E = parseTypesInfo(Ctx, TypesInfoStart, RawData)) + return E; + } + + return Error::success(); +} + +// These classes can be used for direct memory mappings, +// add standard layout assertions just in case. +static_assert(std::is_standard_layout()); +static_assert(std::is_standard_layout()); +static_assert(std::is_standard_layout()); +static_assert(std::is_standard_layout()); +static_assert(std::is_standard_layout()); +static_assert(std::is_standard_layout()); +static_assert(std::is_standard_layout()); +static_assert(std::is_standard_layout()); +static_assert(std::is_standard_layout()); +static_assert(std::is_standard_layout()); + +template static size_t byteSize(ArrayRef A) { + return A.size() * sizeof(T); +} + +// Compute record size for each BTF::CommonType sub-type +// (including entries in the tail position). +static size_t byteSize(BTF::CommonType *Type) { + size_t Size = sizeof(BTF::CommonType); + + if (auto *T = dyn_cast(Type)) + Size += sizeof(T->getBitsInfo()); + else if (auto *T = dyn_cast(Type)) + Size += sizeof(T->getArray()); + else if (auto *T = dyn_cast(Type)) + Size += sizeof(T->getLinkage()); + else if (auto *T = dyn_cast(Type)) + Size += sizeof(T->getComponentIdx()); + else if (auto *T = dyn_cast(Type)) + Size += byteSize(T->members()); + else if (auto *T = dyn_cast(Type)) + Size += byteSize(T->values()); + else if (auto *T = dyn_cast(Type)) + Size += byteSize(T->values()); + else if (auto *T = dyn_cast(Type)) + Size += byteSize(T->params()); + else if (auto *T = dyn_cast(Type)) + Size += byteSize(T->vars()); + + return Size; +} + +// Guard value for voids, simplifies code a bit, but NameOff is not +// actually valid. +const static BTF::CommonType VOID_TYPE_INST = { + 0, BTF::BTF_KIND_UNKN << 24, {0}}; + +// Type information "parsing" is very primitive: +// - The `RawData` is copied to a buffer owned by `BTFParser` instance. +// - The buffer is treated as an array of `uint32_t` values, each value +// is swapped to use native endianness. This is possible, because +// according to BTF spec all buffer elements are structures comprised +// of `uint32_t` fields. +// - `BTFParser::Types` vector is filled with pointers to buffer +// elements, using `byteSize()` function to slice the buffer at type +// record boundaries. +// - If at some point a type definition with incorrect size (logical size +// exceeding buffer boundaries) is reached it is not added to the +// `BTFParser::Types` vector and the process stops. +Error BTFParser::parseTypesInfo(ParseContext &Ctx, uint64_t TypesInfoStart, + StringRef RawData) { + using support::big; + using support::endianness; + using support::little; + using support::endian::byte_swap; + using support::endian::system_endianness; + + TypesBuffer = OwningArrayRef(RawData.size()); + std::memcpy(TypesBuffer.data(), RawData.data(), RawData.size()); + + endianness Endianness = Ctx.Obj.isLittleEndian() ? little : big; + uint32_t *TypesBuffer32 = (uint32_t *)TypesBuffer.data(); + for (uint64_t I = 0; I < TypesBuffer.size() / 4; ++I) + TypesBuffer32[I] = byte_swap(TypesBuffer32[I], Endianness); + + // void + Types.push_back(&VOID_TYPE_INST); + + uint64_t Pos = 0; + while (Pos < RawData.size()) { + auto BytesLeft = RawData.size() - Pos; + auto Offset = TypesInfoStart + Pos; + BTF::CommonType *Type = (BTF::CommonType *)&TypesBuffer[Pos]; + if (BytesLeft < sizeof(*Type)) + return Err("Incomplete type definition in .BTF section:") + << " offset " << Offset << ", index " << Types.size(); + + uint64_t Size = byteSize(Type); + if (BytesLeft < Size) + return Err("Incomplete type definition in .BTF section:") + << " offset=" << Offset << ", index=" << Types.size() + << ", vlen=" << Type->getVlen(); + + LLVM_DEBUG({ + llvm::dbgs() << "Adding type " << Types.size() << " " + << findString(Type->NameOff) << ", record size " << Size + << "\n"; + }); + Types.push_back(Type); + Pos += Size; + } + return Error::success(); } @@ -177,22 +301,32 @@ auto HdrLen = Extractor.getU32(C); if (!C) return Err(".BTF.ext", C); - if (HdrLen < 8) + if (HdrLen < 8 * sizeof(uint32_t)) 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); + auto RelocInfoOff = Extractor.getU32(C); + auto RelocInfoLen = Extractor.getU32(C); if (!C) return Err(".BTF.ext", C); - auto LineInfoStart = HdrLen + LineInfoOff; - auto LineInfoEnd = LineInfoStart + LineInfoLen; + if (LineInfoLen > 0 && Ctx.Opts.LoadLines) { + auto LineInfoStart = HdrLen + LineInfoOff; + auto LineInfoEnd = LineInfoStart + LineInfoLen; + if (auto E = parseLineInfo(Ctx, Extractor, LineInfoStart, LineInfoEnd)) + return E; + } - if (auto E = parseLineInfo(Ctx, Extractor, LineInfoStart, LineInfoEnd)) - return E; + if (RelocInfoLen > 0 && Ctx.Opts.LoadRelocs) { + auto RelocInfoStart = HdrLen + RelocInfoOff; + auto RelocInfoEnd = RelocInfoStart + RelocInfoLen; + if (auto E = parseRelocInfo(Ctx, Extractor, RelocInfoStart, RelocInfoEnd)) + return E; + } return Error::success(); } @@ -242,11 +376,61 @@ return Error::success(); } -Error BTFParser::parse(const ObjectFile &Obj) { +Error BTFParser::parseRelocInfo(ParseContext &Ctx, DataExtractor &Extractor, + uint64_t RelocInfoStart, + uint64_t RelocInfoEnd) { + DataExtractor::Cursor C = DataExtractor::Cursor(RelocInfoStart); + auto RecSize = Extractor.getU32(C); + if (!C) + return Err(".BTF.ext", C); + if (RecSize < 16) + return Err("Unexpected .BTF.ext field reloc info record length: ") + << RecSize; + + while (C && C.tell() < RelocInfoEnd) { + std::unique_ptr Relocs(new BTFRelocVector()); + + 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 TypeID = Extractor.getU32(C); + auto OffsetNameOff = Extractor.getU32(C); + auto RelocKind = Extractor.getU32(C); + + Relocs->push_back({InsnOff, TypeID, OffsetNameOff, RelocKind}); + + C.seek(RecStart + RecSize); + } + + std::sort(Relocs->begin(), Relocs->end(), [](const auto &L, const auto &R) { + return L.InsnOffset < R.InsnOffset; + }); + + if (Sec) + SectionRelocs[Sec->getIndex()] = std::move(Relocs); + } + + if (!C) + return Err(".BTF.ext", C); + + return Error::success(); +} + +Error BTFParser::parse(const ObjectFile &Obj, const ParseOptions &Opts) { StringsTable = StringRef(); SectionLines.clear(); + SectionRelocs.clear(); + Types.clear(); + TypesBuffer = OwningArrayRef(); - ParseContext Ctx(Obj); + ParseContext Ctx(Obj, Opts); if (auto E = Ctx.init()) return E; @@ -287,17 +471,377 @@ return StringsTable.slice(Offset, StringsTable.find(0, Offset)); } -BTF::BPFLineInfo *BTFParser::findLineInfo(SectionedAddress Address) { - auto MaybeSecInfo = SectionLines.find(Address.SectionIndex); - if (MaybeSecInfo == SectionLines.end()) +template +static T *findInfo(std::map>> &SecMap, + SectionedAddress Address) { + auto MaybeSecInfo = SecMap.find(Address.SectionIndex); + if (MaybeSecInfo == SecMap.end()) return nullptr; auto &SecInfo = MaybeSecInfo->second; - auto LineInfo = std::lower_bound( + auto Info = 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) + if (Info == SecInfo->end() || Info->InsnOffset != Address.Address) return nullptr; - return &*LineInfo; + return &*Info; +} + +BTF::BPFLineInfo *BTFParser::findLineInfo(SectionedAddress Address) { + return findInfo(SectionLines, Address); +} + +BTF::BPFFieldReloc *BTFParser::findFieldReloc(SectionedAddress Address) { + return findInfo(SectionRelocs, Address); +} + +const BTF::CommonType *BTFParser::findType(uint32_t Id) { + if (Id < Types.size()) + return Types[Id]; + return nullptr; +} + +enum ReloKindGroup { + RKG_FIELD, + RKG_TYPE, + RKG_ENUMVAL, + RKG_UNKNOWN, +}; + +static ReloKindGroup reloKindGroup(const BTF::BPFFieldReloc *Reloc) { + switch (Reloc->RelocKind) { + case BTF::FIELD_BYTE_OFFSET: + case BTF::FIELD_BYTE_SIZE: + case BTF::FIELD_EXISTENCE: + case BTF::FIELD_SIGNEDNESS: + case BTF::FIELD_LSHIFT_U64: + case BTF::FIELD_RSHIFT_U64: + return RKG_FIELD; + case BTF::BTF_TYPE_ID_LOCAL: + case BTF::BTF_TYPE_ID_REMOTE: + case BTF::TYPE_EXISTENCE: + case BTF::TYPE_MATCH: + case BTF::TYPE_SIZE: + return RKG_TYPE; + case BTF::ENUM_VALUE_EXISTENCE: + case BTF::ENUM_VALUE: + return RKG_ENUMVAL; + default: + return RKG_UNKNOWN; + } +} + +static bool isMod(const BTF::CommonType *Type) { + switch (Type->getKind()) { + case BTF::BTF_KIND_VOLATILE: + case BTF::BTF_KIND_CONST: + case BTF::BTF_KIND_RESTRICT: + case BTF::BTF_KIND_TYPE_TAG: + return true; + default: + return false; + } +} + +static bool printMod(BTFParser &BTF, const BTF::CommonType *Type, + raw_ostream &Stream) { + switch (Type->getKind()) { + case BTF::BTF_KIND_CONST: + Stream << " const"; + break; + case BTF::BTF_KIND_VOLATILE: + Stream << " volatile"; + break; + case BTF::BTF_KIND_RESTRICT: + Stream << " restrict"; + break; + case BTF::BTF_KIND_TYPE_TAG: + Stream << " type_tag(\"" << BTF.findString(Type->NameOff) << "\")"; + break; + default: + return false; + } + return true; +} + +static const BTF::CommonType *skipModsAndTypedefs(BTFParser &BTF, + const BTF::CommonType *Type) { + while (isMod(Type) || Type->getKind() == BTF::BTF_KIND_TYPEDEF) { + auto *Base = BTF.findType(Type->Type); + if (!Base) + break; + Type = Base; + } + return Type; +} + +struct StrOrAnon { + BTFParser &BTF; + uint32_t Offset; + uint32_t Idx; +}; + +static raw_ostream &operator<<(raw_ostream &Stream, const StrOrAnon &S) { + StringRef Str = S.BTF.findString(S.Offset); + if (Str.empty()) + Stream << ""; + else + Stream << Str; + return Stream; +} + +static void reloKindName(uint32_t X, raw_ostream &out) { + switch (X) { + default: + out << ""; + break; +#define HANDLE_RELO_KIND(Val, Name) \ + case Val: \ + out << #Name; \ + break; +#include "llvm/DebugInfo/BTF/CoreRelo.def" +#undef HANDLE_RELO_KIND + } +} + +// Produces a human readable description of a CO-RE relocation. +// Such relocations are generated by BPF backend, specifically by code +// from the following files: +// - llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp +// - llvm/lib/Target/BPF/BTFDebug.cpp +// And than processed by libbpf's BPF program loader [1]. +// +// Each relocation record has the following information: +// - Relocation kind; +// - BTF type ID; +// - Access string offset in string table. +// +// There are different kinds of relocations, these kinds could be split +// in three groups: +// - load-time information about types (size, existence), +// `BTFParser::symbolize()` output for such relocations uses the template: +// +// [] +// +// For example: +// - "TYPE_EXISTENCE [7] struct foo" +// - "TYPE_SIZE [7] struct foo" +// +// - load-time information about enums (literal existence, literal value), +// `BTFParser::symbolize()` output for such relocations uses the template: +// +// [] :: +// +// For example: +// - "ENUM_VALUE_EXISTENCE [5] enum foo::U" +// - "ENUM_VALUE [5] enum foo::V" +// +// - load-time information about fields (e.g. field offset), +// `BTFParser::symbolize()` output for such relocations uses the template: +// +// [] ::[N].... +// +// For example: +// - "FIELD_BYTE_OFFSET [8] struct bar::[7].v" +// - "FIELD_EXISTENCE [8] struct bar::[0].v" +// +// If relocation description is not valid output follows the following pattern: +// +// :: <> +// +// For example: +// +// - "TYPE_SIZE 42:: " +// - "FIELD_BYTE_OFFSET 4:: " +// +// Additional examples could be found in unit tests, see +// llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp. +// +// [1] https://www.kernel.org/doc/html/latest/bpf/libbpf/index.html +std::string BTFParser::symbolize(BTF::BPFFieldReloc *Reloc) { + std::string Result; + raw_string_ostream Stream(Result); + StringRef SpecStr = findString(Reloc->OffsetNameOff); + SmallVector RawSpec; + + auto Fail = [&](auto Msg) { + Result.resize(0); + reloKindName(Reloc->RelocKind, Stream); + Stream << " " << Reloc->TypeID << "::" << findString(Reloc->OffsetNameOff); + Stream << " <" << Msg << ">"; + return Result; + }; + + // Relocation access string follows pattern [0-9]+(:[0-9]+)*, + // e.g.: 12:22:3. Code below Splis the `SpecStr` by ':', + // parses numbers and pushes those to `RawSpec`. + while (SpecStr.size()) { + unsigned long long Val; + if (consumeUnsignedInteger(SpecStr, 10, Val)) + return Fail("spec string is not a number"); + + RawSpec.push_back(Val); + + if (SpecStr.size() && SpecStr[0] != ':') + return Fail(format("unexpected spec string delimiter: '%c'", SpecStr[0])); + + SpecStr = SpecStr.substr(1); + } + + // Print relocation kind to `Stream` + reloKindName(Reloc->RelocKind, Stream); + + auto CurId = Reloc->TypeID; + auto *Type = findType(CurId); + if (!Type) + return Fail(format("unknown type id: %d", CurId)); + + Stream << " [" << CurId << "]"; + + // `Type` might have modifiers, e.g. for type 'const int' the `Type` + // would refer to BTF type of kind BTF_KIND_CONST. + // Print all these modifiers to `Stream`. + for (uint32_t ChainLen = 0; printMod(*this, Type, Stream); ++ChainLen) { + if (ChainLen >= 32) + return Fail("modifiers chain is too long"); + + CurId = Type->Type; + auto *NextType = findType(CurId); + if (!NextType) + return Fail(format("unknown type id: %d in modifiers chain", CurId)); + Type = NextType; + } + // Print the type name to `Stream` + if (CurId == 0) { + Stream << " void"; + } else { + switch (Type->getKind()) { + case BTF::BTF_KIND_TYPEDEF: + Stream << " typedef"; + break; + case BTF::BTF_KIND_STRUCT: + Stream << " struct"; + break; + case BTF::BTF_KIND_UNION: + Stream << " union"; + break; + case BTF::BTF_KIND_ENUM: + Stream << " enum"; + break; + case BTF::BTF_KIND_ENUM64: + Stream << " enum"; + break; + case BTF::BTF_KIND_FWD: + if (Type->Info & BTF::FWD_UNION_FLAG) + Stream << " union"; + else + Stream << " struct"; + break; + default: + break; + } + Stream << " " << StrOrAnon({*this, Type->NameOff, CurId}); + } + + auto Group = reloKindGroup(Reloc); + + // Type-based relocations don't use access string. + if (Group == RKG_TYPE) + return Result; + + Stream << "::"; + + // For enum-based relocations access string is a single number, + // corresponding to the enum literal sequential number. + // E.g. for `enum E { U, V }`, relocation requesting value of `V` + // would look as follows: + // - kind: BTF::ENUM_VALUE + // - BTF id: id for `E` + // - access string: "1" + if (Group == RKG_ENUMVAL) { + Type = skipModsAndTypedefs(*this, Type); + + if (RawSpec.size() != 1) + return Fail("unexpected enumval relo spec size"); + + uint32_t NameOff; + uint32_t Idx = RawSpec[0]; + if (auto *T = dyn_cast(Type)) { + if (T->values().size() <= Idx) + return Fail(format("bad value index: %d", Idx)); + NameOff = T->values()[Idx].NameOff; + } else if (auto *T = dyn_cast(Type)) { + if (T->values().size() <= Idx) + return Fail(format("bad value index: %d", Idx)); + NameOff = T->values()[Idx].NameOff; + } else { + return Fail( + format("unexpected type kind for enum relo: %d", Type->getKind())); + } + + Stream << StrOrAnon({*this, NameOff, Idx}); + return Result; + } + + // For type-based relocations access string is an array of numbers, + // which resemble index parameters for `getelementptr` LLVM IR instruction. + // E.g. for the following types: + // + // struct foo { + // int a; + // int b; + // }; + // struct bar { + // int u; + // struct foo v[7]; + // }; + // + // Relocation requesting `offsetof(struct bar, v[2].b)` will have + // the following access string: 0:1:2:1 + // ^ ^ ^ ^ + // | | | | + // initial index | | field 'b' is a field #1 + // | | (counting from 0) + // | array index #2 + // field 'v' is a field #1 + // (counting from 0) + if (Group == RKG_FIELD) { + if (RawSpec.size() < 1) + return Fail("field spec too short"); + + Stream << "[" << RawSpec[0] << "]"; + for (uint32_t I = 1; I < RawSpec.size(); ++I) { + Type = skipModsAndTypedefs(*this, Type); + uint32_t Idx = RawSpec[I]; + + if (auto *T = dyn_cast(Type)) { + if (T->getVlen() <= Idx) + return Fail( + format("member index %d for spec sub-string %d is out of range", + Idx, I)); + + auto &Member = T->members()[Idx]; + Stream << "." << StrOrAnon({*this, Member.NameOff, Idx}); + Type = findType(Member.Type); + if (!Type) + return Fail(format("unknown member type id %d for spec sub-string %d", + Member.Type, I)); + } else if (auto *T = dyn_cast(Type)) { + Stream << "[" << Idx << "]"; + Type = findType(T->getArray().ElemType); + if (!Type) + return Fail( + format("unknown element type id %d for spec sub-string %d", + T->getArray().ElemType, I)); + } else { + return Fail(format("unexpected type kind %d for spec sub-string %d", + Type->getKind(), I)); + } + } + + return Result; + } + + return Fail(format("unknown relo kind: %d", Reloc->RelocKind)); } diff --git a/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp b/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp --- a/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp +++ b/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp @@ -78,6 +78,7 @@ #include "BPFCORE.h" #include "BPFTargetMachine.h" #include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/DebugInfo/BTF/BTF.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/GlobalVariable.h" #include "llvm/IR/Instruction.h" @@ -396,7 +397,7 @@ CInfo.Metadata = nullptr; // Check validity of info_kind as clang did not check this. uint64_t InfoKind = getConstant(Call->getArgOperand(1)); - if (InfoKind >= BPFCoreSharedInfo::MAX_FIELD_RELOC_KIND) + if (InfoKind >= BTF::MAX_FIELD_RELOC_KIND) report_fatal_error("Incorrect info_kind for llvm.bpf.preserve.field.info intrinsic"); CInfo.AccessIndex = InfoKind; return true; @@ -410,11 +411,11 @@ if (Flag >= BPFCoreSharedInfo::MAX_PRESERVE_TYPE_INFO_FLAG) report_fatal_error("Incorrect flag for llvm.bpf.preserve.type.info intrinsic"); if (Flag == BPFCoreSharedInfo::PRESERVE_TYPE_INFO_EXISTENCE) - CInfo.AccessIndex = BPFCoreSharedInfo::TYPE_EXISTENCE; + CInfo.AccessIndex = BTF::TYPE_EXISTENCE; else if (Flag == BPFCoreSharedInfo::PRESERVE_TYPE_INFO_MATCH) - CInfo.AccessIndex = BPFCoreSharedInfo::TYPE_MATCH; + CInfo.AccessIndex = BTF::TYPE_MATCH; else - CInfo.AccessIndex = BPFCoreSharedInfo::TYPE_SIZE; + CInfo.AccessIndex = BTF::TYPE_SIZE; return true; } if (GV->getName().startswith("llvm.bpf.preserve.enum.value")) { @@ -426,9 +427,9 @@ if (Flag >= BPFCoreSharedInfo::MAX_PRESERVE_ENUM_VALUE_FLAG) report_fatal_error("Incorrect flag for llvm.bpf.preserve.enum.value intrinsic"); if (Flag == BPFCoreSharedInfo::PRESERVE_ENUM_VALUE_EXISTENCE) - CInfo.AccessIndex = BPFCoreSharedInfo::ENUM_VALUE_EXISTENCE; + CInfo.AccessIndex = BTF::ENUM_VALUE_EXISTENCE; else - CInfo.AccessIndex = BPFCoreSharedInfo::ENUM_VALUE; + CInfo.AccessIndex = BTF::ENUM_VALUE; return true; } @@ -699,11 +700,11 @@ uint32_t AccessIndex, uint32_t PatchImm, MaybeAlign RecordAlignment) { - if (InfoKind == BPFCoreSharedInfo::FIELD_EXISTENCE) - return 1; + if (InfoKind == BTF::FIELD_EXISTENCE) + return 1; uint32_t Tag = CTy->getTag(); - if (InfoKind == BPFCoreSharedInfo::FIELD_BYTE_OFFSET) { + if (InfoKind == BTF::FIELD_BYTE_OFFSET) { if (Tag == dwarf::DW_TAG_array_type) { auto *EltTy = stripQualifiers(CTy->getBaseType()); PatchImm += AccessIndex * calcArraySize(CTy, 1) * @@ -722,7 +723,7 @@ return PatchImm; } - if (InfoKind == BPFCoreSharedInfo::FIELD_BYTE_SIZE) { + if (InfoKind == BTF::FIELD_BYTE_SIZE) { if (Tag == dwarf::DW_TAG_array_type) { auto *EltTy = stripQualifiers(CTy->getBaseType()); return calcArraySize(CTy, 1) * (EltTy->getSizeInBits() >> 3); @@ -742,7 +743,7 @@ } } - if (InfoKind == BPFCoreSharedInfo::FIELD_SIGNEDNESS) { + if (InfoKind == BTF::FIELD_SIGNEDNESS) { const DIType *BaseTy; if (Tag == dwarf::DW_TAG_array_type) { // Signedness only checked when final array elements are accessed. @@ -768,7 +769,7 @@ return (Encoding == dwarf::DW_ATE_signed || Encoding == dwarf::DW_ATE_signed_char); } - if (InfoKind == BPFCoreSharedInfo::FIELD_LSHIFT_U64) { + if (InfoKind == BTF::FIELD_LSHIFT_U64) { // The value is loaded into a value with FIELD_BYTE_SIZE size, // and then zero or sign extended to U64. // FIELD_LSHIFT_U64 and FIELD_RSHIFT_U64 are operations @@ -805,7 +806,7 @@ return OffsetInBits + 64 - NextSBitOffset; } - if (InfoKind == BPFCoreSharedInfo::FIELD_RSHIFT_U64) { + if (InfoKind == BTF::FIELD_RSHIFT_U64) { DIDerivedType *MemberTy = nullptr; bool IsBitField = false; uint32_t SizeInBits; @@ -876,7 +877,7 @@ // we will skip them. uint32_t FirstIndex = 0; uint32_t PatchImm = 0; // AccessOffset or the requested field info - uint32_t InfoKind = BPFCoreSharedInfo::FIELD_BYTE_OFFSET; + uint32_t InfoKind = BTF::FIELD_BYTE_OFFSET; while (CallStack.size()) { auto StackElem = CallStack.top(); Call = StackElem.first; @@ -966,7 +967,7 @@ if (CInfo.Kind == BPFPreserveFieldInfoAI) { InfoKind = CInfo.AccessIndex; - if (InfoKind == BPFCoreSharedInfo::FIELD_EXISTENCE) + if (InfoKind == BTF::FIELD_EXISTENCE) PatchImm = 1; break; } @@ -1014,10 +1015,10 @@ int64_t PatchImm; std::string AccessStr("0"); - if (CInfo.AccessIndex == BPFCoreSharedInfo::TYPE_EXISTENCE || - CInfo.AccessIndex == BPFCoreSharedInfo::TYPE_MATCH) { + if (CInfo.AccessIndex == BTF::TYPE_EXISTENCE || + CInfo.AccessIndex == BTF::TYPE_MATCH) { PatchImm = 1; - } else if (CInfo.AccessIndex == BPFCoreSharedInfo::TYPE_SIZE) { + } else if (CInfo.AccessIndex == BTF::TYPE_SIZE) { // typedef debuginfo type has size 0, get the eventual base type. DIType *BaseTy = stripQualifiers(Ty, true); PatchImm = BaseTy->getSizeInBits() / 8; @@ -1053,7 +1054,7 @@ EnumIndex++; } - if (CInfo.AccessIndex == BPFCoreSharedInfo::ENUM_VALUE) { + if (CInfo.AccessIndex == BTF::ENUM_VALUE) { StringRef EValueStr = ValueStr.substr(Separator + 1); PatchImm = std::stoll(std::string(EValueStr)); } else { diff --git a/llvm/lib/Target/BPF/BPFCORE.h b/llvm/lib/Target/BPF/BPFCORE.h --- a/llvm/lib/Target/BPF/BPFCORE.h +++ b/llvm/lib/Target/BPF/BPFCORE.h @@ -19,24 +19,6 @@ class BPFCoreSharedInfo { public: - enum PatchableRelocKind : uint32_t { - FIELD_BYTE_OFFSET = 0, - FIELD_BYTE_SIZE, - FIELD_EXISTENCE, - FIELD_SIGNEDNESS, - FIELD_LSHIFT_U64, - FIELD_RSHIFT_U64, - BTF_TYPE_ID_LOCAL, - BTF_TYPE_ID_REMOTE, - TYPE_EXISTENCE, - TYPE_SIZE, - ENUM_VALUE_EXISTENCE, - ENUM_VALUE, - TYPE_MATCH, - - MAX_FIELD_RELOC_KIND, - }; - enum BTFTypeIdFlag : uint32_t { BTF_TYPE_ID_LOCAL_RELOC = 0, BTF_TYPE_ID_REMOTE_RELOC, diff --git a/llvm/lib/Target/BPF/BPFPreserveDIType.cpp b/llvm/lib/Target/BPF/BPFPreserveDIType.cpp --- a/llvm/lib/Target/BPF/BPFPreserveDIType.cpp +++ b/llvm/lib/Target/BPF/BPFPreserveDIType.cpp @@ -13,6 +13,7 @@ #include "BPF.h" #include "BPFCORE.h" #include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/DebugInfo/BTF/BTF.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/GlobalVariable.h" #include "llvm/IR/Instruction.h" @@ -82,9 +83,9 @@ uint32_t Reloc; if (FlagValue == BPFCoreSharedInfo::BTF_TYPE_ID_LOCAL_RELOC) { - Reloc = BPFCoreSharedInfo::BTF_TYPE_ID_LOCAL; + Reloc = BTF::BTF_TYPE_ID_LOCAL; } else { - Reloc = BPFCoreSharedInfo::BTF_TYPE_ID_REMOTE; + Reloc = BTF::BTF_TYPE_ID_REMOTE; DIType *Ty = cast(MD); while (auto *DTy = dyn_cast(Ty)) { unsigned Tag = DTy->getTag(); diff --git a/llvm/lib/Target/BPF/BTFDebug.cpp b/llvm/lib/Target/BPF/BTFDebug.cpp --- a/llvm/lib/Target/BPF/BTFDebug.cpp +++ b/llvm/lib/Target/BPF/BTFDebug.cpp @@ -1510,10 +1510,8 @@ return false; } - if (Reloc == BPFCoreSharedInfo::ENUM_VALUE_EXISTENCE || - Reloc == BPFCoreSharedInfo::ENUM_VALUE || - Reloc == BPFCoreSharedInfo::BTF_TYPE_ID_LOCAL || - Reloc == BPFCoreSharedInfo::BTF_TYPE_ID_REMOTE) + if (Reloc == BTF::ENUM_VALUE_EXISTENCE || Reloc == BTF::ENUM_VALUE || + Reloc == BTF::BTF_TYPE_ID_LOCAL || Reloc == BTF::BTF_TYPE_ID_REMOTE) OutMI.setOpcode(BPF::LD_imm64); else OutMI.setOpcode(BPF::MOV_ri); diff --git a/llvm/test/tools/llvm-objdump/BPF/core-relo-byte-offset.ll b/llvm/test/tools/llvm-objdump/BPF/core-relo-byte-offset.ll new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-byte-offset.ll @@ -0,0 +1,154 @@ +; REQUIRES: bpf-registered-target +; +; Verify that llvm-objdump can use .BTF.ext to CO-RE relocation data. +; +; RUN: llc --mtriple bpfel %s --filetype=obj -o - \ +; RUN: | llvm-objdump --no-addresses --no-show-raw-insn -dr - \ +; RUN: | FileCheck %s +; +; RUN: llc --mtriple bpfeb %s --filetype=obj -o - \ +; RUN: | llvm-objdump --no-addresses --no-show-raw-insn -dr - \ +; RUN: | FileCheck %s +; +; Input generated from the following C code: +; +; #define __pai __attribute__((preserve_access_index)) +; +; struct buz { +; int a; +; int b; +; } __pai; +; +; struct foo { +; int :4; +; int i; +; struct buz k[10]; +; } __pai; +; +; struct bar { +; struct foo f; +; } __pai; +; +; void * volatile g; +; +; void root(void) { +; struct bar *bar = (void *)0; +; g = &bar->f; +; g = &bar->f.i; +; g = &bar->f.k; +; g = &bar->f.k[7].a; +; g = &bar->f.k[7].b; +; g = &bar[1].f.k[7].b; +; } +; +; Using the following command: +; +; clang -target bpf -g -O2 -emit-llvm -S t.c +; +; CHECK: CO-RE relo: FIELD_BYTE_OFFSET [[[#]]] struct bar::[0].f +; CHECK: CO-RE relo: FIELD_BYTE_OFFSET [[[#]]] struct bar::[0].f.i +; CHECK: CO-RE relo: FIELD_BYTE_OFFSET [[[#]]] struct bar::[0].f.k +; CHECK: CO-RE relo: FIELD_BYTE_OFFSET [[[#]]] struct bar::[0].f.k[7].a +; CHECK: CO-RE relo: FIELD_BYTE_OFFSET [[[#]]] struct bar::[0].f.k[7].b +; CHECK: CO-RE relo: FIELD_BYTE_OFFSET [[[#]]] struct bar::[1].f.k[7].b + +@g = dso_local global ptr null, align 8, !dbg !0 +@"llvm.bar:0:0$0:0" = external global i64, !llvm.preserve.access.index !14 #0 +@"llvm.bar:0:8$0:0:1" = external global i64, !llvm.preserve.access.index !14 #0 +@"llvm.bar:0:4$0:0:0" = external global i64, !llvm.preserve.access.index !14 #0 +@"llvm.bar:0:64$0:0:1:7:0" = external global i64, !llvm.preserve.access.index !14 #0 +@"llvm.bar:0:68$0:0:1:7:1" = external global i64, !llvm.preserve.access.index !14 #0 +@"llvm.bar:0:156$1:0:1:7:1" = external global i64, !llvm.preserve.access.index !14 #0 + +; Function Attrs: nofree nounwind memory(readwrite, argmem: none) +define dso_local void @root() local_unnamed_addr #1 !dbg !29 { +entry: + call void @llvm.dbg.value(metadata ptr null, metadata !33, metadata !DIExpression()), !dbg !34 + %0 = load i64, ptr @"llvm.bar:0:0$0:0", align 8 + %1 = getelementptr i8, ptr null, i64 %0 + %2 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 0, ptr %1) + store volatile ptr %2, ptr @g, align 8, !dbg !35, !tbaa !36 + %3 = load i64, ptr @"llvm.bar:0:4$0:0:0", align 8 + %4 = getelementptr i8, ptr null, i64 %3 + %5 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 2, ptr %4) + store volatile ptr %5, ptr @g, align 8, !dbg !40, !tbaa !36 + %6 = load i64, ptr @"llvm.bar:0:8$0:0:1", align 8 + %7 = getelementptr i8, ptr null, i64 %6 + %8 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 1, ptr %7) + store volatile ptr %8, ptr @g, align 8, !dbg !41, !tbaa !36 + %9 = load i64, ptr @"llvm.bar:0:64$0:0:1:7:0", align 8 + %10 = getelementptr i8, ptr null, i64 %9 + %11 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 3, ptr %10) + store volatile ptr %11, ptr @g, align 8, !dbg !42, !tbaa !36 + %12 = load i64, ptr @"llvm.bar:0:68$0:0:1:7:1", align 8 + %13 = getelementptr i8, ptr null, i64 %12 + %14 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 4, ptr %13) + store volatile ptr %14, ptr @g, align 8, !dbg !43, !tbaa !36 + %15 = load i64, ptr @"llvm.bar:0:156$1:0:1:7:1", align 8 + %16 = getelementptr i8, ptr null, i64 %15 + %17 = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 5, ptr %16) + store volatile ptr %17, ptr @g, align 8, !dbg !44, !tbaa !36 + ret void, !dbg !45 +} + +; Function Attrs: nofree nosync nounwind memory(none) +declare ptr @llvm.bpf.passthrough.p0.p0(i32, ptr) #2 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare void @llvm.dbg.value(metadata, metadata, metadata) #3 + +attributes #0 = { "btf_ama" } +attributes #1 = { nofree nounwind memory(readwrite, argmem: none) "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nofree nosync nounwind memory(none) } +attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!24, !25, !26, !27} +!llvm.ident = !{!28} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 18, type: !22, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, retainedTypes: !4, globals: !21, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "t.c", directory: "/home/eddy/work/tmp", checksumkind: CSK_MD5, checksum: "f7c638151153f385e69bef98e88c80ef") +!4 = !{!5, !13} +!5 = !DICompositeType(tag: DW_TAG_array_type, baseType: !6, size: 640, elements: !11) +!6 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "buz", file: !3, line: 3, size: 64, elements: !7) +!7 = !{!8, !10} +!8 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !6, file: !3, line: 4, baseType: !9, size: 32) +!9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!10 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !6, file: !3, line: 5, baseType: !9, size: 32, offset: 32) +!11 = !{!12} +!12 = !DISubrange(count: 10) +!13 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !14, size: 64) +!14 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "bar", file: !3, line: 14, size: 704, elements: !15) +!15 = !{!16} +!16 = !DIDerivedType(tag: DW_TAG_member, name: "f", scope: !14, file: !3, line: 15, baseType: !17, size: 704) +!17 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "foo", file: !3, line: 8, size: 704, elements: !18) +!18 = !{!19, !20} +!19 = !DIDerivedType(tag: DW_TAG_member, name: "i", scope: !17, file: !3, line: 10, baseType: !9, size: 32, offset: 32) +!20 = !DIDerivedType(tag: DW_TAG_member, name: "k", scope: !17, file: !3, line: 11, baseType: !5, size: 640, offset: 64) +!21 = !{!0} +!22 = !DIDerivedType(tag: DW_TAG_volatile_type, baseType: !23) +!23 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64) +!24 = !{i32 7, !"Dwarf Version", i32 5} +!25 = !{i32 2, !"Debug Info Version", i32 3} +!26 = !{i32 1, !"wchar_size", i32 4} +!27 = !{i32 7, !"frame-pointer", i32 2} +!28 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)"} +!29 = distinct !DISubprogram(name: "root", scope: !3, file: !3, line: 20, type: !30, scopeLine: 20, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !32) +!30 = !DISubroutineType(types: !31) +!31 = !{null} +!32 = !{!33} +!33 = !DILocalVariable(name: "bar", scope: !29, file: !3, line: 21, type: !13) +!34 = !DILocation(line: 0, scope: !29) +!35 = !DILocation(line: 22, column: 5, scope: !29) +!36 = !{!37, !37, i64 0} +!37 = !{!"any pointer", !38, i64 0} +!38 = !{!"omnipotent char", !39, i64 0} +!39 = !{!"Simple C/C++ TBAA"} +!40 = !DILocation(line: 23, column: 5, scope: !29) +!41 = !DILocation(line: 24, column: 5, scope: !29) +!42 = !DILocation(line: 25, column: 5, scope: !29) +!43 = !DILocation(line: 26, column: 5, scope: !29) +!44 = !DILocation(line: 27, column: 5, scope: !29) +!45 = !DILocation(line: 28, column: 1, scope: !29) diff --git a/llvm/test/tools/llvm-objdump/BPF/core-relo-enum-value.ll b/llvm/test/tools/llvm-objdump/BPF/core-relo-enum-value.ll new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-enum-value.ll @@ -0,0 +1,86 @@ +; REQUIRES: bpf-registered-target +; +; Verify that llvm-objdump can use .BTF.ext to CO-RE relocation data. +; +; RUN: llc --mtriple bpfel %s --filetype=obj -o - \ +; RUN: | llvm-objdump --no-addresses --no-show-raw-insn -dr - \ +; RUN: | FileCheck %s +; +; RUN: llc --mtriple bpfeb %s --filetype=obj -o - \ +; RUN: | llvm-objdump --no-addresses --no-show-raw-insn -dr - \ +; RUN: | FileCheck %s +; +; Input generated from the following C code: +; +; #define __pai __attribute__((preserve_access_index)) +; +; enum bar { U, V }; +; volatile unsigned long g; +; void root(void) { +; g = __builtin_preserve_enum_value(*(enum bar *)U, 0); +; g = __builtin_preserve_enum_value(*(enum bar *)V, 1); +; } +; +; Using the following command: +; +; clang -target bpf -g -O2 -emit-llvm -S t.c +; +; CHECK: CO-RE relo: ENUM_VALUE_EXISTENCE [[[#]]] enum bar::U +; CHECK: CO-RE relo: ENUM_VALUE [[[#]]] enum bar::V + +@g = dso_local global i64 0, align 8, !dbg !0 +@"llvm.bar:11:1$1" = external global i64, !llvm.preserve.access.index !5 #0 +@"llvm.bar:10:1$0" = external global i64, !llvm.preserve.access.index !5 #0 + +; Function Attrs: nofree nounwind memory(readwrite, argmem: none) +define dso_local void @root() local_unnamed_addr #1 !dbg !18 { +entry: + %0 = load i64, ptr @"llvm.bar:10:1$0", align 8 + %1 = tail call i64 @llvm.bpf.passthrough.i64.i64(i32 1, i64 %0) + store volatile i64 %1, ptr @g, align 8, !dbg !22, !tbaa !23 + %2 = load i64, ptr @"llvm.bar:11:1$1", align 8 + %3 = tail call i64 @llvm.bpf.passthrough.i64.i64(i32 0, i64 %2) + store volatile i64 %3, ptr @g, align 8, !dbg !27, !tbaa !23 + ret void, !dbg !28 +} + +; Function Attrs: nofree nosync nounwind memory(none) +declare i64 @llvm.bpf.passthrough.i64.i64(i32, i64) #2 + +attributes #0 = { "btf_ama" } +attributes #1 = { nofree nounwind memory(readwrite, argmem: none) "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nofree nosync nounwind memory(none) } + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!13, !14, !15, !16} +!llvm.ident = !{!17} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 4, type: !11, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !10, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "t.c", directory: "/home/eddy/work/tmp", checksumkind: CSK_MD5, checksum: "5423aa9ef48cb61e948b5c2bd75fd1df") +!4 = !{!5} +!5 = !DICompositeType(tag: DW_TAG_enumeration_type, name: "bar", file: !3, line: 3, baseType: !6, size: 32, elements: !7) +!6 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned) +!7 = !{!8, !9} +!8 = !DIEnumerator(name: "U", value: 0) +!9 = !DIEnumerator(name: "V", value: 1) +!10 = !{!0} +!11 = !DIDerivedType(tag: DW_TAG_volatile_type, baseType: !12) +!12 = !DIBasicType(name: "unsigned long", size: 64, encoding: DW_ATE_unsigned) +!13 = !{i32 7, !"Dwarf Version", i32 5} +!14 = !{i32 2, !"Debug Info Version", i32 3} +!15 = !{i32 1, !"wchar_size", i32 4} +!16 = !{i32 7, !"frame-pointer", i32 2} +!17 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)"} +!18 = distinct !DISubprogram(name: "root", scope: !3, file: !3, line: 5, type: !19, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !21) +!19 = !DISubroutineType(types: !20) +!20 = !{null} +!21 = !{} +!22 = !DILocation(line: 6, column: 5, scope: !18) +!23 = !{!24, !24, i64 0} +!24 = !{!"long", !25, i64 0} +!25 = !{!"omnipotent char", !26, i64 0} +!26 = !{!"Simple C/C++ TBAA"} +!27 = !DILocation(line: 7, column: 5, scope: !18) +!28 = !DILocation(line: 8, column: 1, scope: !18) diff --git a/llvm/test/tools/llvm-objdump/BPF/core-relo-field-info.ll b/llvm/test/tools/llvm-objdump/BPF/core-relo-field-info.ll new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-field-info.ll @@ -0,0 +1,124 @@ +; REQUIRES: bpf-registered-target +; +; Verify that llvm-objdump can use .BTF.ext to CO-RE relocation data. +; +; RUN: llc --mtriple bpfel %s --filetype=obj -o - \ +; RUN: | llvm-objdump --no-addresses --no-show-raw-insn -dr - \ +; RUN: | FileCheck %s +; +; RUN: llc --mtriple bpfeb %s --filetype=obj -o - \ +; RUN: | llvm-objdump --no-addresses --no-show-raw-insn -dr - \ +; RUN: | FileCheck %s +; +; Input generated from the following C code: +; +; #define __pai __attribute__((preserve_access_index)) +; +; struct bar { int a; } __pai; +; volatile unsigned long g; +; void root(void) { +; struct bar *bar = (void *)0; +; g = __builtin_preserve_field_info(bar->a, 1); +; g = __builtin_preserve_field_info(bar->a, 2); +; g = __builtin_preserve_field_info(bar->a, 3); +; g = __builtin_preserve_field_info(bar->a, 4); +; g = __builtin_preserve_field_info(bar->a, 5); +; } +; +; Using the following command: +; +; clang -target bpf -g -O2 -emit-llvm -S t.c +; +; CHECK: CO-RE relo: FIELD_BYTE_SIZE [[[#]]] struct bar::[0].a +; CHECK: CO-RE relo: FIELD_EXISTENCE [[[#]]] struct bar::[0].a +; CHECK: CO-RE relo: FIELD_SIGNEDNESS [[[#]]] struct bar::[0].a +; CHECK: CO-RE relo: FIELD_LSHIFT_U64 [[[#]]] struct bar::[0].a +; CHECK: CO-RE relo: FIELD_RSHIFT_U64 [[[#]]] struct bar::[0].a + +@g = dso_local global i64 0, align 8, !dbg !0 +@"llvm.bar:1:4$0:0" = external global i32, !llvm.preserve.access.index !7 #0 +@"llvm.bar:2:1$0:0" = external global i32, !llvm.preserve.access.index !7 #0 +@"llvm.bar:3:1$0:0" = external global i32, !llvm.preserve.access.index !7 #0 +@"llvm.bar:4:32$0:0" = external global i32, !llvm.preserve.access.index !7 #0 +@"llvm.bar:5:32$0:0" = external global i32, !llvm.preserve.access.index !7 #0 + +; Function Attrs: nofree nounwind memory(readwrite, argmem: none) +define dso_local void @root() local_unnamed_addr #1 !dbg !16 { +entry: + call void @llvm.dbg.value(metadata ptr null, metadata !20, metadata !DIExpression()), !dbg !22 + %0 = load i32, ptr @"llvm.bar:1:4$0:0", align 4 + %1 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 0, i32 %0) + %conv = zext i32 %1 to i64, !dbg !23 + store volatile i64 %conv, ptr @g, align 8, !dbg !24, !tbaa !25 + %2 = load i32, ptr @"llvm.bar:2:1$0:0", align 4 + %3 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 1, i32 %2) + %conv1 = zext i32 %3 to i64, !dbg !29 + store volatile i64 %conv1, ptr @g, align 8, !dbg !30, !tbaa !25 + %4 = load i32, ptr @"llvm.bar:3:1$0:0", align 4 + %5 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 2, i32 %4) + %conv2 = zext i32 %5 to i64, !dbg !31 + store volatile i64 %conv2, ptr @g, align 8, !dbg !32, !tbaa !25 + %6 = load i32, ptr @"llvm.bar:4:32$0:0", align 4 + %7 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 3, i32 %6) + %conv3 = zext i32 %7 to i64, !dbg !33 + store volatile i64 %conv3, ptr @g, align 8, !dbg !34, !tbaa !25 + %8 = load i32, ptr @"llvm.bar:5:32$0:0", align 4 + %9 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 4, i32 %8) + %conv4 = zext i32 %9 to i64, !dbg !35 + store volatile i64 %conv4, ptr @g, align 8, !dbg !36, !tbaa !25 + ret void, !dbg !37 +} + +; Function Attrs: nofree nosync nounwind memory(none) +declare i32 @llvm.bpf.passthrough.i32.i32(i32, i32) #2 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare void @llvm.dbg.value(metadata, metadata, metadata) #3 + +attributes #0 = { "btf_ama" } +attributes #1 = { nofree nounwind memory(readwrite, argmem: none) "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nofree nosync nounwind memory(none) } +attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!11, !12, !13, !14} +!llvm.ident = !{!15} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 4, type: !5, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "t.c", directory: "/home/eddy/work/tmp", checksumkind: CSK_MD5, checksum: "ff78616039301f51cd56ee6ea1377b86") +!4 = !{!0} +!5 = !DIDerivedType(tag: DW_TAG_volatile_type, baseType: !6) +!6 = !DIBasicType(name: "unsigned long", size: 64, encoding: DW_ATE_unsigned) +!7 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "bar", file: !3, line: 3, size: 32, elements: !8) +!8 = !{!9} +!9 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !7, file: !3, line: 3, baseType: !10, size: 32) +!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!11 = !{i32 7, !"Dwarf Version", i32 5} +!12 = !{i32 2, !"Debug Info Version", i32 3} +!13 = !{i32 1, !"wchar_size", i32 4} +!14 = !{i32 7, !"frame-pointer", i32 2} +!15 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)"} +!16 = distinct !DISubprogram(name: "root", scope: !3, file: !3, line: 5, type: !17, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !19) +!17 = !DISubroutineType(types: !18) +!18 = !{null} +!19 = !{!20} +!20 = !DILocalVariable(name: "bar", scope: !16, file: !3, line: 6, type: !21) +!21 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !7, size: 64) +!22 = !DILocation(line: 0, scope: !16) +!23 = !DILocation(line: 7, column: 7, scope: !16) +!24 = !DILocation(line: 7, column: 5, scope: !16) +!25 = !{!26, !26, i64 0} +!26 = !{!"long", !27, i64 0} +!27 = !{!"omnipotent char", !28, i64 0} +!28 = !{!"Simple C/C++ TBAA"} +!29 = !DILocation(line: 8, column: 7, scope: !16) +!30 = !DILocation(line: 8, column: 5, scope: !16) +!31 = !DILocation(line: 9, column: 7, scope: !16) +!32 = !DILocation(line: 9, column: 5, scope: !16) +!33 = !DILocation(line: 10, column: 7, scope: !16) +!34 = !DILocation(line: 10, column: 5, scope: !16) +!35 = !DILocation(line: 11, column: 7, scope: !16) +!36 = !DILocation(line: 11, column: 5, scope: !16) +!37 = !DILocation(line: 12, column: 1, scope: !16) diff --git a/llvm/test/tools/llvm-objdump/BPF/core-relo-type-id.ll b/llvm/test/tools/llvm-objdump/BPF/core-relo-type-id.ll new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-type-id.ll @@ -0,0 +1,91 @@ +; REQUIRES: bpf-registered-target +; +; Verify that llvm-objdump can use .BTF.ext to CO-RE relocation data. +; +; RUN: llc --mtriple bpfel %s --filetype=obj -o - \ +; RUN: | llvm-objdump --no-addresses --no-show-raw-insn -dr - \ +; RUN: | FileCheck %s +; +; RUN: llc --mtriple bpfeb %s --filetype=obj -o - \ +; RUN: | llvm-objdump --no-addresses --no-show-raw-insn -dr - \ +; RUN: | FileCheck %s +; +; Input generated from the following C code: +; +; #define __pai __attribute__((preserve_access_index)) +; +; struct bar { } __pai; +; volatile unsigned long g; +; void root(void) { +; struct bar *bar = (void *)0; +; g = __builtin_btf_type_id(*bar, 0); +; g = __builtin_btf_type_id(*bar, 1); +; } +; +; Using the following command: +; +; clang -target bpf -g -O2 -emit-llvm -S t.c +; +; CHECK: CO-RE relo: BTF_TYPE_ID_LOCAL [[[#]]] struct bar +; CHECK: CO-RE relo: BTF_TYPE_ID_REMOTE [[[#]]] struct bar + +@g = dso_local global i64 0, align 8, !dbg !0 +@"llvm.btf_type_id.0$6" = external global i64, !llvm.preserve.access.index !7 #0 +@"llvm.btf_type_id.1$7" = external global i64, !llvm.preserve.access.index !7 #0 + +; Function Attrs: nofree nounwind memory(readwrite, argmem: none) +define dso_local void @root() local_unnamed_addr #1 !dbg !14 { +entry: + call void @llvm.dbg.value(metadata ptr null, metadata !18, metadata !DIExpression()), !dbg !20 + %0 = load i64, ptr @"llvm.btf_type_id.0$6", align 8 + %1 = tail call i64 @llvm.bpf.passthrough.i64.i64(i32 0, i64 %0) + store volatile i64 %1, ptr @g, align 8, !dbg !21, !tbaa !22 + %2 = load i64, ptr @"llvm.btf_type_id.1$7", align 8 + %3 = tail call i64 @llvm.bpf.passthrough.i64.i64(i32 1, i64 %2) + store volatile i64 %3, ptr @g, align 8, !dbg !26, !tbaa !22 + ret void, !dbg !27 +} + +; Function Attrs: nofree nosync nounwind memory(none) +declare i64 @llvm.bpf.passthrough.i64.i64(i32, i64) #2 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare void @llvm.dbg.value(metadata, metadata, metadata) #3 + +attributes #0 = { "btf_type_id" } +attributes #1 = { nofree nounwind memory(readwrite, argmem: none) "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nofree nosync nounwind memory(none) } +attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!9, !10, !11, !12} +!llvm.ident = !{!13} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 4, type: !5, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "t.c", directory: "/home/eddy/work/tmp", checksumkind: CSK_MD5, checksum: "29efc9dba44aaba9e4b0c389bb8694ea") +!4 = !{!0} +!5 = !DIDerivedType(tag: DW_TAG_volatile_type, baseType: !6) +!6 = !DIBasicType(name: "unsigned long", size: 64, encoding: DW_ATE_unsigned) +!7 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "bar", file: !3, line: 3, elements: !8) +!8 = !{} +!9 = !{i32 7, !"Dwarf Version", i32 5} +!10 = !{i32 2, !"Debug Info Version", i32 3} +!11 = !{i32 1, !"wchar_size", i32 4} +!12 = !{i32 7, !"frame-pointer", i32 2} +!13 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)"} +!14 = distinct !DISubprogram(name: "root", scope: !3, file: !3, line: 5, type: !15, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !17) +!15 = !DISubroutineType(types: !16) +!16 = !{null} +!17 = !{!18} +!18 = !DILocalVariable(name: "bar", scope: !14, file: !3, line: 6, type: !19) +!19 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !7, size: 64) +!20 = !DILocation(line: 0, scope: !14) +!21 = !DILocation(line: 7, column: 5, scope: !14) +!22 = !{!23, !23, i64 0} +!23 = !{!"long", !24, i64 0} +!24 = !{!"omnipotent char", !25, i64 0} +!25 = !{!"Simple C/C++ TBAA"} +!26 = !DILocation(line: 8, column: 5, scope: !14) +!27 = !DILocation(line: 9, column: 1, scope: !14) diff --git a/llvm/test/tools/llvm-objdump/BPF/core-relo-type-info.ll b/llvm/test/tools/llvm-objdump/BPF/core-relo-type-info.ll new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-type-info.ll @@ -0,0 +1,104 @@ +; REQUIRES: bpf-registered-target +; +; Verify that llvm-objdump can use .BTF.ext to CO-RE relocation data. +; +; RUN: llc --mtriple bpfel %s --filetype=obj -o - \ +; RUN: | llvm-objdump --no-addresses --no-show-raw-insn -dr - \ +; RUN: | FileCheck %s +; +; RUN: llc --mtriple bpfeb %s --filetype=obj -o - \ +; RUN: | llvm-objdump --no-addresses --no-show-raw-insn -dr - \ +; RUN: | FileCheck %s +; +; Input generated from the following C code: +; +; #define __pai __attribute__((preserve_access_index)) +; +; struct bar { } __pai; +; volatile unsigned long g; +; void root(void) { +; struct bar *bar = (void *)0; +; g = __builtin_preserve_type_info(*bar, 0); +; g = __builtin_preserve_type_info(*bar, 1); +; g = __builtin_preserve_type_info(*bar, 2); +; } +; +; Using the following command: +; +; clang -target bpf -g -O2 -emit-llvm -S t.c +; +; CHECK: CO-RE relo: TYPE_EXISTENCE [[[#]]] struct bar +; CHECK: CO-RE relo: TYPE_SIZE [[[#]]] struct bar +; CHECK: CO-RE relo: TYPE_MATCH [[[#]]] struct bar + +@g = dso_local global i64 0, align 8, !dbg !0 +@"llvm.bar:8:1$0" = external global i32, !llvm.preserve.access.index !7 #0 +@"llvm.bar:9:0$0" = external global i32, !llvm.preserve.access.index !7 #0 +@"llvm.bar:12:1$0" = external global i32, !llvm.preserve.access.index !7 #0 + +; Function Attrs: nofree nounwind memory(readwrite, argmem: none) +define dso_local void @root() local_unnamed_addr #1 !dbg !14 { +entry: + call void @llvm.dbg.value(metadata ptr null, metadata !18, metadata !DIExpression()), !dbg !20 + %0 = load i32, ptr @"llvm.bar:8:1$0", align 4 + %1 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 0, i32 %0) + %conv = zext i32 %1 to i64, !dbg !21 + store volatile i64 %conv, ptr @g, align 8, !dbg !22, !tbaa !23 + %2 = load i32, ptr @"llvm.bar:9:0$0", align 4 + %3 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 1, i32 %2) + %conv1 = zext i32 %3 to i64, !dbg !27 + store volatile i64 %conv1, ptr @g, align 8, !dbg !28, !tbaa !23 + %4 = load i32, ptr @"llvm.bar:12:1$0", align 4 + %5 = tail call i32 @llvm.bpf.passthrough.i32.i32(i32 2, i32 %4) + %conv2 = zext i32 %5 to i64, !dbg !29 + store volatile i64 %conv2, ptr @g, align 8, !dbg !30, !tbaa !23 + ret void, !dbg !31 +} + +; Function Attrs: nofree nosync nounwind memory(none) +declare i32 @llvm.bpf.passthrough.i32.i32(i32, i32) #2 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare void @llvm.dbg.value(metadata, metadata, metadata) #3 + +attributes #0 = { "btf_ama" } +attributes #1 = { nofree nounwind memory(readwrite, argmem: none) "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nofree nosync nounwind memory(none) } +attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!9, !10, !11, !12} +!llvm.ident = !{!13} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 4, type: !5, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "t.c", directory: "/home/eddy/work/tmp", checksumkind: CSK_MD5, checksum: "5bf218e82301e866fa302fd927913bcf") +!4 = !{!0} +!5 = !DIDerivedType(tag: DW_TAG_volatile_type, baseType: !6) +!6 = !DIBasicType(name: "unsigned long", size: 64, encoding: DW_ATE_unsigned) +!7 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "bar", file: !3, line: 3, elements: !8) +!8 = !{} +!9 = !{i32 7, !"Dwarf Version", i32 5} +!10 = !{i32 2, !"Debug Info Version", i32 3} +!11 = !{i32 1, !"wchar_size", i32 4} +!12 = !{i32 7, !"frame-pointer", i32 2} +!13 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 2f8c5c0afd1d79a771dd74c8fb1e5bbae6d04eb7)"} +!14 = distinct !DISubprogram(name: "root", scope: !3, file: !3, line: 5, type: !15, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !17) +!15 = !DISubroutineType(types: !16) +!16 = !{null} +!17 = !{!18} +!18 = !DILocalVariable(name: "bar", scope: !14, file: !3, line: 6, type: !19) +!19 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !7, size: 64) +!20 = !DILocation(line: 0, scope: !14) +!21 = !DILocation(line: 7, column: 7, scope: !14) +!22 = !DILocation(line: 7, column: 5, scope: !14) +!23 = !{!24, !24, i64 0} +!24 = !{!"long", !25, i64 0} +!25 = !{!"omnipotent char", !26, i64 0} +!26 = !{!"Simple C/C++ TBAA"} +!27 = !DILocation(line: 8, column: 7, scope: !14) +!28 = !DILocation(line: 8, column: 5, scope: !14) +!29 = !DILocation(line: 9, column: 7, scope: !14) +!30 = !DILocation(line: 9, column: 5, scope: !14) +!31 = !DILocation(line: 10, column: 1, scope: !14) diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp --- a/llvm/tools/llvm-objdump/llvm-objdump.cpp +++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp @@ -31,6 +31,7 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSet.h" #include "llvm/ADT/Twine.h" +#include "llvm/DebugInfo/BTF/BTFParser.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h" #include "llvm/DebugInfo/Symbolize/Symbolize.h" @@ -1267,6 +1268,20 @@ FOS.flush(); } +static void emitBTFReloc(formatted_raw_ostream &FOS, llvm::BTFParser &BTF, + object::SectionedAddress Address, + LiveVariablePrinter &LVP) { + llvm::BTF::BPFFieldReloc *Reloc = BTF.findFieldReloc(Address); + if (!Reloc) + return; + + FOS << (LeadingAddr ? "\t\t" : "\t"); + FOS << "CO-RE relo: " << BTF.symbolize(Reloc); + LVP.printAfterInst(FOS); + FOS << '\n'; + FOS.flush(); +} + static void createFakeELFSections(ObjectFile &Obj) { assert(Obj.isELF()); if (auto *Elf32LEObj = dyn_cast(&Obj)) @@ -1454,6 +1469,16 @@ if (SymbolizeOperands && !Obj.isRelocatableObject()) ReadBBAddrMap(); + std::unique_ptr BTF; + if (InlineRelocs && BTFParser::hasBTFSections(Obj)) { + BTF.reset(new llvm::BTFParser()); + BTFParser::ParseOptions Opts = {}; + Opts.LoadTypes = true; + Opts.LoadRelocs = true; + if (auto E = BTF->parse(Obj, Opts)) + WithColor::defaultErrorHandler(std::move(E)); + } + for (const SectionRef &Section : ToolSectionFilter(Obj)) { if (FilterSections.empty() && !DisassembleAll && (!Section.isText() || Section.isVirtual())) @@ -1946,6 +1971,9 @@ CommentStream.str(), LVP); Comments.clear(); + if (BTF) + emitBTFReloc(FOS, *BTF, {Index, Section.getIndex()}, LVP); + // Hexagon does this in pretty printer if (Obj.getArch() != Triple::hexagon) { // Print relocation for instruction and data. diff --git a/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp b/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp --- a/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp +++ b/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp @@ -215,13 +215,16 @@ EXPECT_TRUE(BTF.findLineInfo({0, 2})); } -static void parseError(MockData1 &Mock, const char *Message) { +static void parseError(ObjectFile &Obj, const char *Message) { BTFParser BTF; - auto Obj = Mock.makeObj(); - EXPECT_THAT_ERROR(BTF.parse(*Obj), + EXPECT_THAT_ERROR(BTF.parse(Obj), FailedWithMessage(testing::HasSubstr(Message))); } +static void parseError(MockData1 &Mock, const char *Message) { + parseError(*Mock.makeObj(), Message); +} + TEST(BTFParserTest, badBTFMagic) { MockData1 Mock; Mock.BTF.Header.Magic = 42; @@ -347,4 +350,693 @@ EXPECT_EQ(I2.LineSource, std::nullopt); } +#define KIND(Kind) ((Kind) << 24) + +template static void append(std::string &S, const T &What) { + S.append((const char *)&What, sizeof(What)); +} + +class MockData2 { + std::string Types; + std::string Strings; + std::string Relocs; + std::string Lines; + unsigned TotalTypes; + int LastRelocSecIdx; + unsigned NumRelocs; + int LastLineSecIdx; + unsigned NumLines; + +public: + MockData2() { reset(); } + + unsigned totalTypes() { return TotalTypes; } + + uint32_t addString(StringRef S) { + uint32_t Off = Strings.size(); + Strings.append(S.data(), S.size()); + Strings.append("\0", 1); + return Off; + }; + + uint32_t addType(const BTF::CommonType &Tp) { + append(Types, Tp); + return ++TotalTypes; + } + + template void addTail(const T &Tp) { append(Types, Tp); } + + void resetTypes() { + Types.resize(0); + TotalTypes = 0; + } + + void reset() { + Types.resize(0); + Strings.resize(0); + Relocs.resize(0); + Lines.resize(0); + TotalTypes = 0; + LastRelocSecIdx = -1; + NumRelocs = 0; + LastLineSecIdx = -1; + NumLines = 0; + } + + void finishtRelocSec() { + if (LastRelocSecIdx == -1) + return; + + BTF::SecFieldReloc *SecInfo = + (BTF::SecFieldReloc *)&Relocs[LastRelocSecIdx]; + SecInfo->NumFieldReloc = NumRelocs; + LastRelocSecIdx = -1; + NumRelocs = 0; + } + + void finishLineSec() { + if (LastLineSecIdx == -1) + return; + + BTF::SecLineInfo *SecInfo = (BTF::SecLineInfo *)&Lines[LastLineSecIdx]; + SecInfo->NumLineInfo = NumLines; + NumLines = 0; + LastLineSecIdx = -1; + } + + void addRelocSec(const BTF::SecFieldReloc &R) { + finishtRelocSec(); + LastRelocSecIdx = Relocs.size(); + append(Relocs, R); + } + + void addReloc(const BTF::BPFFieldReloc &R) { + append(Relocs, R); + ++NumRelocs; + } + + void addLinesSec(const BTF::SecLineInfo &R) { + finishLineSec(); + LastLineSecIdx = Lines.size(); + append(Lines, R); + } + + void addLine(const BTF::BPFLineInfo &R) { + append(Lines, R); + ++NumLines; + } + + std::unique_ptr makeObj() { + finishtRelocSec(); + finishLineSec(); + + BTF::Header BTFHeader = {}; + BTFHeader.Magic = BTF::MAGIC; + BTFHeader.Version = 1; + BTFHeader.HdrLen = sizeof(BTFHeader); + BTFHeader.StrOff = 0; + BTFHeader.StrLen = Strings.size(); + BTFHeader.TypeOff = Strings.size(); + BTFHeader.TypeLen = Types.size(); + + std::string BTFSec; + append(BTFSec, BTFHeader); + BTFSec.append(Strings); + BTFSec.append(Types); + + BTF::ExtHeader ExtHeader = {}; + ExtHeader.Magic = BTF::MAGIC; + ExtHeader.Version = 1; + ExtHeader.HdrLen = sizeof(ExtHeader); + ExtHeader.FieldRelocOff = 0; + ExtHeader.FieldRelocLen = Relocs.size() + sizeof(uint32_t); + ExtHeader.LineInfoOff = ExtHeader.FieldRelocLen; + ExtHeader.LineInfoLen = Lines.size() + sizeof(uint32_t); + + std::string ExtSec; + append(ExtSec, ExtHeader); + append(ExtSec, (uint32_t)sizeof(BTF::BPFFieldReloc)); + ExtSec.append(Relocs); + append(ExtSec, (uint32_t)sizeof(BTF::BPFLineInfo)); + ExtSec.append(Lines); + + SmallString<0> ObjStorage; + std::string YamlBuffer; + raw_string_ostream Yaml(YamlBuffer); + Yaml << R"( +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_BPF +Sections: + - Name: foo + Type: SHT_PROGBITS + Size: 0x80 + - Name: bar + Type: SHT_PROGBITS + Size: 0x80 + - Name: .BTF + Type: SHT_PROGBITS + Content: )" + << makeBinRef(BTFSec.data(), BTFSec.size()); + Yaml << R"( + - Name: .BTF.ext + Type: SHT_PROGBITS + Content: )" + << makeBinRef(ExtSec.data(), ExtSec.size()); + + return yaml::yaml2ObjectFile(ObjStorage, YamlBuffer, + [](const Twine &Err) { errs() << Err; }); + } +}; + +TEST(BTFParserTest, allTypeKinds) { + MockData2 D; + + // won't work on big-endian machine? + D.addType({D.addString("1"), KIND(BTF::BTF_KIND_INT), {4}}); + D.addTail((uint32_t)0); + D.addType({D.addString("2"), KIND(BTF::BTF_KIND_PTR), {1}}); + D.addType({D.addString("3"), KIND(BTF::BTF_KIND_ARRAY), {0}}); + D.addTail(BTF::BTFArray({1, 1, 2})); + D.addType({D.addString("4"), KIND(BTF::BTF_KIND_STRUCT) | 2, {8}}); + D.addTail(BTF::BTFMember({D.addString("a"), 1, 0})); + D.addTail(BTF::BTFMember({D.addString("b"), 1, 0})); + D.addType({D.addString("5"), KIND(BTF::BTF_KIND_UNION) | 3, {8}}); + D.addTail(BTF::BTFMember({D.addString("a"), 1, 0})); + D.addTail(BTF::BTFMember({D.addString("b"), 1, 0})); + D.addTail(BTF::BTFMember({D.addString("c"), 1, 0})); + D.addType({D.addString("6"), KIND(BTF::BTF_KIND_ENUM) | 2, {4}}); + D.addTail(BTF::BTFEnum({D.addString("U"), 1})); + D.addTail(BTF::BTFEnum({D.addString("V"), 2})); + D.addType({D.addString("7"), KIND(BTF::BTF_KIND_ENUM64) | 1, {4}}); + D.addTail(BTF::BTFEnum64({D.addString("W"), 0, 1})); + D.addType( + {D.addString("8"), BTF::FWD_UNION_FLAG | KIND(BTF::BTF_KIND_FWD), {0}}); + D.addType({D.addString("9"), KIND(BTF::BTF_KIND_TYPEDEF), {1}}); + D.addType({D.addString("10"), KIND(BTF::BTF_KIND_VOLATILE), {1}}); + D.addType({D.addString("11"), KIND(BTF::BTF_KIND_CONST), {1}}); + D.addType({D.addString("12"), KIND(BTF::BTF_KIND_RESTRICT), {1}}); + D.addType({D.addString("13"), KIND(BTF::BTF_KIND_FUNC_PROTO) | 1, {1}}); + D.addTail(BTF::BTFParam({D.addString("P"), 2})); + D.addType({D.addString("14"), KIND(BTF::BTF_KIND_FUNC), {13}}); + D.addType({D.addString("15"), KIND(BTF::BTF_KIND_VAR), {2}}); + D.addTail((uint32_t)0); + D.addType({D.addString("16"), KIND(BTF::BTF_KIND_DATASEC) | 3, {0}}); + D.addTail(BTF::BTFDataSec({1, 0, 4})); + D.addTail(BTF::BTFDataSec({1, 4, 4})); + D.addTail(BTF::BTFDataSec({1, 8, 4})); + D.addType({D.addString("17"), KIND(BTF::BTF_KIND_FLOAT), {4}}); + D.addType({D.addString("18"), KIND(BTF::BTF_KIND_DECL_TAG), {0}}); + D.addTail((uint32_t)-1); + D.addType({D.addString("19"), KIND(BTF::BTF_KIND_TYPE_TAG), {0}}); + + BTFParser BTF; + auto Obj = D.makeObj(); + auto Err = BTF.parse(*Obj); + EXPECT_FALSE(Err); + + EXPECT_EQ(D.totalTypes() + 1 /* +1 for void */, BTF.typesCount()); + for (unsigned Id = 1; Id < D.totalTypes() + 1; ++Id) { + const BTF::CommonType *Tp = BTF.findType(Id); + ASSERT_TRUE(Tp); + std::string IdBuf; + raw_string_ostream IdBufStream(IdBuf); + IdBufStream << Id; + EXPECT_EQ(BTF.findString(Tp->NameOff), IdBuf); + } +} + +TEST(BTFParserTest, incompleteTypes) { + MockData2 D; + auto IncompleteType = [&](const BTF::CommonType &Tp) { + D.resetTypes(); + D.addType(Tp); + parseError(*D.makeObj(), "Incomplete type definition in .BTF section"); + }; + + // All kinds that need tail + IncompleteType({D.addString("a"), KIND(BTF::BTF_KIND_INT), {4}}); + IncompleteType({D.addString("b"), KIND(BTF::BTF_KIND_ARRAY), {0}}); + IncompleteType({D.addString("c"), KIND(BTF::BTF_KIND_VAR), {0}}); + IncompleteType({D.addString("d"), KIND(BTF::BTF_KIND_DECL_TAG), {0}}); + + // All kinds with vlen + IncompleteType({D.addString("a"), KIND(BTF::BTF_KIND_STRUCT) | 2, {8}}); + IncompleteType({D.addString("b"), KIND(BTF::BTF_KIND_UNION) | 3, {8}}); + IncompleteType({D.addString("c"), KIND(BTF::BTF_KIND_ENUM) | 2, {4}}); + IncompleteType({D.addString("d"), KIND(BTF::BTF_KIND_ENUM64) | 1, {4}}); + IncompleteType({D.addString("e"), KIND(BTF::BTF_KIND_FUNC_PROTO) | 1, {1}}); + IncompleteType({D.addString("f"), KIND(BTF::BTF_KIND_DATASEC) | 3, {0}}); + + // An unexpected tail + D.resetTypes(); + D.addTail((uint32_t)0); + parseError(*D.makeObj(), "Incomplete type definition in .BTF section"); +} + +// Use macro to preserve line number in error message +#define SYMBOLIZE(SecAddr, Expected) \ + do { \ + BTF::BPFFieldReloc *R = BTF.findFieldReloc((SecAddr)); \ + ASSERT_TRUE(R); \ + EXPECT_EQ(BTF.symbolize(R), (Expected)); \ + } while (false) + +// Shorter name for initializers below +typedef SectionedAddress SA; + +TEST(BTFParserTest, typeRelocs) { + MockData2 D; + uint32_t EmptyString = D.addString(""); + // id 1: struct foo {} + // id 2: union bar; + // id 3: struct buz; + D.addType({D.addString("foo"), KIND(BTF::BTF_KIND_STRUCT), {0}}); + D.addType( + {D.addString("bar"), KIND(BTF::BTF_KIND_FWD) | BTF::FWD_UNION_FLAG, {0}}); + D.addType({D.addString("buz"), KIND(BTF::BTF_KIND_FWD), {0}}); + D.addRelocSec({D.addString("foo"), 7}); + // List of all possible correct type relocations for type id #1 + D.addReloc({0, 1, EmptyString, BTF::BTF_TYPE_ID_LOCAL}); + D.addReloc({8, 1, EmptyString, BTF::BTF_TYPE_ID_REMOTE}); + D.addReloc({16, 1, EmptyString, BTF::TYPE_EXISTENCE}); + D.addReloc({24, 1, EmptyString, BTF::TYPE_MATCH}); + D.addReloc({32, 1, EmptyString, BTF::TYPE_SIZE}); + // Forward declarations + D.addReloc({40, 2, EmptyString, BTF::TYPE_SIZE}); + D.addReloc({48, 3, EmptyString, BTF::TYPE_SIZE}); + // Incorrect type relocation: bad type id + D.addReloc({56, 42, EmptyString, BTF::TYPE_SIZE}); + + auto Obj = D.makeObj(); + BTFParser BTF; + auto E = BTF.parse(*Obj); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), "BTF_TYPE_ID_LOCAL [1] struct foo"); + SYMBOLIZE(SA({8, 1}), "BTF_TYPE_ID_REMOTE [1] struct foo"); + SYMBOLIZE(SA({16, 1}), "TYPE_EXISTENCE [1] struct foo"); + SYMBOLIZE(SA({24, 1}), "TYPE_MATCH [1] struct foo"); + SYMBOLIZE(SA({32, 1}), "TYPE_SIZE [1] struct foo"); + SYMBOLIZE(SA({40, 1}), "TYPE_SIZE [2] union bar"); + SYMBOLIZE(SA({48, 1}), "TYPE_SIZE [3] struct buz"); + SYMBOLIZE(SA({56, 1}), "TYPE_SIZE 42:: "); +} + +TEST(BTFParserTest, enumRelocs) { + MockData2 D; + // id 1: enum { U, V } + D.addType({D.addString("foo"), KIND(BTF::BTF_KIND_ENUM) | 2, {4}}); + D.addTail(BTF::BTFEnum({D.addString("U"), 1})); + D.addTail(BTF::BTFEnum({D.addString("V"), 2})); + // id 2: int + D.addType({D.addString("int"), KIND(BTF::BTF_KIND_INT), {4}}); + D.addTail((uint32_t)0); + // id 3: enum: uint64_t { A, B } + D.addType({D.addString("bar"), KIND(BTF::BTF_KIND_ENUM64) | 2, {8}}); + D.addTail(BTF::BTFEnum64({D.addString("A"), 1, 0})); + D.addTail(BTF::BTFEnum64({D.addString("B"), 2, 0})); + + D.addRelocSec({D.addString("foo"), 5}); + // An ok relocation accessing value #1: U + D.addReloc({0, 1, D.addString("0"), BTF::ENUM_VALUE_EXISTENCE}); + // An ok relocation accessing value #2: V + D.addReloc({8, 1, D.addString("1"), BTF::ENUM_VALUE}); + // Incorrect relocation: too many elements in string "1:0" + D.addReloc({16, 1, D.addString("1:0"), BTF::ENUM_VALUE}); + // Incorrect relocation: type id "2" is not an enum + D.addReloc({24, 2, D.addString("1"), BTF::ENUM_VALUE}); + // Incorrect relocation: value #42 does not exist for enum "foo" + D.addReloc({32, 1, D.addString("42"), BTF::ENUM_VALUE}); + // An ok relocation accessing value #1: A + D.addReloc({40, 3, D.addString("0"), BTF::ENUM_VALUE_EXISTENCE}); + // An ok relocation accessing value #2: B + D.addReloc({48, 3, D.addString("1"), BTF::ENUM_VALUE}); + + auto Obj = D.makeObj(); + BTFParser BTF; + auto E = BTF.parse(*Obj); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), "ENUM_VALUE_EXISTENCE [1] enum foo::U"); + SYMBOLIZE(SA({8, 1}), "ENUM_VALUE [1] enum foo::V"); + SYMBOLIZE(SA({16, 1}), + "ENUM_VALUE 1::1:0 "); + SYMBOLIZE(SA({24, 1}), + "ENUM_VALUE 2::1 "); + SYMBOLIZE(SA({32, 1}), "ENUM_VALUE 1::42 "); + SYMBOLIZE(SA({40, 1}), "ENUM_VALUE_EXISTENCE [3] enum bar::A"); + SYMBOLIZE(SA({48, 1}), "ENUM_VALUE [3] enum bar::B"); +} + +TEST(BTFParserTest, enumRelocsMods) { + MockData2 D; + // id 1: enum { U, V } + D.addType({D.addString("foo"), KIND(BTF::BTF_KIND_ENUM) | 2, {4}}); + D.addTail(BTF::BTFEnum({D.addString("U"), 1})); + D.addTail(BTF::BTFEnum({D.addString("V"), 2})); + // id 2: typedef enum foo a; + D.addType({D.addString("a"), KIND(BTF::BTF_KIND_TYPEDEF), {1}}); + // id 3: const enum foo + D.addType({D.addString(""), KIND(BTF::BTF_KIND_CONST), {1}}); + + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, 2, D.addString("0"), BTF::ENUM_VALUE}); + D.addReloc({8, 3, D.addString("1"), BTF::ENUM_VALUE}); + + auto Obj = D.makeObj(); + BTFParser BTF; + auto E = BTF.parse(*Obj); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), "ENUM_VALUE [2] typedef a::U"); + SYMBOLIZE(SA({8, 1}), "ENUM_VALUE [3] const enum foo::V"); +} + +TEST(BTFParserTest, fieldRelocs) { + MockData2 D; + // id 1: int + D.addType({D.addString("int"), KIND(BTF::BTF_KIND_INT), {4}}); + D.addTail((uint32_t)0); + // id 2: struct foo { int a; int b; } + D.addType({D.addString("foo"), KIND(BTF::BTF_KIND_STRUCT) | 2, {8}}); + D.addTail(BTF::BTFMember({D.addString("a"), 1, 0})); + D.addTail(BTF::BTFMember({D.addString("b"), 1, 0})); + // id 3: array of struct foo + D.addType({D.addString(""), KIND(BTF::BTF_KIND_ARRAY), {0}}); + D.addTail(BTF::BTFArray({2, 1, 2})); + // id 4: struct bar { struct foo u[2]; int v; } + D.addType({D.addString("bar"), KIND(BTF::BTF_KIND_STRUCT) | 2, {8}}); + D.addTail(BTF::BTFMember({D.addString("u"), 3, 0})); + D.addTail(BTF::BTFMember({D.addString("v"), 1, 0})); + // id 5: array with bad element type id + D.addType({D.addString(""), KIND(BTF::BTF_KIND_ARRAY), {0}}); + D.addTail(BTF::BTFArray({42, 1, 2})); + // id 6: struct buz { u[2]; v; } + D.addType({D.addString("bar"), KIND(BTF::BTF_KIND_STRUCT) | 2, {8}}); + D.addTail(BTF::BTFMember({D.addString("u"), 5, 0})); + D.addTail(BTF::BTFMember({D.addString("v"), 42, 0})); + + D.addRelocSec({D.addString("foo"), 0 /* patched automatically */}); + // All field relocations kinds for struct bar::v + D.addReloc({0, 4, D.addString("0:1"), BTF::FIELD_BYTE_OFFSET}); + D.addReloc({8, 4, D.addString("0:1"), BTF::FIELD_BYTE_SIZE}); + D.addReloc({16, 4, D.addString("0:1"), BTF::FIELD_EXISTENCE}); + D.addReloc({24, 4, D.addString("0:1"), BTF::FIELD_SIGNEDNESS}); + D.addReloc({32, 4, D.addString("0:1"), BTF::FIELD_LSHIFT_U64}); + D.addReloc({40, 4, D.addString("0:1"), BTF::FIELD_RSHIFT_U64}); + // Non-zero first idx + D.addReloc({48, 4, D.addString("7:1"), BTF::FIELD_BYTE_OFFSET}); + // Access through array and struct: struct bar::u[1].a + D.addReloc({56, 4, D.addString("0:0:1:0"), BTF::FIELD_BYTE_OFFSET}); + // Access through array and struct: struct bar::u[1].b + D.addReloc({64, 4, D.addString("0:0:1:1"), BTF::FIELD_BYTE_OFFSET}); + // Incorrect relocation: empty access string + D.addReloc({72, 4, D.addString(""), BTF::FIELD_BYTE_OFFSET}); + // Incorrect relocation: member index out of range (only two members in bar) + D.addReloc({80, 4, D.addString("0:2"), BTF::FIELD_BYTE_OFFSET}); + // Incorrect relocation: unknown element type id (buz::u[0] access) + D.addReloc({88, 6, D.addString("0:0:0"), BTF::FIELD_BYTE_OFFSET}); + // Incorrect relocation: unknown member type id (buz::v access) + D.addReloc({96, 6, D.addString("0:1:0"), BTF::FIELD_BYTE_OFFSET}); + // Incorrect relocation: non structural type in the middle of access string + // struct bar::v. + D.addReloc({104, 4, D.addString("0:1:0"), BTF::FIELD_BYTE_OFFSET}); + + auto Obj = D.makeObj(); + BTFParser BTF; + auto E = BTF.parse(*Obj); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), "FIELD_BYTE_OFFSET [4] struct bar::[0].v"); + SYMBOLIZE(SA({8, 1}), "FIELD_BYTE_SIZE [4] struct bar::[0].v"); + SYMBOLIZE(SA({16, 1}), "FIELD_EXISTENCE [4] struct bar::[0].v"); + SYMBOLIZE(SA({24, 1}), "FIELD_SIGNEDNESS [4] struct bar::[0].v"); + SYMBOLIZE(SA({32, 1}), "FIELD_LSHIFT_U64 [4] struct bar::[0].v"); + SYMBOLIZE(SA({40, 1}), "FIELD_RSHIFT_U64 [4] struct bar::[0].v"); + SYMBOLIZE(SA({48, 1}), "FIELD_BYTE_OFFSET [4] struct bar::[7].v"); + SYMBOLIZE(SA({56, 1}), "FIELD_BYTE_OFFSET [4] struct bar::[0].u[1].a"); + SYMBOLIZE(SA({64, 1}), "FIELD_BYTE_OFFSET [4] struct bar::[0].u[1].b"); + SYMBOLIZE(SA({72, 1}), "FIELD_BYTE_OFFSET 4:: "); + SYMBOLIZE(SA({80, 1}), "FIELD_BYTE_OFFSET 4::0:2 "); + SYMBOLIZE(SA({88, 1}), "FIELD_BYTE_OFFSET 6::0:0:0 "); + SYMBOLIZE(SA({96, 1}), "FIELD_BYTE_OFFSET 6::0:1:0 "); + SYMBOLIZE(SA({104, 1}), "FIELD_BYTE_OFFSET 4::0:1:0 "); +} + +TEST(BTFParserTest, fieldRelocsMods) { + MockData2 D; + // struct foo { + // int u; + // } + // typedef struct foo bar; + // struct buz { + // const bar v; + // } + // typedef buz quux; + // const volatile restrict quux ; + auto Int = D.addType({D.addString("int"), KIND(BTF::BTF_KIND_INT), {4}}); + D.addTail((uint32_t)0); + auto Foo = + D.addType({D.addString("foo"), KIND(BTF::BTF_KIND_STRUCT) | 1, {4}}); + D.addTail(BTF::BTFMember({D.addString("u"), Int, 0})); + auto Bar = + D.addType({D.addString("bar"), KIND(BTF::BTF_KIND_TYPEDEF), {Foo}}); + auto CBar = D.addType({D.addString("bar"), KIND(BTF::BTF_KIND_CONST), {Bar}}); + auto Buz = + D.addType({D.addString("buz"), KIND(BTF::BTF_KIND_STRUCT) | 1, {4}}); + D.addTail(BTF::BTFMember({D.addString("v"), CBar, 0})); + auto Quux = + D.addType({D.addString("quux"), KIND(BTF::BTF_KIND_TYPEDEF), {Buz}}); + auto RQuux = + D.addType({D.addString(""), KIND(BTF::BTF_KIND_RESTRICT), {Quux}}); + auto VRQuux = + D.addType({D.addString(""), KIND(BTF::BTF_KIND_VOLATILE), {RQuux}}); + auto CVRQuux = + D.addType({D.addString(""), KIND(BTF::BTF_KIND_CONST), {VRQuux}}); + + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, Bar, D.addString("0:0"), BTF::FIELD_BYTE_OFFSET}); + D.addReloc({8, CVRQuux, D.addString("0:0:0"), BTF::FIELD_BYTE_OFFSET}); + + auto Obj = D.makeObj(); + BTFParser BTF; + auto E = BTF.parse(*Obj); + EXPECT_FALSE(E); + + // Should show modifiers / name of typedef + SYMBOLIZE(SA({0, 1}), "FIELD_BYTE_OFFSET [3] typedef bar::[0].u"); + SYMBOLIZE( + SA({8, 1}), + "FIELD_BYTE_OFFSET [9] const volatile restrict typedef quux::[0].v.u"); +} + +TEST(BTFParserTest, relocTypeTagAndVoid) { + MockData2 D; + // __attribute__((type_tag("tag"))) void + auto Tag = D.addType({D.addString("tag"), KIND(BTF::BTF_KIND_TYPE_TAG), {0}}); + + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, Tag, D.addString(""), BTF::TYPE_EXISTENCE}); + D.addReloc({8, 0 /* void */, D.addString(""), BTF::TYPE_EXISTENCE}); + + auto Obj = D.makeObj(); + BTFParser BTF; + auto E = BTF.parse(*Obj); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), "TYPE_EXISTENCE [1] type_tag(\"tag\") void"); + SYMBOLIZE(SA({8, 1}), "TYPE_EXISTENCE [0] void"); +} + +TEST(BTFParserTest, longRelocModifiersCycle) { + MockData2 D; + + D.addType({D.addString(""), KIND(BTF::BTF_KIND_CONST), {1 /* ourselves */}}); + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, 1, D.addString(""), BTF::TYPE_EXISTENCE}); + + auto Obj = D.makeObj(); + BTFParser BTF; + auto E = BTF.parse(*Obj); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), "TYPE_EXISTENCE 1:: "); +} + +TEST(BTFParserTest, relocAnonFieldsAndTypes) { + MockData2 D; + + // struct { + // int :32; + // } v; + auto Int = D.addType({D.addString("int"), KIND(BTF::BTF_KIND_INT), {4}}); + D.addTail((uint32_t)0); + auto Anon = D.addType({D.addString(""), KIND(BTF::BTF_KIND_STRUCT) | 1, {4}}); + D.addTail(BTF::BTFMember({D.addString(""), Int, 0})); + + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, Anon, D.addString(""), BTF::TYPE_EXISTENCE}); + D.addReloc({8, Anon, D.addString("0:0"), BTF::FIELD_BYTE_OFFSET}); + + auto Obj = D.makeObj(); + BTFParser BTF; + auto E = BTF.parse(*Obj); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), "TYPE_EXISTENCE [2] struct "); + SYMBOLIZE(SA({8, 1}), "FIELD_BYTE_OFFSET [2] struct ::[0]."); +} + +TEST(BTFParserTest, miscBadRelos) { + MockData2 D; + + auto S = D.addType({D.addString("S"), KIND(BTF::BTF_KIND_STRUCT), {0}}); + + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, 0, D.addString(""), 777}); + D.addReloc({8, S, D.addString("abc"), BTF::FIELD_BYTE_OFFSET}); + D.addReloc({16, S, D.addString("0#"), BTF::FIELD_BYTE_OFFSET}); + + auto Obj = D.makeObj(); + BTFParser BTF; + auto E = BTF.parse(*Obj); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), " 0:: "); + SYMBOLIZE(SA({8, 1}), + "FIELD_BYTE_OFFSET 1::abc "); + SYMBOLIZE(SA({16, 1}), + "FIELD_BYTE_OFFSET 1::0# "); +} + +TEST(BTFParserTest, relocsMultipleSections) { + MockData2 D; + + auto S = D.addType({D.addString("S"), KIND(BTF::BTF_KIND_STRUCT), {0}}); + auto T = D.addType({D.addString("T"), KIND(BTF::BTF_KIND_STRUCT), {0}}); + + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, S, D.addString(""), BTF::TYPE_EXISTENCE}); + D.addReloc({8, S, D.addString(""), BTF::TYPE_EXISTENCE}); + + D.addRelocSec({D.addString("bar"), 0}); + D.addReloc({8, T, D.addString(""), BTF::TYPE_EXISTENCE}); + D.addReloc({16, T, D.addString(""), BTF::TYPE_EXISTENCE}); + + auto Obj = D.makeObj(); + BTFParser BTF; + auto E = BTF.parse(*Obj); + EXPECT_FALSE(E); + + EXPECT_TRUE(BTF.findFieldReloc({0, 1})); + EXPECT_TRUE(BTF.findFieldReloc({8, 1})); + EXPECT_FALSE(BTF.findFieldReloc({16, 1})); + + EXPECT_FALSE(BTF.findFieldReloc({0, 2})); + EXPECT_TRUE(BTF.findFieldReloc({8, 2})); + EXPECT_TRUE(BTF.findFieldReloc({16, 2})); + + EXPECT_FALSE(BTF.findFieldReloc({0, 3})); + EXPECT_FALSE(BTF.findFieldReloc({8, 3})); + EXPECT_FALSE(BTF.findFieldReloc({16, 3})); + + auto AssertReloType = [&](const SectionedAddress &A, const char *Name) { + auto *Relo = BTF.findFieldReloc(A); + ASSERT_TRUE(Relo); + auto *Type = BTF.findType(Relo->TypeID); + ASSERT_TRUE(Type); + ASSERT_EQ(BTF.findString(Type->NameOff), Name); + }; + + AssertReloType({8, 1}, "S"); + AssertReloType({8, 2}, "T"); +} + +TEST(BTFParserTest, parserResetReloAndTypes) { + BTFParser BTF; + MockData2 D; + + // First time: two types, two relos + D.addType({D.addString("foo"), KIND(BTF::BTF_KIND_STRUCT), {0}}); + D.addType({D.addString("bar"), KIND(BTF::BTF_KIND_STRUCT), {0}}); + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, 1, D.addString(""), BTF::TYPE_EXISTENCE}); + D.addReloc({8, 2, D.addString(""), BTF::TYPE_EXISTENCE}); + + auto Obj1 = D.makeObj(); + auto E1 = BTF.parse(*Obj1); + EXPECT_FALSE(E1); + + ASSERT_TRUE(BTF.findType(1)); + EXPECT_EQ(BTF.findString(BTF.findType(1)->NameOff), "foo"); + EXPECT_TRUE(BTF.findType(2)); + EXPECT_TRUE(BTF.findFieldReloc({0, 1})); + EXPECT_TRUE(BTF.findFieldReloc({8, 1})); + + // Second time: one type, one relo + D.reset(); + D.addType({D.addString("buz"), KIND(BTF::BTF_KIND_STRUCT), {0}}); + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, 1, D.addString(""), BTF::TYPE_EXISTENCE}); + + auto Obj2 = D.makeObj(); + auto E2 = BTF.parse(*Obj2); + EXPECT_FALSE(E2); + + ASSERT_TRUE(BTF.findType(1)); + EXPECT_EQ(BTF.findString(BTF.findType(1)->NameOff), "buz"); + EXPECT_FALSE(BTF.findType(2)); + EXPECT_TRUE(BTF.findFieldReloc({0, 1})); + EXPECT_FALSE(BTF.findFieldReloc({8, 1})); +} + +TEST(BTFParserTest, selectiveLoad) { + BTFParser BTF1, BTF2, BTF3; + MockData2 D; + + D.addType({D.addString("foo"), KIND(BTF::BTF_KIND_STRUCT), {0}}); + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, 1, D.addString(""), BTF::TYPE_EXISTENCE}); + D.addLinesSec({D.addString("foo"), 0}); + D.addLine({0, D.addString("file.c"), D.addString("some line"), LC(2, 3)}); + + BTFParser::ParseOptions Opts; + + auto Obj1 = D.makeObj(); + Opts = {}; + Opts.LoadLines = true; + auto E1 = BTF1.parse(*Obj1, Opts); + ASSERT_FALSE(E1); + + Opts = {}; + Opts.LoadTypes = true; + auto E2 = BTF2.parse(*Obj1, Opts); + ASSERT_FALSE(E2); + + Opts = {}; + Opts.LoadRelocs = true; + auto E3 = BTF3.parse(*Obj1, Opts); + ASSERT_FALSE(E3); + + ASSERT_TRUE(BTF1.findLineInfo({0, 1})); + ASSERT_FALSE(BTF2.findLineInfo({0, 1})); + ASSERT_FALSE(BTF3.findLineInfo({0, 1})); + + ASSERT_FALSE(BTF1.findType(1)); + ASSERT_TRUE(BTF2.findType(1)); + ASSERT_FALSE(BTF3.findType(1)); + + ASSERT_FALSE(BTF1.findFieldReloc({0, 1})); + ASSERT_FALSE(BTF2.findFieldReloc({0, 1})); + ASSERT_TRUE(BTF3.findFieldReloc({0, 1})); +} + } // namespace