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,7 +48,8 @@ #ifndef LLVM_LIB_TARGET_BPF_BTF_H #define LLVM_LIB_TARGET_BPF_BTF_H -#include +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/TrailingObjects.h" namespace llvm { namespace BTF { @@ -97,6 +98,10 @@ #include "BTF.def" }; +// Constants for CommonType::Info field. +constexpr uint32_t FWD_UNION_FLAG = 1u << 31; +constexpr uint32_t ENUM_SIGNED_FLAG = 1u << 31; + /// The BTF common type definition. Different kinds may have /// additional information after this structure data. struct CommonType { @@ -106,8 +111,8 @@ /// "Info" bits arrangement: /// Bits 0-15: vlen (e.g. # of struct's members) /// Bits 16-23: unused - /// Bits 24-27: kind (e.g. int, ptr, array...etc) - /// Bits 28-30: unused + /// Bits 24-28: kind (e.g. int, ptr, array...etc) + /// Bits 29-30: unused /// Bit 31: kind_flag, currently used by /// struct, union and fwd uint32_t Info; @@ -122,6 +127,9 @@ uint32_t Size; uint32_t Type; }; + + uint32_t getKind() const { return Info >> 24 & 0x1f; } + uint32_t getVlen() const { return Info & 0xffff; } }; // For some specific BTF_KIND, "struct CommonType" is immediately @@ -269,6 +277,84 @@ uint32_t NumFieldReloc; ///< Number of offset reloc's in this section }; +/// CO-RE relocation kind codes used in .BTF.ext section. +enum PatchableRelocKind : uint32_t { + FIELD_BYTE_OFFSET = 0, + FIELD_BYTE_SIZE, + FIELD_EXISTENCE, + FIELD_SIGNEDNESS, + FIELD_LSHIFT_U64, + FIELD_RSHIFT_U64, + BTF_TYPE_ID_LOCAL, + BTF_TYPE_ID_REMOTE, + TYPE_EXISTENCE, + TYPE_SIZE, + ENUM_VALUE_EXISTENCE, + ENUM_VALUE, + TYPE_MATCH, + MAX_FIELD_RELOC_KIND, +}; + +// Define a number of sub-types for CommonType, each with: +// - An accessor for a relevant "tail" information (data fields that +// follow the CommonType record in binary format). +// - A classof() definition based on CommonType::getKind() value to +// allow use with dyn_cast<>() function. + +// For CommonType sub-types that are followed by a single entry of +// some type in the binary format. +#define BTF_DEFINE_TAIL(Type, Accessor) \ + const Type &Accessor() const { return *getTrailingObjects(); } + +// For CommonType sub-types that are followed by CommonType::getVlen() +// number of entries of some type in the binary format. +#define BTF_DEFINE_TAIL_ARR(Type, Accessor) \ + ArrayRef Accessor() const { \ + return ArrayRef(getTrailingObjects(), getVlen()); \ + } + +struct ArrayType final : CommonType, + private TrailingObjects { + friend TrailingObjects; + BTF_DEFINE_TAIL(BTFArray, getArray) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_ARRAY; + } +}; + +struct StructType final : CommonType, + private TrailingObjects { + friend TrailingObjects; + BTF_DEFINE_TAIL_ARR(BTFMember, members) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_STRUCT || V->getKind() == BTF_KIND_UNION; + } +}; + +struct EnumType final : CommonType, private TrailingObjects { + friend TrailingObjects; + BTF_DEFINE_TAIL_ARR(BTFEnum, values) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_ENUM; + } +}; + +struct Enum64Type final : CommonType, + private TrailingObjects { + friend TrailingObjects; + BTF_DEFINE_TAIL_ARR(BTFEnum64, values) + + static bool classof(const CommonType *V) { + return V->getKind() == BTF_KIND_ENUM64; + } +}; + +#undef BTF_DEFINE_TAIL +#undef BTF_DEFINE_TAIL_ARR + } // End namespace BTF. } // End namespace llvm. diff --git a/llvm/include/llvm/DebugInfo/BTF/BTFParser.h b/llvm/include/llvm/DebugInfo/BTF/BTFParser.h --- a/llvm/include/llvm/DebugInfo/BTF/BTFParser.h +++ b/llvm/include/llvm/DebugInfo/BTF/BTFParser.h @@ -10,7 +10,9 @@ // BPF backend and provides introspection for the stored information. // Currently the following information is accessible: // - string table; -// - instruction offset to line information mapping. +// - instruction offset to line information mapping; +// - types table; +// - CO-RE relocations table. // // See llvm/DebugInfo/BTF/BTF.h for some details about binary format // and links to Linux Kernel documentation. @@ -32,6 +34,7 @@ class BTFParser { using BTFLinesVector = SmallVector; + using BTFRelocVector = SmallVector; // In BTF strings are stored as a continuous memory region with // individual strings separated by 0 bytes. Strings are identified @@ -39,15 +42,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 ~250MiB vmlinux + // image it is ~4MiB. + OwningArrayRef TypesBuffer; + // Maps ELF section number to instruction line number information. // Each BTFLinesVector is sorted by `InsnOffset` to allow fast lookups. DenseMap SectionLines; + // Maps ELF section number to CO-RE relocation information. + // Each BTFRelocVector is sorted by `InsnOffset` to allow fast lookups. + DenseMap 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 +83,34 @@ // owned by this class. const BTF::BPFLineInfo *findLineInfo(SectionedAddress Address) const; + // Search for CO-RE relocation information for a specific address. + // Return nullptr if no information found. + // If information is present, return a pointer to object + // owned by this class. + const BTF::BPFFieldReloc *findFieldReloc(SectionedAddress Address) const; + + // Return a human readable representation of the CO-RE relocation + // record, this is for display purpose only. + // See implementation for details. + void symbolize(const BTF::BPFFieldReloc *Reloc, + SmallVectorImpl &Result) const; + + // Lookup BTF type definition with a specific index. + // Return nullptr if no information found. + // If information is present, return a pointer to object + // owned by this class. + const BTF::CommonType *findType(uint32_t Id) const; + + // Return total number of known BTF types. + size_t typesCount() const { return Types.size(); } + + // Allow to selectively load BTF information. + struct ParseOptions { + bool LoadLines = false; + bool LoadTypes = false; + bool LoadRelocs = false; + }; + // Fills instance of BTFParser with information stored in .BTF and // .BTF.ext sections of the `Obj`. If this instance was already // filled, old data is discarded. @@ -70,7 +120,8 @@ // - state of the BTFParser might be incomplete but is not invalid, // queries might be run against it, but some (or all) information // might be unavailable; - Error parse(const ObjectFile &Obj); + Error parse(const ObjectFile &Obj, const ParseOptions &Opts); + Error parse(const ObjectFile &Obj) { return parse(Obj, {true, true, true}); } // Return true if `Obj` has .BTF and .BTF.ext sections. static bool hasBTFSections(const ObjectFile &Obj); diff --git a/llvm/lib/DebugInfo/BTF/BTFContext.cpp b/llvm/lib/DebugInfo/BTF/BTFContext.cpp --- a/llvm/lib/DebugInfo/BTF/BTFContext.cpp +++ b/llvm/lib/DebugInfo/BTF/BTFContext.cpp @@ -63,7 +63,9 @@ BTFContext::create(const ObjectFile &Obj, std::function ErrorHandler) { auto Ctx = std::make_unique(); - if (Error E = Ctx->BTF.parse(Obj)) + BTFParser::ParseOptions Opts; + Opts.LoadLines = true; + if (Error E = Ctx->BTF.parse(Obj, Opts)) ErrorHandler(std::move(E)); return Ctx; } diff --git a/llvm/lib/DebugInfo/BTF/BTFParser.cpp b/llvm/lib/DebugInfo/BTF/BTFParser.cpp --- a/llvm/lib/DebugInfo/BTF/BTFParser.cpp +++ b/llvm/lib/DebugInfo/BTF/BTFParser.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "llvm/DebugInfo/BTF/BTFParser.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/Errc.h" #define DEBUG_TYPE "debug-info-btf-parser" @@ -74,11 +75,13 @@ // Used by BTFParser::parse* auxiliary functions. struct BTFParser::ParseContext { const ObjectFile &Obj; + const ParseOptions &Opts; // Map from ELF section name to SectionRef DenseMap Sections; public: - ParseContext(const ObjectFile &Obj) : Obj(Obj) {} + ParseContext(const ObjectFile &Obj, const ParseOptions &Opts) + : Obj(Obj), Opts(Opts) {} Expected makeExtractor(SectionRef Sec) { Expected Contents = Sec.getContents(); @@ -119,19 +122,128 @@ return Err(".BTF", C); if (HdrLen < 8) return Err("unexpected .BTF header length: ") << HdrLen; - (void)Extractor.getU32(C); // type_off - (void)Extractor.getU32(C); // type_len + uint32_t TypeOff = Extractor.getU32(C); + uint32_t TypeLen = Extractor.getU32(C); uint32_t StrOff = Extractor.getU32(C); uint32_t StrLen = Extractor.getU32(C); uint32_t StrStart = HdrLen + StrOff; uint32_t StrEnd = StrStart + StrLen; + uint32_t TypesInfoStart = HdrLen + TypeOff; + uint32_t TypesInfoEnd = TypesInfoStart + TypeLen; + uint32_t BytesExpected = std::max(StrEnd, TypesInfoEnd); if (!C) return Err(".BTF", C); - if (Extractor.getData().size() < StrEnd) + if (Extractor.getData().size() < BytesExpected) return Err("invalid .BTF section size, expecting at-least ") - << StrEnd << " bytes"; + << BytesExpected << " bytes"; + + StringsTable = Extractor.getData().slice(StrStart, StrEnd); + + if (TypeLen > 0 && Ctx.Opts.LoadTypes) { + StringRef RawData = Extractor.getData().slice(TypesInfoStart, TypesInfoEnd); + if (Error E = parseTypesInfo(Ctx, TypesInfoStart, RawData)) + return E; + } + + return Error::success(); +} + +// Compute record size for each BTF::CommonType sub-type +// (including entries in the tail position). +static size_t byteSize(BTF::CommonType *Type) { + size_t Size = sizeof(BTF::CommonType); + switch (Type->getKind()) { + case BTF::BTF_KIND_INT: + Size += sizeof(uint32_t); + break; + case BTF::BTF_KIND_ARRAY: + Size += sizeof(BTF::BTFArray); + break; + case BTF::BTF_KIND_VAR: + Size += sizeof(uint32_t); + break; + case BTF::BTF_KIND_DECL_TAG: + Size += sizeof(uint32_t); + break; + case BTF::BTF_KIND_STRUCT: + case BTF::BTF_KIND_UNION: + Size += sizeof(BTF::BTFMember) * Type->getVlen(); + break; + case BTF::BTF_KIND_ENUM: + Size += sizeof(BTF::BTFEnum) * Type->getVlen(); + break; + case BTF::BTF_KIND_ENUM64: + Size += sizeof(BTF::BTFEnum64) * Type->getVlen(); + break; + case BTF::BTF_KIND_FUNC_PROTO: + Size += sizeof(BTF::BTFParam) * Type->getVlen(); + break; + case BTF::BTF_KIND_DATASEC: + Size += sizeof(BTF::BTFDataSec) * Type->getVlen(); + break; + } + return Size; +} + +// Guard value for voids, simplifies code a bit, but NameOff is not +// actually valid. +const BTF::CommonType VoidTypeInst = {0, BTF::BTF_KIND_UNKN << 24, {0}}; + +// Type information "parsing" is very primitive: +// - The `RawData` is copied to a buffer owned by `BTFParser` instance. +// - The buffer is treated as an array of `uint32_t` values, each value +// is swapped to use native endianness. This is possible, because +// according to BTF spec all buffer elements are structures comprised +// of `uint32_t` fields. +// - `BTFParser::Types` vector is filled with pointers to buffer +// elements, using `byteSize()` function to slice the buffer at type +// record boundaries. +// - If at some point a type definition with incorrect size (logical size +// exceeding buffer boundaries) is reached it is not added to the +// `BTFParser::Types` vector and the process stops. +Error BTFParser::parseTypesInfo(ParseContext &Ctx, uint64_t TypesInfoStart, + StringRef RawData) { + using support::big; + using support::endianness; + using support::little; + using support::endian::byte_swap; + + TypesBuffer = OwningArrayRef(arrayRefFromStringRef(RawData)); + // Switch endianness if necessary. + endianness Endianness = Ctx.Obj.isLittleEndian() ? little : big; + uint32_t *TypesBuffer32 = (uint32_t *)TypesBuffer.data(); + for (uint64_t I = 0; I < TypesBuffer.size() / 4; ++I) + TypesBuffer32[I] = byte_swap(TypesBuffer32[I], Endianness); + + // The type id 0 is reserved for void type. + Types.push_back(&VoidTypeInst); + + uint64_t Pos = 0; + while (Pos < RawData.size()) { + uint64_t BytesLeft = RawData.size() - Pos; + uint64_t Offset = TypesInfoStart + Pos; + BTF::CommonType *Type = (BTF::CommonType *)&TypesBuffer[Pos]; + if (BytesLeft < sizeof(*Type)) + return Err("incomplete type definition in .BTF section:") + << " offset " << Offset << ", index " << Types.size(); + + uint64_t Size = byteSize(Type); + if (BytesLeft < Size) + return Err("incomplete type definition in .BTF section:") + << " offset=" << Offset << ", index=" << Types.size() + << ", vlen=" << Type->getVlen(); + + LLVM_DEBUG({ + llvm::dbgs() << "Adding BTF type:\n" + << " Id = " << Types.size() << "\n" + << " Kind = " << Type->getKind() << "\n" + << " Name = " << findString(Type->NameOff) << "\n" + << " Record Size = " << Size << "\n"; + }); + Types.push_back(Type); + Pos += Size; + } - StringsTable = Extractor.getData().substr(StrStart, StrLen); return Error::success(); } @@ -162,12 +274,24 @@ (void)Extractor.getU32(C); // func_info_len uint32_t LineInfoOff = Extractor.getU32(C); uint32_t LineInfoLen = Extractor.getU32(C); + uint32_t RelocInfoOff = Extractor.getU32(C); + uint32_t RelocInfoLen = Extractor.getU32(C); if (!C) return Err(".BTF.ext", C); - uint32_t LineInfoStart = HdrLen + LineInfoOff; - uint32_t LineInfoEnd = LineInfoStart + LineInfoLen; - if (Error E = parseLineInfo(Ctx, Extractor, LineInfoStart, LineInfoEnd)) - return E; + + if (LineInfoLen > 0 && Ctx.Opts.LoadLines) { + uint32_t LineInfoStart = HdrLen + LineInfoOff; + uint32_t LineInfoEnd = LineInfoStart + LineInfoLen; + if (Error E = parseLineInfo(Ctx, Extractor, LineInfoStart, LineInfoEnd)) + return E; + } + + if (RelocInfoLen > 0 && Ctx.Opts.LoadRelocs) { + uint32_t RelocInfoStart = HdrLen + RelocInfoOff; + uint32_t RelocInfoEnd = RelocInfoStart + RelocInfoLen; + if (Error E = parseRelocInfo(Ctx, Extractor, RelocInfoStart, RelocInfoEnd)) + return E; + } return Error::success(); } @@ -214,11 +338,52 @@ return Error::success(); } -Error BTFParser::parse(const ObjectFile &Obj) { +Error BTFParser::parseRelocInfo(ParseContext &Ctx, DataExtractor &Extractor, + uint64_t RelocInfoStart, + uint64_t RelocInfoEnd) { + DataExtractor::Cursor C = DataExtractor::Cursor(RelocInfoStart); + uint32_t RecSize = Extractor.getU32(C); + if (!C) + return Err(".BTF.ext", C); + if (RecSize < 16) + return Err("unexpected .BTF.ext field reloc info record length: ") + << RecSize; + while (C && C.tell() < RelocInfoEnd) { + uint32_t SecNameOff = Extractor.getU32(C); + uint32_t NumInfo = Extractor.getU32(C); + StringRef SecName = findString(SecNameOff); + std::optional Sec = Ctx.findSection(SecName); + BTFRelocVector &Relocs = SectionRelocs[Sec->getIndex()]; + for (uint32_t I = 0; C && I < NumInfo; ++I) { + uint64_t RecStart = C.tell(); + uint32_t InsnOff = Extractor.getU32(C); + uint32_t TypeID = Extractor.getU32(C); + uint32_t OffsetNameOff = Extractor.getU32(C); + uint32_t RelocKind = Extractor.getU32(C); + if (!C) + return Err(".BTF.ext", C); + Relocs.push_back({InsnOff, TypeID, OffsetNameOff, RelocKind}); + C.seek(RecStart + RecSize); + } + llvm::stable_sort( + Relocs, [](const BTF::BPFFieldReloc &L, const BTF::BPFFieldReloc &R) { + return L.InsnOffset < R.InsnOffset; + }); + } + if (!C) + return Err(".BTF.ext", C); + + return Error::success(); +} + +Error BTFParser::parse(const ObjectFile &Obj, const ParseOptions &Opts) { StringsTable = StringRef(); SectionLines.clear(); + SectionRelocs.clear(); + Types.clear(); + TypesBuffer = OwningArrayRef(); - ParseContext Ctx(Obj); + ParseContext Ctx(Obj, Opts); std::optional BTF; std::optional BTFExt; for (SectionRef Sec : Obj.sections()) { @@ -264,20 +429,430 @@ return StringsTable.slice(Offset, StringsTable.find(0, Offset)); } -const BTF::BPFLineInfo * -BTFParser::findLineInfo(SectionedAddress Address) const { - auto MaybeSecInfo = SectionLines.find(Address.SectionIndex); - if (MaybeSecInfo == SectionLines.end()) +template +static const T *findInfo(const DenseMap> &SecMap, + SectionedAddress Address) { + auto MaybeSecInfo = SecMap.find(Address.SectionIndex); + if (MaybeSecInfo == SecMap.end()) return nullptr; - const BTFLinesVector &SecInfo = MaybeSecInfo->second; + const SmallVector &SecInfo = MaybeSecInfo->second; const uint64_t TargetOffset = Address.Address; - BTFLinesVector::const_iterator LineInfo = - llvm::partition_point(SecInfo, [=](const BTF::BPFLineInfo &Line) { - return Line.InsnOffset < TargetOffset; - }); - if (LineInfo == SecInfo.end() || LineInfo->InsnOffset != Address.Address) + typename SmallVector::const_iterator MaybeInfo = llvm::partition_point( + SecInfo, [=](const T &Entry) { return Entry.InsnOffset < TargetOffset; }); + if (MaybeInfo == SecInfo.end() || MaybeInfo->InsnOffset != Address.Address) return nullptr; - return LineInfo; + return &*MaybeInfo; +} + +const BTF::BPFLineInfo * +BTFParser::findLineInfo(SectionedAddress Address) const { + return findInfo(SectionLines, Address); +} + +const BTF::BPFFieldReloc * +BTFParser::findFieldReloc(SectionedAddress Address) const { + return findInfo(SectionRelocs, Address); +} + +const BTF::CommonType *BTFParser::findType(uint32_t Id) const { + if (Id < Types.size()) + return Types[Id]; + return nullptr; +} + +enum RelocKindGroup { + RKG_FIELD, + RKG_TYPE, + RKG_ENUMVAL, + RKG_UNKNOWN, +}; + +static RelocKindGroup relocKindGroup(const BTF::BPFFieldReloc *Reloc) { + switch (Reloc->RelocKind) { + case BTF::FIELD_BYTE_OFFSET: + case BTF::FIELD_BYTE_SIZE: + case BTF::FIELD_EXISTENCE: + case BTF::FIELD_SIGNEDNESS: + case BTF::FIELD_LSHIFT_U64: + case BTF::FIELD_RSHIFT_U64: + return RKG_FIELD; + case BTF::BTF_TYPE_ID_LOCAL: + case BTF::BTF_TYPE_ID_REMOTE: + case BTF::TYPE_EXISTENCE: + case BTF::TYPE_MATCH: + case BTF::TYPE_SIZE: + return RKG_TYPE; + case BTF::ENUM_VALUE_EXISTENCE: + case BTF::ENUM_VALUE: + return RKG_ENUMVAL; + default: + return RKG_UNKNOWN; + } +} + +static bool isMod(const BTF::CommonType *Type) { + switch (Type->getKind()) { + case BTF::BTF_KIND_VOLATILE: + case BTF::BTF_KIND_CONST: + case BTF::BTF_KIND_RESTRICT: + case BTF::BTF_KIND_TYPE_TAG: + return true; + default: + return false; + } +} + +static bool printMod(const BTFParser &BTF, const BTF::CommonType *Type, + raw_ostream &Stream) { + switch (Type->getKind()) { + case BTF::BTF_KIND_CONST: + Stream << " const"; + break; + case BTF::BTF_KIND_VOLATILE: + Stream << " volatile"; + break; + case BTF::BTF_KIND_RESTRICT: + Stream << " restrict"; + break; + case BTF::BTF_KIND_TYPE_TAG: + Stream << " type_tag(\"" << BTF.findString(Type->NameOff) << "\")"; + break; + default: + return false; + } + return true; +} + +static const BTF::CommonType *skipModsAndTypedefs(const BTFParser &BTF, + const BTF::CommonType *Type) { + while (isMod(Type) || Type->getKind() == BTF::BTF_KIND_TYPEDEF) { + auto *Base = BTF.findType(Type->Type); + if (!Base) + break; + Type = Base; + } + return Type; +} + +namespace { +struct StrOrAnon { + const BTFParser &BTF; + uint32_t Offset; + uint32_t Idx; +}; + +static raw_ostream &operator<<(raw_ostream &Stream, const StrOrAnon &S) { + StringRef Str = S.BTF.findString(S.Offset); + if (Str.empty()) + Stream << ""; + else + Stream << Str; + return Stream; +} +} // anonymous namespace + +static void relocKindName(uint32_t X, raw_ostream &Out) { + Out << "<"; + switch (X) { + default: + Out << "reloc kind #" << X; + break; + case BTF::FIELD_BYTE_OFFSET: + Out << "byte_off"; + break; + case BTF::FIELD_BYTE_SIZE: + Out << "byte_sz"; + break; + case BTF::FIELD_EXISTENCE: + Out << "field_exists"; + break; + case BTF::FIELD_SIGNEDNESS: + Out << "signed"; + break; + case BTF::FIELD_LSHIFT_U64: + Out << "lshift_u64"; + break; + case BTF::FIELD_RSHIFT_U64: + Out << "rshift_u64"; + break; + case BTF::BTF_TYPE_ID_LOCAL: + Out << "local_type_id"; + break; + case BTF::BTF_TYPE_ID_REMOTE: + Out << "target_type_id"; + break; + case BTF::TYPE_EXISTENCE: + Out << "type_exists"; + break; + case BTF::TYPE_MATCH: + Out << "type_matches"; + break; + case BTF::TYPE_SIZE: + Out << "type_size"; + break; + case BTF::ENUM_VALUE_EXISTENCE: + Out << "enumval_exists"; + break; + case BTF::ENUM_VALUE: + Out << "enumval_value"; + break; + } + Out << ">"; +} + +// Produces a human readable description of a CO-RE relocation. +// Such relocations are generated by BPF backend, and processed +// by libbpf's BPF program loader [1]. +// +// Each relocation record has the following information: +// - Relocation kind; +// - BTF type ID; +// - Access string offset in string table. +// +// There are 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: +// - " [7] struct foo" +// - " [7] struct foo" +// +// - load-time information about enums (literal existence, literal value), +// `BTFParser::symbolize()` output for such relocations uses the template: +// +// [] :: = +// +// For example: +// - " [5] enum foo::U = 1" +// - " [5] enum foo::V = 2" +// +// - load-time information about fields (e.g. field offset), +// `BTFParser::symbolize()` output for such relocations uses the template: +// +// [] \ +// ::[N].... \ +// () +// +// For example: +// - " [8] struct bar::[7].v (7:1)" +// - " [8] struct bar::v (0:1)" +// +// If relocation description is not valid output follows the following pattern: +// +// :: <> +// +// For example: +// +// - " [42] '' " +// - " [4] '0:' " +// +// Additional examples could be found in unit tests, see +// llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp. +// +// [1] https://www.kernel.org/doc/html/latest/bpf/libbpf/index.html +void BTFParser::symbolize(const BTF::BPFFieldReloc *Reloc, + SmallVectorImpl &Result) const { + raw_svector_ostream Stream(Result); + StringRef FullSpecStr = findString(Reloc->OffsetNameOff); + SmallVector RawSpec; + + auto Fail = [&](auto Msg) { + Result.resize(0); + relocKindName(Reloc->RelocKind, Stream); + Stream << " [" << Reloc->TypeID << "] '" << FullSpecStr << "'" + << " <" << Msg << ">"; + }; + + // Relocation access string follows pattern [0-9]+(:[0-9]+)*, + // e.g.: 12:22:3. Code below splits `SpecStr` by ':', parses + // numbers, and pushes them to `RawSpec`. + StringRef SpecStr = FullSpecStr; + while (SpecStr.size()) { + unsigned long long Val; + if (consumeUnsignedInteger(SpecStr, 10, Val)) + return Fail("spec string is not a number"); + RawSpec.push_back(Val); + if (SpecStr.empty()) + break; + if (SpecStr[0] != ':') + return Fail(format("unexpected spec string delimiter: '%c'", SpecStr[0])); + SpecStr = SpecStr.substr(1); + } + + // Print relocation kind to `Stream`. + relocKindName(Reloc->RelocKind, Stream); + + uint32_t CurId = Reloc->TypeID; + const BTF::CommonType *Type = findType(CurId); + if (!Type) + return Fail(format("unknown type id: %d", CurId)); + + Stream << " [" << CurId << "]"; + + // `Type` might have modifiers, e.g. for type 'const int' the `Type` + // would refer to BTF type of kind BTF_KIND_CONST. + // Print all these modifiers to `Stream`. + for (uint32_t ChainLen = 0; printMod(*this, Type, Stream); ++ChainLen) { + if (ChainLen >= 32) + return Fail("modifiers chain is too long"); + + CurId = Type->Type; + const BTF::CommonType *NextType = findType(CurId); + if (!NextType) + return Fail(format("unknown type id: %d in modifiers chain", CurId)); + Type = NextType; + } + // Print the type name to `Stream`. + if (CurId == 0) { + Stream << " void"; + } else { + switch (Type->getKind()) { + case BTF::BTF_KIND_TYPEDEF: + Stream << " typedef"; + break; + case BTF::BTF_KIND_STRUCT: + Stream << " struct"; + break; + case BTF::BTF_KIND_UNION: + Stream << " union"; + break; + case BTF::BTF_KIND_ENUM: + Stream << " enum"; + break; + case BTF::BTF_KIND_ENUM64: + Stream << " enum"; + break; + case BTF::BTF_KIND_FWD: + if (Type->Info & BTF::FWD_UNION_FLAG) + Stream << " fwd union"; + else + Stream << " fwd struct"; + break; + default: + break; + } + Stream << " " << StrOrAnon({*this, Type->NameOff, CurId}); + } + + RelocKindGroup Group = relocKindGroup(Reloc); + // Type-based relocations don't use access string but clang backend + // generates '0' and libbpf checks it's value, do the same here. + if (Group == RKG_TYPE) { + if (RawSpec.size() != 1 || RawSpec[0] != 0) + return Fail("unexpected type-based relocation spec: should be '0'"); + return; + } + + Stream << "::"; + + // For enum-based relocations access string is a single number, + // corresponding to the enum literal sequential number. + // E.g. for `enum E { U, V }`, relocation requesting value of `V` + // would look as follows: + // - kind: BTF::ENUM_VALUE + // - BTF id: id for `E` + // - access string: "1" + if (Group == RKG_ENUMVAL) { + Type = skipModsAndTypedefs(*this, Type); + + if (RawSpec.size() != 1) + return Fail("unexpected enumval relocation spec size"); + + uint32_t NameOff; + uint64_t Val; + uint32_t Idx = RawSpec[0]; + if (auto *T = dyn_cast(Type)) { + if (T->values().size() <= Idx) + return Fail(format("bad value index: %d", Idx)); + const BTF::BTFEnum &E = T->values()[Idx]; + NameOff = E.NameOff; + Val = E.Val; + } else if (auto *T = dyn_cast(Type)) { + if (T->values().size() <= Idx) + return Fail(format("bad value index: %d", Idx)); + const BTF::BTFEnum64 &E = T->values()[Idx]; + NameOff = E.NameOff; + Val = (uint64_t)E.Val_Hi32 << 32u | E.Val_Lo32; + } else { + return Fail(format("unexpected type kind for enum relocation: %d", + Type->getKind())); + } + + Stream << StrOrAnon({*this, NameOff, Idx}); + if (Type->Info & BTF::ENUM_SIGNED_FLAG) + Stream << " = " << (int64_t)Val; + else + Stream << " = " << (uint64_t)Val; + return; + } + + // For type-based relocations access string is an array of numbers, + // which resemble index parameters for `getelementptr` LLVM IR instruction. + // E.g. for the following types: + // + // struct foo { + // int a; + // int b; + // }; + // struct bar { + // int u; + // struct foo v[7]; + // }; + // + // Relocation requesting `offsetof(struct bar, v[2].b)` will have + // the following access string: 0:1:2:1 + // ^ ^ ^ ^ + // | | | | + // initial index | | field 'b' is a field #1 + // | | (counting from 0) + // | array index #2 + // field 'v' is a field #1 + // (counting from 0) + if (Group == RKG_FIELD) { + if (RawSpec.size() < 1) + return Fail("field spec too short"); + + if (RawSpec[0] != 0) + Stream << "[" << RawSpec[0] << "]"; + for (uint32_t I = 1; I < RawSpec.size(); ++I) { + Type = skipModsAndTypedefs(*this, Type); + uint32_t Idx = RawSpec[I]; + + if (auto *T = dyn_cast(Type)) { + if (T->getVlen() <= Idx) + return Fail( + format("member index %d for spec sub-string %d is out of range", + Idx, I)); + + const BTF::BTFMember &Member = T->members()[Idx]; + if (I != 1 || RawSpec[0] != 0) + Stream << "."; + Stream << StrOrAnon({*this, Member.NameOff, Idx}); + Type = findType(Member.Type); + if (!Type) + return Fail(format("unknown member type id %d for spec sub-string %d", + Member.Type, I)); + } else if (auto *T = dyn_cast(Type)) { + Stream << "[" << Idx << "]"; + Type = findType(T->getArray().ElemType); + if (!Type) + return Fail( + format("unknown element type id %d for spec sub-string %d", + T->getArray().ElemType, I)); + } else { + return Fail(format("unexpected type kind %d for spec sub-string %d", + Type->getKind(), I)); + } + } + + Stream << " (" << FullSpecStr << ")"; + return; + } + + return Fail(format("unknown relocation kind: %d", Reloc->RelocKind)); } diff --git a/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp b/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp --- a/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp +++ b/llvm/lib/Target/BPF/BPFAbstractMemberAccess.cpp @@ -78,6 +78,7 @@ #include "BPFCORE.h" #include "BPFTargetMachine.h" #include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/DebugInfo/BTF/BTF.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/GlobalVariable.h" #include "llvm/IR/Instruction.h" @@ -369,7 +370,7 @@ CInfo.Metadata = nullptr; // Check validity of info_kind as clang did not check this. uint64_t InfoKind = getConstant(Call->getArgOperand(1)); - if (InfoKind >= BPFCoreSharedInfo::MAX_FIELD_RELOC_KIND) + if (InfoKind >= BTF::MAX_FIELD_RELOC_KIND) report_fatal_error("Incorrect info_kind for llvm.bpf.preserve.field.info intrinsic"); CInfo.AccessIndex = InfoKind; return true; @@ -383,11 +384,11 @@ if (Flag >= BPFCoreSharedInfo::MAX_PRESERVE_TYPE_INFO_FLAG) report_fatal_error("Incorrect flag for llvm.bpf.preserve.type.info intrinsic"); if (Flag == BPFCoreSharedInfo::PRESERVE_TYPE_INFO_EXISTENCE) - CInfo.AccessIndex = BPFCoreSharedInfo::TYPE_EXISTENCE; + CInfo.AccessIndex = BTF::TYPE_EXISTENCE; else if (Flag == BPFCoreSharedInfo::PRESERVE_TYPE_INFO_MATCH) - CInfo.AccessIndex = BPFCoreSharedInfo::TYPE_MATCH; + CInfo.AccessIndex = BTF::TYPE_MATCH; else - CInfo.AccessIndex = BPFCoreSharedInfo::TYPE_SIZE; + CInfo.AccessIndex = BTF::TYPE_SIZE; return true; } if (GV->getName().startswith("llvm.bpf.preserve.enum.value")) { @@ -399,9 +400,9 @@ if (Flag >= BPFCoreSharedInfo::MAX_PRESERVE_ENUM_VALUE_FLAG) report_fatal_error("Incorrect flag for llvm.bpf.preserve.enum.value intrinsic"); if (Flag == BPFCoreSharedInfo::PRESERVE_ENUM_VALUE_EXISTENCE) - CInfo.AccessIndex = BPFCoreSharedInfo::ENUM_VALUE_EXISTENCE; + CInfo.AccessIndex = BTF::ENUM_VALUE_EXISTENCE; else - CInfo.AccessIndex = BPFCoreSharedInfo::ENUM_VALUE; + CInfo.AccessIndex = BTF::ENUM_VALUE; return true; } @@ -672,11 +673,11 @@ uint32_t AccessIndex, uint32_t PatchImm, MaybeAlign RecordAlignment) { - if (InfoKind == BPFCoreSharedInfo::FIELD_EXISTENCE) - return 1; + if (InfoKind == BTF::FIELD_EXISTENCE) + return 1; uint32_t Tag = CTy->getTag(); - if (InfoKind == BPFCoreSharedInfo::FIELD_BYTE_OFFSET) { + if (InfoKind == BTF::FIELD_BYTE_OFFSET) { if (Tag == dwarf::DW_TAG_array_type) { auto *EltTy = stripQualifiers(CTy->getBaseType()); PatchImm += AccessIndex * calcArraySize(CTy, 1) * @@ -695,7 +696,7 @@ return PatchImm; } - if (InfoKind == BPFCoreSharedInfo::FIELD_BYTE_SIZE) { + if (InfoKind == BTF::FIELD_BYTE_SIZE) { if (Tag == dwarf::DW_TAG_array_type) { auto *EltTy = stripQualifiers(CTy->getBaseType()); return calcArraySize(CTy, 1) * (EltTy->getSizeInBits() >> 3); @@ -715,7 +716,7 @@ } } - if (InfoKind == BPFCoreSharedInfo::FIELD_SIGNEDNESS) { + if (InfoKind == BTF::FIELD_SIGNEDNESS) { const DIType *BaseTy; if (Tag == dwarf::DW_TAG_array_type) { // Signedness only checked when final array elements are accessed. @@ -741,7 +742,7 @@ return (Encoding == dwarf::DW_ATE_signed || Encoding == dwarf::DW_ATE_signed_char); } - if (InfoKind == BPFCoreSharedInfo::FIELD_LSHIFT_U64) { + if (InfoKind == BTF::FIELD_LSHIFT_U64) { // The value is loaded into a value with FIELD_BYTE_SIZE size, // and then zero or sign extended to U64. // FIELD_LSHIFT_U64 and FIELD_RSHIFT_U64 are operations @@ -778,7 +779,7 @@ return OffsetInBits + 64 - NextSBitOffset; } - if (InfoKind == BPFCoreSharedInfo::FIELD_RSHIFT_U64) { + if (InfoKind == BTF::FIELD_RSHIFT_U64) { DIDerivedType *MemberTy = nullptr; bool IsBitField = false; uint32_t SizeInBits; @@ -849,7 +850,7 @@ // we will skip them. uint32_t FirstIndex = 0; uint32_t PatchImm = 0; // AccessOffset or the requested field info - uint32_t InfoKind = BPFCoreSharedInfo::FIELD_BYTE_OFFSET; + uint32_t InfoKind = BTF::FIELD_BYTE_OFFSET; while (CallStack.size()) { auto StackElem = CallStack.top(); Call = StackElem.first; @@ -939,7 +940,7 @@ if (CInfo.Kind == BPFPreserveFieldInfoAI) { InfoKind = CInfo.AccessIndex; - if (InfoKind == BPFCoreSharedInfo::FIELD_EXISTENCE) + if (InfoKind == BTF::FIELD_EXISTENCE) PatchImm = 1; break; } @@ -987,10 +988,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; @@ -1026,7 +1027,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 @@ -1518,10 +1518,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 show CO-RE relocation data. + +; RUN: llc --mtriple bpfel %s --filetype=obj -o - | \ +; RUN: llvm-objdump --no-addresses --no-show-raw-insn -dr - | \ +; RUN: FileCheck %s + +; RUN: llc --mtriple bpfeb %s --filetype=obj -o - | \ +; RUN: llvm-objdump --no-addresses --no-show-raw-insn -dr - | \ +; RUN: FileCheck %s + +;; Input generated from the following C code: +;; +;; #define __pai __attribute__((preserve_access_index)) +;; +;; struct buz { +;; int a; +;; int b; +;; } __pai; +;; +;; struct foo { +;; int :4; +;; int i; +;; struct buz k[10]; +;; } __pai; +;; +;; struct bar { +;; struct foo f; +;; } __pai; +;; +;; void * volatile g; +;; +;; void root(void) { +;; struct bar *bar = (void *)0; +;; g = &bar->f; +;; g = &bar->f.i; +;; g = &bar->f.k; +;; g = &bar->f.k[7].a; +;; g = &bar->f.k[7].b; +;; g = &bar[1].f.k[7].b; +;; } +;; +;; Using the following command: +;; +;; clang --target=bpf -g -O2 -emit-llvm -S t.c + +; CHECK: CO-RE [[[#bar:]]] struct bar::f (0:0) +; CHECK: CO-RE [[[#bar]]] struct bar::f.i (0:0:0) +; CHECK: CO-RE [[[#bar]]] struct bar::f.k (0:0:1) +; CHECK: CO-RE [[[#bar]]] struct bar::f.k[7].a (0:0:1:7:0) +; CHECK: CO-RE [[[#bar]]] struct bar::f.k[7].b (0:0:1:7:1) +; CHECK: CO-RE [[[#bar]]] struct bar::[1].f.k[7].b (1:0:1:7:1) + +@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 show CO-RE relocation data. + +; RUN: llc --mtriple bpfel %s --filetype=obj -o - | \ +; RUN: llvm-objdump --no-addresses --no-show-raw-insn -dr - | \ +; RUN: FileCheck %s + +; RUN: llc --mtriple bpfeb %s --filetype=obj -o - | \ +; RUN: llvm-objdump --no-addresses --no-show-raw-insn -dr - | \ +; RUN: FileCheck %s + +;; Input generated from the following C code: +;; +;; #define __pai __attribute__((preserve_access_index)) +;; +;; enum bar { U, V }; +;; volatile unsigned long g; +;; void root(void) { +;; g = __builtin_preserve_enum_value(*(enum bar *)U, 0); +;; g = __builtin_preserve_enum_value(*(enum bar *)V, 1); +;; } +;; +;; Using the following command: +;; +;; clang --target=bpf -g -O2 -emit-llvm -S t.c + +; CHECK: CO-RE [[[#]]] enum bar::U = 0 +; CHECK: CO-RE [[[#]]] enum bar::V = 1 + +@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 show CO-RE relocation data. + +; RUN: llc --mtriple bpfel %s --filetype=obj -o - | \ +; RUN: llvm-objdump --no-addresses --no-show-raw-insn -dr - | \ +; RUN: FileCheck %s + +; RUN: llc --mtriple bpfeb %s --filetype=obj -o - | \ +; RUN: llvm-objdump --no-addresses --no-show-raw-insn -dr - | \ +; RUN: FileCheck %s + +;; Input generated from the following C code: +;; +;; #define __pai __attribute__((preserve_access_index)) +;; +;; struct bar { int a; } __pai; +;; volatile unsigned long g; +;; void root(void) { +;; struct bar *bar = (void *)0; +;; g = __builtin_preserve_field_info(bar->a, 1); +;; g = __builtin_preserve_field_info(bar->a, 2); +;; g = __builtin_preserve_field_info(bar->a, 3); +;; g = __builtin_preserve_field_info(bar->a, 4); +;; g = __builtin_preserve_field_info(bar->a, 5); +;; } +;; +;; Using the following command: +;; +;; clang --target=bpf -g -O2 -emit-llvm -S t.c + +; CHECK: CO-RE [[[#]]] struct bar::a +; CHECK: CO-RE [[[#]]] struct bar::a +; CHECK: CO-RE [[[#]]] struct bar::a +; CHECK: CO-RE [[[#]]] struct bar::a +; CHECK: CO-RE [[[#]]] struct bar::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-formatting.s b/llvm/test/tools/llvm-objdump/BPF/core-relo-formatting.s new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-formatting.s @@ -0,0 +1,614 @@ +# REQUIRES: bpf-registered-target + +## Verify that when llvm-objdump uses .BTF.ext to show CO-RE +## relocations formatting options operate as expected. + +# RUN: llvm-mc --triple bpfel %s --filetype=obj | \ +# RUN: llvm-objdump --no-addresses --no-show-raw-insn -dr - | \ +# RUN: FileCheck --strict-whitespace --check-prefix=NOADDR %s + +# RUN: llvm-mc --triple bpfel %s --filetype=obj | \ +# RUN: llvm-objdump --no-addresses --no-show-raw-insn -d - | \ +# RUN: FileCheck --strict-whitespace --check-prefix=NORELO %s + +# RUN: llvm-mc --triple bpfel %s --filetype=obj | \ +# RUN: llvm-objdump --no-show-raw-insn -dr - | \ +# RUN: FileCheck --strict-whitespace --check-prefix=ADDR %s + +# RUN: llvm-mc --triple bpfel %s --filetype=obj | \ +# RUN: llvm-objdump --adjust-vma=0x10 --no-show-raw-insn -dr - | \ +# RUN: FileCheck --strict-whitespace --check-prefix=VMA %s + +## Input generated from the following C code: +## +## #define __pai __attribute__((preserve_access_index)) +## struct foo { +## int a; +## } __pai; +## enum bar { U, V }; +## extern void consume(unsigned long); +## void root() { +## asm volatile("r0 = 42;":::); +## struct foo *foo = 0; +## consume(__builtin_preserve_type_info(*foo, 0)); +## consume((unsigned long) &foo->a); +## consume(__builtin_preserve_enum_value(*(enum bar *)U, 0)); +## } +## +## Using the following command: +## +## clang -target bpf -g -O2 -S t.c + +# NOADDR: r1 = 0x1 +# NOADDR-NEXT: CO-RE [3] struct foo +# NOADDR-NEXT: call -0x1 +# NOADDR-NEXT: R_BPF_64_32 consume +# NOADDR-NEXT: r1 = 0x0 +# NOADDR-NEXT: CO-RE [3] struct foo::a (0:0) +# NOADDR-NEXT: call -0x1 +# NOADDR-NEXT: R_BPF_64_32 consume +# NOADDR-NEXT: r1 = 0x1 ll +# NOADDR-NEXT: CO-RE [8] enum bar::U = 0 +# NOADDR-NEXT: call -0x1 +# NOADDR-NEXT: R_BPF_64_32 consume +# NOADDR-NEXT: exit + +# NORELO: r1 = 0x1 +# NORELO-NEXT: call -0x1 +# NORELO-NEXT: r1 = 0x0 +# NORELO-NEXT: call -0x1 +# NORELO-NEXT: r1 = 0x1 ll +# NORELO-NEXT: call -0x1 +# NORELO-NEXT: exit + +# ADDR: 1: r1 = 0x1 +# ADDR-NEXT: 0000000000000008: CO-RE [3] struct foo +# ADDR-NEXT: 2: call -0x1 +# ADDR-NEXT: 0000000000000010: R_BPF_64_32 consume +# ADDR-NEXT: 3: r1 = 0x0 +# ADDR-NEXT: 0000000000000018: CO-RE [3] struct foo::a (0:0) +# ADDR-NEXT: 4: call -0x1 +# ADDR-NEXT: 0000000000000020: R_BPF_64_32 consume +# ADDR-NEXT: 5: r1 = 0x1 ll +# ADDR-NEXT: 0000000000000028: CO-RE [8] enum bar::U = 0 +# ADDR-NEXT: 7: call -0x1 +# ADDR-NEXT: 0000000000000038: R_BPF_64_32 consume +# ADDR-NEXT: 8: exit + +# VMA: 3: r1 = 0x1 +# VMA-NEXT: 0000000000000018: CO-RE [3] struct foo +# VMA-NEXT: 4: call -0x1 +# VMA-NEXT: 0000000000000010: R_BPF_64_32 consume +# VMA-NEXT: 5: r1 = 0x0 +# VMA-NEXT: 0000000000000028: CO-RE [3] struct foo::a (0:0) +# VMA-NEXT: 6: call -0x1 +# VMA-NEXT: 0000000000000020: R_BPF_64_32 consume +# VMA-NEXT: 7: r1 = 0x1 ll +# VMA-NEXT: 0000000000000038: CO-RE [8] enum bar::U = 0 +# VMA-NEXT: 9: call -0x1 +# VMA-NEXT: 0000000000000038: R_BPF_64_32 consume +# VMA-NEXT: 10: exit + + .text + .file "t.c" + .file 0 "/home/eddy/work/tmp" "t.c" md5 0x7675be79a30f35c69b89cf826ff55a5f + .globl root # -- Begin function root + .p2align 3 + .type root,@function +root: # @root +.Lfunc_begin0: + .cfi_sections .debug_frame + .cfi_startproc +# %bb.0: # %entry + .loc 0 8 3 prologue_end # t.c:8:3 +.Ltmp0: + #APP + r0 = 42 + + #NO_APP +.Ltmp1: +.Ltmp2: + #DEBUG_VALUE: root:foo <- 0 + .loc 0 10 3 # t.c:10:3 +.Ltmp3: +.Ltmp4: + r1 = 1 + call consume +.Ltmp5: + .loc 0 11 3 # t.c:11:3 +.Ltmp6: +.Ltmp7: + r1 = 0 + call consume +.Ltmp8: + .loc 0 12 3 # t.c:12:3 +.Ltmp9: +.Ltmp10: + r1 = 1 ll + call consume +.Ltmp11: + .loc 0 13 1 # t.c:13:1 +.Ltmp12: + exit +.Ltmp13: +.Ltmp14: +.Lfunc_end0: + .size root, .Lfunc_end0-root + .cfi_endproc + # -- End function + .section .debug_loclists,"",@progbits + .long .Ldebug_list_header_end0-.Ldebug_list_header_start0 # Length +.Ldebug_list_header_start0: + .short 5 # Version + .byte 8 # Address size + .byte 0 # Segment selector size + .long 1 # Offset entry count +.Lloclists_table_base0: + .long .Ldebug_loc0-.Lloclists_table_base0 +.Ldebug_loc0: + .byte 4 # DW_LLE_offset_pair + .uleb128 .Ltmp1-.Lfunc_begin0 # starting offset + .uleb128 .Lfunc_end0-.Lfunc_begin0 # ending offset + .byte 2 # Loc expr size + .byte 48 # DW_OP_lit0 + .byte 159 # DW_OP_stack_value + .byte 0 # DW_LLE_end_of_list +.Ldebug_list_header_end0: + .section .debug_abbrev,"",@progbits + .byte 1 # Abbreviation Code + .byte 17 # DW_TAG_compile_unit + .byte 1 # DW_CHILDREN_yes + .byte 37 # DW_AT_producer + .byte 37 # DW_FORM_strx1 + .byte 19 # DW_AT_language + .byte 5 # DW_FORM_data2 + .byte 3 # DW_AT_name + .byte 37 # DW_FORM_strx1 + .byte 114 # DW_AT_str_offsets_base + .byte 23 # DW_FORM_sec_offset + .byte 16 # DW_AT_stmt_list + .byte 23 # DW_FORM_sec_offset + .byte 27 # DW_AT_comp_dir + .byte 37 # DW_FORM_strx1 + .byte 17 # DW_AT_low_pc + .byte 27 # DW_FORM_addrx + .byte 18 # DW_AT_high_pc + .byte 6 # DW_FORM_data4 + .byte 115 # DW_AT_addr_base + .byte 23 # DW_FORM_sec_offset + .ascii "\214\001" # DW_AT_loclists_base + .byte 23 # DW_FORM_sec_offset + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 2 # Abbreviation Code + .byte 4 # DW_TAG_enumeration_type + .byte 1 # DW_CHILDREN_yes + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 3 # DW_AT_name + .byte 37 # DW_FORM_strx1 + .byte 11 # DW_AT_byte_size + .byte 11 # DW_FORM_data1 + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 3 # Abbreviation Code + .byte 40 # DW_TAG_enumerator + .byte 0 # DW_CHILDREN_no + .byte 3 # DW_AT_name + .byte 37 # DW_FORM_strx1 + .byte 28 # DW_AT_const_value + .byte 15 # DW_FORM_udata + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 4 # Abbreviation Code + .byte 36 # DW_TAG_base_type + .byte 0 # DW_CHILDREN_no + .byte 3 # DW_AT_name + .byte 37 # DW_FORM_strx1 + .byte 62 # DW_AT_encoding + .byte 11 # DW_FORM_data1 + .byte 11 # DW_AT_byte_size + .byte 11 # DW_FORM_data1 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 5 # Abbreviation Code + .byte 46 # DW_TAG_subprogram + .byte 1 # DW_CHILDREN_yes + .byte 17 # DW_AT_low_pc + .byte 27 # DW_FORM_addrx + .byte 18 # DW_AT_high_pc + .byte 6 # DW_FORM_data4 + .byte 64 # DW_AT_frame_base + .byte 24 # DW_FORM_exprloc + .byte 122 # DW_AT_call_all_calls + .byte 25 # DW_FORM_flag_present + .byte 3 # DW_AT_name + .byte 37 # DW_FORM_strx1 + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 63 # DW_AT_external + .byte 25 # DW_FORM_flag_present + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 6 # Abbreviation Code + .byte 52 # DW_TAG_variable + .byte 0 # DW_CHILDREN_no + .byte 2 # DW_AT_location + .byte 34 # DW_FORM_loclistx + .byte 3 # DW_AT_name + .byte 37 # DW_FORM_strx1 + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 7 # Abbreviation Code + .byte 72 # DW_TAG_call_site + .byte 0 # DW_CHILDREN_no + .byte 127 # DW_AT_call_origin + .byte 19 # DW_FORM_ref4 + .byte 125 # DW_AT_call_return_pc + .byte 27 # DW_FORM_addrx + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 8 # Abbreviation Code + .byte 46 # DW_TAG_subprogram + .byte 1 # DW_CHILDREN_yes + .byte 3 # DW_AT_name + .byte 37 # DW_FORM_strx1 + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 39 # DW_AT_prototyped + .byte 25 # DW_FORM_flag_present + .byte 60 # DW_AT_declaration + .byte 25 # DW_FORM_flag_present + .byte 63 # DW_AT_external + .byte 25 # DW_FORM_flag_present + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 9 # Abbreviation Code + .byte 5 # DW_TAG_formal_parameter + .byte 0 # DW_CHILDREN_no + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 10 # Abbreviation Code + .byte 15 # DW_TAG_pointer_type + .byte 0 # DW_CHILDREN_no + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 11 # Abbreviation Code + .byte 19 # DW_TAG_structure_type + .byte 1 # DW_CHILDREN_yes + .byte 3 # DW_AT_name + .byte 37 # DW_FORM_strx1 + .byte 11 # DW_AT_byte_size + .byte 11 # DW_FORM_data1 + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 12 # Abbreviation Code + .byte 13 # DW_TAG_member + .byte 0 # DW_CHILDREN_no + .byte 3 # DW_AT_name + .byte 37 # DW_FORM_strx1 + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 56 # DW_AT_data_member_location + .byte 11 # DW_FORM_data1 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 0 # EOM(3) + .section .debug_info,"",@progbits +.Lcu_begin0: + .long .Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit +.Ldebug_info_start0: + .short 5 # DWARF version number + .byte 1 # DWARF Unit Type + .byte 8 # Address Size (in bytes) + .long .debug_abbrev # Offset Into Abbrev. Section + .byte 1 # Abbrev [1] 0xc:0x7d DW_TAG_compile_unit + .byte 0 # DW_AT_producer + .short 29 # DW_AT_language + .byte 1 # DW_AT_name + .long .Lstr_offsets_base0 # DW_AT_str_offsets_base + .long .Lline_table_start0 # DW_AT_stmt_list + .byte 2 # DW_AT_comp_dir + .byte 0 # DW_AT_low_pc + .long .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc + .long .Laddr_table_base0 # DW_AT_addr_base + .long .Lloclists_table_base0 # DW_AT_loclists_base + .byte 2 # Abbrev [2] 0x27:0x10 DW_TAG_enumeration_type + .long 55 # DW_AT_type + .byte 6 # DW_AT_name + .byte 4 # DW_AT_byte_size + .byte 0 # DW_AT_decl_file + .byte 5 # DW_AT_decl_line + .byte 3 # Abbrev [3] 0x30:0x3 DW_TAG_enumerator + .byte 4 # DW_AT_name + .byte 0 # DW_AT_const_value + .byte 3 # Abbrev [3] 0x33:0x3 DW_TAG_enumerator + .byte 5 # DW_AT_name + .byte 1 # DW_AT_const_value + .byte 0 # End Of Children Mark + .byte 4 # Abbrev [4] 0x37:0x4 DW_TAG_base_type + .byte 3 # DW_AT_name + .byte 7 # DW_AT_encoding + .byte 4 # DW_AT_byte_size + .byte 4 # Abbrev [4] 0x3b:0x4 DW_TAG_base_type + .byte 7 # DW_AT_name + .byte 7 # DW_AT_encoding + .byte 8 # DW_AT_byte_size + .byte 5 # Abbrev [5] 0x3f:0x27 DW_TAG_subprogram + .byte 0 # DW_AT_low_pc + .long .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc + .byte 1 # DW_AT_frame_base + .byte 90 + # DW_AT_call_all_calls + .byte 9 # DW_AT_name + .byte 0 # DW_AT_decl_file + .byte 7 # DW_AT_decl_line + # DW_AT_external + .byte 6 # Abbrev [6] 0x4a:0x9 DW_TAG_variable + .byte 0 # DW_AT_location + .byte 10 # DW_AT_name + .byte 0 # DW_AT_decl_file + .byte 9 # DW_AT_decl_line + .long 112 # DW_AT_type + .byte 7 # Abbrev [7] 0x53:0x6 DW_TAG_call_site + .long 102 # DW_AT_call_origin + .byte 1 # DW_AT_call_return_pc + .byte 7 # Abbrev [7] 0x59:0x6 DW_TAG_call_site + .long 102 # DW_AT_call_origin + .byte 2 # DW_AT_call_return_pc + .byte 7 # Abbrev [7] 0x5f:0x6 DW_TAG_call_site + .long 102 # DW_AT_call_origin + .byte 3 # DW_AT_call_return_pc + .byte 0 # End Of Children Mark + .byte 8 # Abbrev [8] 0x66:0xa DW_TAG_subprogram + .byte 8 # DW_AT_name + .byte 0 # DW_AT_decl_file + .byte 6 # DW_AT_decl_line + # DW_AT_prototyped + # DW_AT_declaration + # DW_AT_external + .byte 9 # Abbrev [9] 0x6a:0x5 DW_TAG_formal_parameter + .long 59 # DW_AT_type + .byte 0 # End Of Children Mark + .byte 10 # Abbrev [10] 0x70:0x5 DW_TAG_pointer_type + .long 117 # DW_AT_type + .byte 11 # Abbrev [11] 0x75:0xf DW_TAG_structure_type + .byte 10 # DW_AT_name + .byte 4 # DW_AT_byte_size + .byte 0 # DW_AT_decl_file + .byte 2 # DW_AT_decl_line + .byte 12 # Abbrev [12] 0x7a:0x9 DW_TAG_member + .byte 11 # DW_AT_name + .long 132 # DW_AT_type + .byte 0 # DW_AT_decl_file + .byte 3 # DW_AT_decl_line + .byte 0 # DW_AT_data_member_location + .byte 0 # End Of Children Mark + .byte 4 # Abbrev [4] 0x84:0x4 DW_TAG_base_type + .byte 12 # DW_AT_name + .byte 5 # DW_AT_encoding + .byte 4 # DW_AT_byte_size + .byte 0 # End Of Children Mark +.Ldebug_info_end0: + .section .debug_str_offsets,"",@progbits + .long 56 # Length of String Offsets Set + .short 5 + .short 0 +.Lstr_offsets_base0: + .section .debug_str,"MS",@progbits,1 +.Linfo_string0: + .asciz "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 76d673bb89f8ec8cf65a4294a98a83c9d6646b11)" # string offset=0 +.Linfo_string1: + .asciz "t.c" # string offset=99 +.Linfo_string2: + .asciz "/home/eddy/work/tmp" # string offset=103 +.Linfo_string3: + .asciz "unsigned int" # string offset=123 +.Linfo_string4: + .asciz "U" # string offset=136 +.Linfo_string5: + .asciz "V" # string offset=138 +.Linfo_string6: + .asciz "bar" # string offset=140 +.Linfo_string7: + .asciz "unsigned long" # string offset=144 +.Linfo_string8: + .asciz "consume" # string offset=158 +.Linfo_string9: + .asciz "root" # string offset=166 +.Linfo_string10: + .asciz "foo" # string offset=171 +.Linfo_string11: + .asciz "a" # string offset=175 +.Linfo_string12: + .asciz "int" # string offset=177 + .section .debug_str_offsets,"",@progbits + .long .Linfo_string0 + .long .Linfo_string1 + .long .Linfo_string2 + .long .Linfo_string3 + .long .Linfo_string4 + .long .Linfo_string5 + .long .Linfo_string6 + .long .Linfo_string7 + .long .Linfo_string8 + .long .Linfo_string9 + .long .Linfo_string10 + .long .Linfo_string11 + .long .Linfo_string12 + .section .debug_addr,"",@progbits + .long .Ldebug_addr_end0-.Ldebug_addr_start0 # Length of contribution +.Ldebug_addr_start0: + .short 5 # DWARF version number + .byte 8 # Address size + .byte 0 # Segment selector size +.Laddr_table_base0: + .quad .Lfunc_begin0 + .quad .Ltmp5 + .quad .Ltmp8 + .quad .Ltmp11 +.Ldebug_addr_end0: + .section .BTF,"",@progbits + .short 60319 # 0xeb9f + .byte 1 + .byte 0 + .long 24 + .long 0 + .long 140 + .long 140 + .long 262 + .long 0 # BTF_KIND_FUNC_PROTO(id = 1) + .long 218103808 # 0xd000000 + .long 0 + .long 1 # BTF_KIND_FUNC(id = 2) + .long 201326593 # 0xc000001 + .long 1 + .long 67 # BTF_KIND_STRUCT(id = 3) + .long 67108865 # 0x4000001 + .long 4 + .long 71 + .long 4 + .long 0 # 0x0 + .long 73 # BTF_KIND_INT(id = 4) + .long 16777216 # 0x1000000 + .long 4 + .long 16777248 # 0x1000020 + .long 0 # BTF_KIND_FUNC_PROTO(id = 5) + .long 218103809 # 0xd000001 + .long 0 + .long 0 + .long 6 + .long 129 # BTF_KIND_INT(id = 6) + .long 16777216 # 0x1000000 + .long 8 + .long 64 # 0x40 + .long 143 # BTF_KIND_FUNC(id = 7) + .long 201326594 # 0xc000002 + .long 5 + .long 191 # BTF_KIND_ENUM(id = 8) + .long 100663298 # 0x6000002 + .long 4 + .long 195 + .long 0 + .long 197 + .long 1 + .byte 0 # string offset=0 + .ascii "root" # string offset=1 + .byte 0 + .ascii ".text" # string offset=6 + .byte 0 + .ascii "/home/eddy/work/tmp/t.c" # string offset=12 + .byte 0 + .ascii " asm volatile(\"r0 = 42;\":::);" # string offset=36 + .byte 0 + .ascii "foo" # string offset=67 + .byte 0 + .byte 97 # string offset=71 + .byte 0 + .ascii "int" # string offset=73 + .byte 0 + .byte 48 # string offset=77 + .byte 0 + .ascii " consume(__builtin_preserve_type_info(*foo, 0));" # string offset=79 + .byte 0 + .ascii "unsigned long" # string offset=129 + .byte 0 + .ascii "consume" # string offset=143 + .byte 0 + .ascii "0:0" # string offset=151 + .byte 0 + .ascii " consume((unsigned long) &foo->a);" # string offset=155 + .byte 0 + .ascii "bar" # string offset=191 + .byte 0 + .byte 85 # string offset=195 + .byte 0 + .byte 86 # string offset=197 + .byte 0 + .ascii " consume(__builtin_preserve_enum_value(*(enum bar *)U, 0));" # string offset=199 + .byte 0 + .byte 125 # string offset=260 + .byte 0 + .section .BTF.ext,"",@progbits + .short 60319 # 0xeb9f + .byte 1 + .byte 0 + .long 32 + .long 0 + .long 20 + .long 20 + .long 92 + .long 112 + .long 60 + .long 8 # FuncInfo + .long 6 # FuncInfo section string offset=6 + .long 1 + .long .Lfunc_begin0 + .long 2 + .long 16 # LineInfo + .long 6 # LineInfo section string offset=6 + .long 5 + .long .Ltmp0 + .long 12 + .long 36 + .long 8195 # Line 8 Col 3 + .long .Ltmp4 + .long 12 + .long 79 + .long 10243 # Line 10 Col 3 + .long .Ltmp7 + .long 12 + .long 155 + .long 11267 # Line 11 Col 3 + .long .Ltmp10 + .long 12 + .long 199 + .long 12291 # Line 12 Col 3 + .long .Ltmp12 + .long 12 + .long 260 + .long 13313 # Line 13 Col 1 + .long 16 # FieldReloc + .long 6 # Field reloc section string offset=6 + .long 3 + .long .Ltmp3 + .long 3 + .long 77 + .long 8 + .long .Ltmp6 + .long 3 + .long 151 + .long 0 + .long .Ltmp9 + .long 8 + .long 77 + .long 10 + .addrsig + .section .debug_line,"",@progbits +.Lline_table_start0: diff --git a/llvm/test/tools/llvm-objdump/BPF/core-relo-type-id.ll b/llvm/test/tools/llvm-objdump/BPF/core-relo-type-id.ll new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/BPF/core-relo-type-id.ll @@ -0,0 +1,91 @@ +; REQUIRES: bpf-registered-target + +;; Verify that llvm-objdump can use .BTF.ext to show CO-RE relocation data. + +; RUN: llc --mtriple bpfel %s --filetype=obj -o - | \ +; RUN: llvm-objdump --no-addresses --no-show-raw-insn -dr - | \ +; RUN: FileCheck %s + +; RUN: llc --mtriple bpfeb %s --filetype=obj -o - | \ +; RUN: llvm-objdump --no-addresses --no-show-raw-insn -dr - | \ +; RUN: FileCheck %s + +;; Input generated from the following C code: +;; +;; #define __pai __attribute__((preserve_access_index)) +;; +;; struct bar { } __pai; +;; volatile unsigned long g; +;; void root(void) { +;; struct bar *bar = (void *)0; +;; g = __builtin_btf_type_id(*bar, 0); +;; g = __builtin_btf_type_id(*bar, 1); +;; } +;; +;; Using the following command: +;; +;; clang --target=bpf -g -O2 -emit-llvm -S t.c + +; CHECK: CO-RE [[[#]]] struct bar +; CHECK: CO-RE [[[#]]] 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 show CO-RE relocation data. + +; RUN: llc --mtriple bpfel %s --filetype=obj -o - | \ +; RUN: llvm-objdump --no-addresses --no-show-raw-insn -dr - | \ +; RUN: FileCheck %s + +; RUN: llc --mtriple bpfeb %s --filetype=obj -o - | \ +; RUN: llvm-objdump --no-addresses --no-show-raw-insn -dr - | \ +; RUN: FileCheck %s + +;; Input generated from the following C code: +;; +;; #define __pai __attribute__((preserve_access_index)) +;; +;; struct bar { } __pai; +;; volatile unsigned long g; +;; void root(void) { +;; struct bar *bar = (void *)0; +;; g = __builtin_preserve_type_info(*bar, 0); +;; g = __builtin_preserve_type_info(*bar, 1); +;; g = __builtin_preserve_type_info(*bar, 2); +;; } +;; +;; Using the following command: +;; +;; clang --target=bpf -g -O2 -emit-llvm -S t.c + +; CHECK: CO-RE [[[#]]] struct bar +; CHECK: CO-RE [[[#]]] struct bar +; CHECK: CO-RE [[[#]]] 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/CMakeLists.txt b/llvm/tools/llvm-objdump/CMakeLists.txt --- a/llvm/tools/llvm-objdump/CMakeLists.txt +++ b/llvm/tools/llvm-objdump/CMakeLists.txt @@ -3,6 +3,7 @@ AllTargetsDisassemblers AllTargetsInfos BinaryFormat + DebugInfoBTF DebugInfoDWARF Demangle MC diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp --- a/llvm/tools/llvm-objdump/llvm-objdump.cpp +++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp @@ -31,6 +31,7 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSet.h" #include "llvm/ADT/Twine.h" +#include "llvm/DebugInfo/BTF/BTFParser.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h" #include "llvm/DebugInfo/Symbolize/Symbolize.h" @@ -535,6 +536,22 @@ OS << Name << "\t" << Val; } +static void printBTFRelocation(formatted_raw_ostream &FOS, llvm::BTFParser &BTF, + object::SectionedAddress Address, + LiveVariablePrinter &LVP) { + const llvm::BTF::BPFFieldReloc *Reloc = BTF.findFieldReloc(Address); + if (!Reloc) + return; + + SmallString<64> Val; + BTF.symbolize(Reloc, Val); + FOS << "\t\t"; + if (LeadingAddr) + FOS << format("%016" PRIx64 ": ", Address.Address + AdjustVMA); + FOS << "CO-RE " << Val; + LVP.printAfterOtherLine(FOS, true); +} + class PrettyPrinter { public: virtual ~PrettyPrinter() = default; @@ -1626,6 +1643,16 @@ if (SymbolizeOperands && !Obj.isRelocatableObject()) ReadBBAddrMap(); + std::optional BTF; + if (InlineRelocs && BTFParser::hasBTFSections(Obj)) { + BTF.emplace(); + BTFParser::ParseOptions Opts = {}; + Opts.LoadTypes = true; + Opts.LoadRelocs = true; + if (Error E = BTF->parse(Obj, Opts)) + WithColor::defaultErrorHandler(std::move(E)); + } + for (const SectionRef &Section : ToolSectionFilter(Obj)) { if (FilterSections.empty() && !DisassembleAll && (!Section.isText() || Section.isVirtual())) @@ -2163,6 +2190,9 @@ *DT->SubtargetInfo, CommentStream.str(), LVP); Comments.clear(); + if (BTF) + printBTFRelocation(FOS, *BTF, {Index, Section.getIndex()}, LVP); + // Hexagon does this in pretty printer if (Obj.getArch() != Triple::hexagon) { // Print relocation for instruction and data. diff --git a/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp b/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp --- a/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp +++ b/llvm/unittests/DebugInfo/BTF/BTFParserTest.cpp @@ -16,6 +16,7 @@ using namespace llvm::object; #define LC(Line, Col) ((Line << 10u) | Col) +#define ASSERT_SUCCEEDED(E) ASSERT_THAT_ERROR((E), Succeeded()) const char BTFEndOfData[] = "error while reading .BTF section: unexpected end of data"; @@ -52,7 +53,7 @@ #pragma pack(push, 1) struct B { BTF::Header Header = {}; - // no types + // No types. struct S { char Foo[4] = "foo"; char Bar[4] = "bar"; @@ -74,7 +75,7 @@ struct E { BTF::ExtHeader Header = {}; - // no func info + // No func info. struct { uint32_t LineRecSize = sizeof(BTF::BPFLineInfo); struct { @@ -163,7 +164,7 @@ EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, File1)), "a.c"); EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, File2)), "b.c"); - // Invalid offset + // Invalid offset. EXPECT_EQ(BTF.findString(sizeof(MockData1::B::S)), StringRef()); const BTF::BPFLineInfo *I1 = BTF.findLineInfo({16, 1}); @@ -187,10 +188,10 @@ EXPECT_EQ(BTF.findString(I3->FileNameOff), "b.c"); EXPECT_EQ(BTF.findString(I3->LineOff), "first line"); - // No info for insn address + // No info for insn address. EXPECT_FALSE(BTF.findLineInfo({24, 1})); EXPECT_FALSE(BTF.findLineInfo({8, 2})); - // No info for section number + // No info for section number. EXPECT_FALSE(BTF.findLineInfo({16, 3})); } @@ -202,13 +203,13 @@ Mock.Ext.Lines.Foo.Sec.SecNameOff = 100500; Error Err = BTF.parse(Mock.makeObj()); EXPECT_FALSE(Err); - // "foo" line info should be corrupted + // "foo" line info should be corrupted. EXPECT_FALSE(BTF.findLineInfo({16, 1})); - // "bar" line info should be ok + // "bar" line info should be ok. EXPECT_TRUE(BTF.findLineInfo({0, 2})); } -// Keep this as macro to preserve line number info +// Keep this as macro to preserve line number info. #define EXPECT_PARSE_ERROR(Mock, Message) \ do { \ BTFParser BTF; \ @@ -237,12 +238,12 @@ TEST(BTFParserTest, badBTFSectionLen) { MockData1 Mock1, Mock2; - // Cut-off string section by one byte + // Cut-off string section by one byte. Mock1.BTFSectionLen = offsetof(MockData1::B, Strings) + sizeof(MockData1::B::S) - 1; EXPECT_PARSE_ERROR(Mock1, "invalid .BTF section size"); - // Cut-off header + // Cut-off header. Mock2.BTFSectionLen = offsetof(BTF::Header, StrOff); EXPECT_PARSE_ERROR(Mock2, BTFEndOfData); } @@ -272,15 +273,15 @@ TEST(BTFParserTest, badBTFExtSectionLen) { MockData1 Mock1, Mock2, Mock3; - // Cut-off header before HdrLen + // Cut-off header before HdrLen. Mock1.ExtSectionLen = offsetof(BTF::ExtHeader, HdrLen); EXPECT_PARSE_ERROR(Mock1, BTFExtEndOfData); - // Cut-off header before LineInfoLen + // Cut-off header before LineInfoLen. Mock2.ExtSectionLen = offsetof(BTF::ExtHeader, LineInfoLen); EXPECT_PARSE_ERROR(Mock2, BTFExtEndOfData); - // Cut-off line-info section somewhere in the middle + // Cut-off line-info section somewhere in the middle. Mock3.ExtSectionLen = offsetof(MockData1::E, Lines) + 4; EXPECT_PARSE_ERROR(Mock3, BTFExtEndOfData); } @@ -355,4 +356,732 @@ EXPECT_EQ(I2.LineSource, std::nullopt); } +static uint32_t mkInfo(uint32_t Kind) { return Kind << 24; } + +template static void append(std::string &S, const T &What) { + S.append((const char *)&What, sizeof(What)); +} + +class MockData2 { + SmallString<0> ObjStorage; + std::unique_ptr Obj; + std::string Types; + std::string Strings; + std::string Relocs; + std::string Lines; + unsigned TotalTypes; + int LastRelocSecIdx; + unsigned NumRelocs; + int LastLineSecIdx; + unsigned NumLines; + +public: + MockData2() { reset(); } + + unsigned totalTypes() const { return TotalTypes; } + + uint32_t addString(StringRef S) { + uint32_t Off = Strings.size(); + Strings.append(S.data(), S.size()); + Strings.append("\0", 1); + return Off; + }; + + uint32_t addType(const BTF::CommonType &Tp) { + append(Types, Tp); + return ++TotalTypes; + } + + template void addTail(const T &Tp) { append(Types, Tp); } + + void resetTypes() { + Types.resize(0); + TotalTypes = 0; + } + + void reset() { + ObjStorage.clear(); + Types.resize(0); + Strings.resize(0); + Relocs.resize(0); + Lines.resize(0); + TotalTypes = 0; + LastRelocSecIdx = -1; + NumRelocs = 0; + LastLineSecIdx = -1; + NumLines = 0; + } + + void finishRelocSec() { + if (LastRelocSecIdx == -1) + return; + + BTF::SecFieldReloc *SecInfo = + (BTF::SecFieldReloc *)&Relocs[LastRelocSecIdx]; + SecInfo->NumFieldReloc = NumRelocs; + LastRelocSecIdx = -1; + NumRelocs = 0; + } + + void finishLineSec() { + if (LastLineSecIdx == -1) + return; + + BTF::SecLineInfo *SecInfo = (BTF::SecLineInfo *)&Lines[LastLineSecIdx]; + SecInfo->NumLineInfo = NumLines; + NumLines = 0; + LastLineSecIdx = -1; + } + + void addRelocSec(const BTF::SecFieldReloc &R) { + finishRelocSec(); + LastRelocSecIdx = Relocs.size(); + append(Relocs, R); + } + + void addReloc(const BTF::BPFFieldReloc &R) { + append(Relocs, R); + ++NumRelocs; + } + + void addLinesSec(const BTF::SecLineInfo &R) { + finishLineSec(); + LastLineSecIdx = Lines.size(); + append(Lines, R); + } + + void addLine(const BTF::BPFLineInfo &R) { + append(Lines, R); + ++NumLines; + } + + ObjectFile &makeObj() { + finishRelocSec(); + finishLineSec(); + + BTF::Header BTFHeader = {}; + BTFHeader.Magic = BTF::MAGIC; + BTFHeader.Version = 1; + BTFHeader.HdrLen = sizeof(BTFHeader); + BTFHeader.StrOff = 0; + BTFHeader.StrLen = Strings.size(); + BTFHeader.TypeOff = Strings.size(); + BTFHeader.TypeLen = Types.size(); + + std::string BTFSec; + append(BTFSec, BTFHeader); + BTFSec.append(Strings); + BTFSec.append(Types); + + BTF::ExtHeader ExtHeader = {}; + ExtHeader.Magic = BTF::MAGIC; + ExtHeader.Version = 1; + ExtHeader.HdrLen = sizeof(ExtHeader); + ExtHeader.FieldRelocOff = 0; + ExtHeader.FieldRelocLen = Relocs.size() + sizeof(uint32_t); + ExtHeader.LineInfoOff = ExtHeader.FieldRelocLen; + ExtHeader.LineInfoLen = Lines.size() + sizeof(uint32_t); + + std::string ExtSec; + append(ExtSec, ExtHeader); + append(ExtSec, (uint32_t)sizeof(BTF::BPFFieldReloc)); + ExtSec.append(Relocs); + append(ExtSec, (uint32_t)sizeof(BTF::BPFLineInfo)); + ExtSec.append(Lines); + + std::string YamlBuffer; + raw_string_ostream Yaml(YamlBuffer); + Yaml << R"( +!ELF +FileHeader: + Class: ELFCLASS64)"; + if (sys::IsBigEndianHost) + Yaml << "\n Data: ELFDATA2MSB"; + else + Yaml << "\n Data: ELFDATA2LSB"; + Yaml << R"( + Type: ET_REL + Machine: EM_BPF +Sections: + - Name: foo + Type: SHT_PROGBITS + Size: 0x80 + - Name: bar + Type: SHT_PROGBITS + Size: 0x80 + - Name: .BTF + Type: SHT_PROGBITS + Content: )" + << makeBinRef(BTFSec.data(), BTFSec.size()); + Yaml << R"( + - Name: .BTF.ext + Type: SHT_PROGBITS + Content: )" + << makeBinRef(ExtSec.data(), ExtSec.size()); + + Obj = yaml::yaml2ObjectFile(ObjStorage, YamlBuffer, + [](const Twine &Err) { errs() << Err; }); + return *Obj.get(); + } +}; + +TEST(BTFParserTest, allTypeKinds) { + MockData2 D; + D.addType({D.addString("1"), mkInfo(BTF::BTF_KIND_INT), {4}}); + D.addTail((uint32_t)0); + D.addType({D.addString("2"), mkInfo(BTF::BTF_KIND_PTR), {1}}); + D.addType({D.addString("3"), mkInfo(BTF::BTF_KIND_ARRAY), {0}}); + D.addTail(BTF::BTFArray({1, 1, 2})); + D.addType({D.addString("4"), mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}}); + D.addTail(BTF::BTFMember({D.addString("a"), 1, 0})); + D.addTail(BTF::BTFMember({D.addString("b"), 1, 0})); + D.addType({D.addString("5"), mkInfo(BTF::BTF_KIND_UNION) | 3, {8}}); + D.addTail(BTF::BTFMember({D.addString("a"), 1, 0})); + D.addTail(BTF::BTFMember({D.addString("b"), 1, 0})); + D.addTail(BTF::BTFMember({D.addString("c"), 1, 0})); + D.addType({D.addString("6"), mkInfo(BTF::BTF_KIND_ENUM) | 2, {4}}); + D.addTail(BTF::BTFEnum({D.addString("U"), 1})); + D.addTail(BTF::BTFEnum({D.addString("V"), 2})); + D.addType({D.addString("7"), mkInfo(BTF::BTF_KIND_ENUM64) | 1, {4}}); + D.addTail(BTF::BTFEnum64({D.addString("W"), 0, 1})); + D.addType( + {D.addString("8"), BTF::FWD_UNION_FLAG | mkInfo(BTF::BTF_KIND_FWD), {0}}); + D.addType({D.addString("9"), mkInfo(BTF::BTF_KIND_TYPEDEF), {1}}); + D.addType({D.addString("10"), mkInfo(BTF::BTF_KIND_VOLATILE), {1}}); + D.addType({D.addString("11"), mkInfo(BTF::BTF_KIND_CONST), {1}}); + D.addType({D.addString("12"), mkInfo(BTF::BTF_KIND_RESTRICT), {1}}); + D.addType({D.addString("13"), mkInfo(BTF::BTF_KIND_FUNC_PROTO) | 1, {1}}); + D.addTail(BTF::BTFParam({D.addString("P"), 2})); + D.addType({D.addString("14"), mkInfo(BTF::BTF_KIND_FUNC), {13}}); + D.addType({D.addString("15"), mkInfo(BTF::BTF_KIND_VAR), {2}}); + D.addTail((uint32_t)0); + D.addType({D.addString("16"), mkInfo(BTF::BTF_KIND_DATASEC) | 3, {0}}); + D.addTail(BTF::BTFDataSec({1, 0, 4})); + D.addTail(BTF::BTFDataSec({1, 4, 4})); + D.addTail(BTF::BTFDataSec({1, 8, 4})); + D.addType({D.addString("17"), mkInfo(BTF::BTF_KIND_FLOAT), {4}}); + D.addType({D.addString("18"), mkInfo(BTF::BTF_KIND_DECL_TAG), {0}}); + D.addTail((uint32_t)-1); + D.addType({D.addString("19"), mkInfo(BTF::BTF_KIND_TYPE_TAG), {0}}); + + BTFParser BTF; + Error Err = BTF.parse(D.makeObj()); + EXPECT_FALSE(Err); + + EXPECT_EQ(D.totalTypes() + 1 /* +1 for void */, BTF.typesCount()); + for (unsigned Id = 1; Id < D.totalTypes() + 1; ++Id) { + const BTF::CommonType *Tp = BTF.findType(Id); + ASSERT_TRUE(Tp); + std::string IdBuf; + raw_string_ostream IdBufStream(IdBuf); + IdBufStream << Id; + EXPECT_EQ(BTF.findString(Tp->NameOff), IdBuf); + } +} + +TEST(BTFParserTest, bigStruct) { + const uint32_t N = 1000u; + MockData2 D; + uint32_t FStr = D.addString("f"); + D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_INT), {4}}); + D.addTail((uint32_t)0); + D.addType({D.addString("big"), mkInfo(BTF::BTF_KIND_STRUCT) | N, {8}}); + for (unsigned I = 0; I < N; ++I) + D.addTail(BTF::BTFMember({FStr, 1, 0})); + D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_INT), {4}}); + D.addTail((uint32_t)0); + + BTFParser BTF; + ASSERT_SUCCEEDED(BTF.parse(D.makeObj())); + ASSERT_EQ(BTF.typesCount(), 4u /* +1 for void */); + const BTF::CommonType *Foo = BTF.findType(1); + const BTF::CommonType *Big = BTF.findType(2); + const BTF::CommonType *Bar = BTF.findType(3); + ASSERT_TRUE(Foo); + ASSERT_TRUE(Big); + ASSERT_TRUE(Bar); + EXPECT_EQ(BTF.findString(Foo->NameOff), "foo"); + EXPECT_EQ(BTF.findString(Big->NameOff), "big"); + EXPECT_EQ(BTF.findString(Bar->NameOff), "bar"); + EXPECT_EQ(Big->getVlen(), N); +} + +TEST(BTFParserTest, incompleteTypes) { + MockData2 D; + auto IncompleteType = [&](const BTF::CommonType &Tp) { + D.resetTypes(); + D.addType(Tp); + EXPECT_PARSE_ERROR(D, "incomplete type definition in .BTF section"); + }; + + // All kinds that need tail. + IncompleteType({D.addString("a"), mkInfo(BTF::BTF_KIND_INT), {4}}); + IncompleteType({D.addString("b"), mkInfo(BTF::BTF_KIND_ARRAY), {0}}); + IncompleteType({D.addString("c"), mkInfo(BTF::BTF_KIND_VAR), {0}}); + IncompleteType({D.addString("d"), mkInfo(BTF::BTF_KIND_DECL_TAG), {0}}); + + // All kinds with vlen. + IncompleteType({D.addString("a"), mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}}); + IncompleteType({D.addString("b"), mkInfo(BTF::BTF_KIND_UNION) | 3, {8}}); + IncompleteType({D.addString("c"), mkInfo(BTF::BTF_KIND_ENUM) | 2, {4}}); + IncompleteType({D.addString("d"), mkInfo(BTF::BTF_KIND_ENUM64) | 1, {4}}); + IncompleteType({D.addString("e"), mkInfo(BTF::BTF_KIND_FUNC_PROTO) | 1, {1}}); + IncompleteType({D.addString("f"), mkInfo(BTF::BTF_KIND_DATASEC) | 3, {0}}); + + // An unexpected tail. + D.resetTypes(); + D.addTail((uint32_t)0); + EXPECT_PARSE_ERROR(D, "incomplete type definition in .BTF section"); +} + +// Use macro to preserve line number in error message. +#define SYMBOLIZE(SecAddr, Expected) \ + do { \ + const BTF::BPFFieldReloc *R = BTF.findFieldReloc((SecAddr)); \ + ASSERT_TRUE(R); \ + SmallString<64> Symbolized; \ + BTF.symbolize(R, Symbolized); \ + EXPECT_EQ(Symbolized, (Expected)); \ + } while (false) + +// Shorter name for initializers below. +using SA = SectionedAddress; + +TEST(BTFParserTest, typeRelocs) { + MockData2 D; + uint32_t Zero = D.addString("0"); + // id 1: struct foo {} + // id 2: union bar; + // id 3: struct buz; + D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_STRUCT), {0}}); + D.addType({D.addString("bar"), + mkInfo(BTF::BTF_KIND_FWD) | BTF::FWD_UNION_FLAG, + {0}}); + D.addType({D.addString("buz"), mkInfo(BTF::BTF_KIND_FWD), {0}}); + D.addRelocSec({D.addString("foo"), 7}); + // List of all possible correct type relocations for type id #1. + D.addReloc({0, 1, Zero, BTF::BTF_TYPE_ID_LOCAL}); + D.addReloc({8, 1, Zero, BTF::BTF_TYPE_ID_REMOTE}); + D.addReloc({16, 1, Zero, BTF::TYPE_EXISTENCE}); + D.addReloc({24, 1, Zero, BTF::TYPE_MATCH}); + D.addReloc({32, 1, Zero, BTF::TYPE_SIZE}); + // Forward declarations. + D.addReloc({40, 2, Zero, BTF::TYPE_SIZE}); + D.addReloc({48, 3, Zero, BTF::TYPE_SIZE}); + // Incorrect type relocation: bad type id. + D.addReloc({56, 42, Zero, BTF::TYPE_SIZE}); + // Incorrect type relocation: spec should be '0'. + D.addReloc({64, 1, D.addString("10"), BTF::TYPE_SIZE}); + + BTFParser BTF; + Error E = BTF.parse(D.makeObj()); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), " [1] struct foo"); + SYMBOLIZE(SA({8, 1}), " [1] struct foo"); + SYMBOLIZE(SA({16, 1}), " [1] struct foo"); + SYMBOLIZE(SA({24, 1}), " [1] struct foo"); + SYMBOLIZE(SA({32, 1}), " [1] struct foo"); + SYMBOLIZE(SA({40, 1}), " [2] fwd union bar"); + SYMBOLIZE(SA({48, 1}), " [3] fwd struct buz"); + SYMBOLIZE(SA({56, 1}), " [42] '0' "); + SYMBOLIZE(SA({64, 1}), + " [1] '10' " + ""); +} + +TEST(BTFParserTest, enumRelocs) { + MockData2 D; + // id 1: enum { U, V } + D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_ENUM) | 2, {4}}); + D.addTail(BTF::BTFEnum({D.addString("U"), 1})); + D.addTail(BTF::BTFEnum({D.addString("V"), 2})); + // id 2: int + D.addType({D.addString("int"), mkInfo(BTF::BTF_KIND_INT), {4}}); + D.addTail((uint32_t)0); + // id 3: enum: uint64_t { A, B } + D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_ENUM64) | 2, {8}}); + D.addTail(BTF::BTFEnum64({D.addString("A"), 1, 0})); + D.addTail(BTF::BTFEnum64({D.addString("B"), 2, 0})); + + D.addRelocSec({D.addString("foo"), 5}); + // An ok relocation accessing value #1: U. + D.addReloc({0, 1, D.addString("0"), BTF::ENUM_VALUE_EXISTENCE}); + // An ok relocation accessing value #2: V. + D.addReloc({8, 1, D.addString("1"), BTF::ENUM_VALUE}); + // Incorrect relocation: too many elements in string "1:0". + D.addReloc({16, 1, D.addString("1:0"), BTF::ENUM_VALUE}); + // Incorrect relocation: type id "2" is not an enum. + D.addReloc({24, 2, D.addString("1"), BTF::ENUM_VALUE}); + // Incorrect relocation: value #42 does not exist for enum "foo". + D.addReloc({32, 1, D.addString("42"), BTF::ENUM_VALUE}); + // An ok relocation accessing value #1: A. + D.addReloc({40, 3, D.addString("0"), BTF::ENUM_VALUE_EXISTENCE}); + // An ok relocation accessing value #2: B. + D.addReloc({48, 3, D.addString("1"), BTF::ENUM_VALUE}); + + BTFParser BTF; + Error E = BTF.parse(D.makeObj()); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), " [1] enum foo::U = 1"); + SYMBOLIZE(SA({8, 1}), " [1] enum foo::V = 2"); + SYMBOLIZE( + SA({16, 1}), + " [1] '1:0' "); + SYMBOLIZE( + SA({24, 1}), + " [2] '1' "); + SYMBOLIZE(SA({32, 1}), " [1] '42' "); + SYMBOLIZE(SA({40, 1}), " [3] enum bar::A = 1"); + SYMBOLIZE(SA({48, 1}), " [3] enum bar::B = 2"); +} + +TEST(BTFParserTest, enumRelocsMods) { + MockData2 D; + // id 1: enum { U, V } + D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_ENUM) | 2, {4}}); + D.addTail(BTF::BTFEnum({D.addString("U"), 1})); + D.addTail(BTF::BTFEnum({D.addString("V"), 2})); + // id 2: typedef enum foo a; + D.addType({D.addString("a"), mkInfo(BTF::BTF_KIND_TYPEDEF), {1}}); + // id 3: const enum foo; + D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_CONST), {1}}); + + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, 2, D.addString("0"), BTF::ENUM_VALUE}); + D.addReloc({8, 3, D.addString("1"), BTF::ENUM_VALUE}); + + BTFParser BTF; + Error E = BTF.parse(D.makeObj()); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), " [2] typedef a::U = 1"); + SYMBOLIZE(SA({8, 1}), " [3] const enum foo::V = 2"); +} + +TEST(BTFParserTest, fieldRelocs) { + MockData2 D; + // id 1: int + D.addType({D.addString("int"), mkInfo(BTF::BTF_KIND_INT), {4}}); + D.addTail((uint32_t)0); + // id 2: struct foo { int a; int b; } + D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}}); + D.addTail(BTF::BTFMember({D.addString("a"), 1, 0})); + D.addTail(BTF::BTFMember({D.addString("b"), 1, 0})); + // id 3: array of struct foo. + D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_ARRAY), {0}}); + D.addTail(BTF::BTFArray({2, 1, 2})); + // id 4: struct bar { struct foo u[2]; int v; } + D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}}); + D.addTail(BTF::BTFMember({D.addString("u"), 3, 0})); + D.addTail(BTF::BTFMember({D.addString("v"), 1, 0})); + // id 5: array with bad element type id. + D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_ARRAY), {0}}); + D.addTail(BTF::BTFArray({42, 1, 2})); + // id 6: struct buz { u[2]; v; } + D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}}); + D.addTail(BTF::BTFMember({D.addString("u"), 5, 0})); + D.addTail(BTF::BTFMember({D.addString("v"), 42, 0})); + + D.addRelocSec({D.addString("foo"), 0 /* patched automatically */}); + // All field relocations kinds for struct bar::v. + D.addReloc({0, 4, D.addString("0:1"), BTF::FIELD_BYTE_OFFSET}); + D.addReloc({8, 4, D.addString("0:1"), BTF::FIELD_BYTE_SIZE}); + D.addReloc({16, 4, D.addString("0:1"), BTF::FIELD_EXISTENCE}); + D.addReloc({24, 4, D.addString("0:1"), BTF::FIELD_SIGNEDNESS}); + D.addReloc({32, 4, D.addString("0:1"), BTF::FIELD_LSHIFT_U64}); + D.addReloc({40, 4, D.addString("0:1"), BTF::FIELD_RSHIFT_U64}); + // Non-zero first idx. + D.addReloc({48, 4, D.addString("7:1"), BTF::FIELD_BYTE_OFFSET}); + // Access through array and struct: struct bar::u[1].a. + D.addReloc({56, 4, D.addString("0:0:1:0"), BTF::FIELD_BYTE_OFFSET}); + // Access through array and struct: struct bar::u[1].b. + D.addReloc({64, 4, D.addString("0:0:1:1"), BTF::FIELD_BYTE_OFFSET}); + // Incorrect relocation: empty access string. + D.addReloc({72, 4, D.addString(""), BTF::FIELD_BYTE_OFFSET}); + // Incorrect relocation: member index out of range (only two members in bar). + D.addReloc({80, 4, D.addString("0:2"), BTF::FIELD_BYTE_OFFSET}); + // Incorrect relocation: unknown element type id (buz::u[0] access). + D.addReloc({88, 6, D.addString("0:0:0"), BTF::FIELD_BYTE_OFFSET}); + // Incorrect relocation: unknown member type id (buz::v access). + D.addReloc({96, 6, D.addString("0:1:0"), BTF::FIELD_BYTE_OFFSET}); + // Incorrect relocation: non structural type in the middle of access string + // struct bar::v.. + D.addReloc({104, 4, D.addString("0:1:0"), BTF::FIELD_BYTE_OFFSET}); + + BTFParser BTF; + Error E = BTF.parse(D.makeObj()); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), " [4] struct bar::v (0:1)"); + SYMBOLIZE(SA({8, 1}), " [4] struct bar::v (0:1)"); + SYMBOLIZE(SA({16, 1}), " [4] struct bar::v (0:1)"); + SYMBOLIZE(SA({24, 1}), " [4] struct bar::v (0:1)"); + SYMBOLIZE(SA({32, 1}), " [4] struct bar::v (0:1)"); + SYMBOLIZE(SA({40, 1}), " [4] struct bar::v (0:1)"); + SYMBOLIZE(SA({48, 1}), " [4] struct bar::[7].v (7:1)"); + SYMBOLIZE(SA({56, 1}), " [4] struct bar::u[1].a (0:0:1:0)"); + SYMBOLIZE(SA({64, 1}), " [4] struct bar::u[1].b (0:0:1:1)"); + SYMBOLIZE(SA({72, 1}), " [4] '' "); + SYMBOLIZE(SA({80, 1}), + " [4] '0:2' " + ""); + SYMBOLIZE(SA({88, 1}), " [6] '0:0:0' " + ""); + SYMBOLIZE(SA({96, 1}), " [6] '0:1:0' " + ""); + SYMBOLIZE(SA({104, 1}), " [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 ; + uint32_t Int = + D.addType({D.addString("int"), mkInfo(BTF::BTF_KIND_INT), {4}}); + D.addTail((uint32_t)0); + uint32_t Foo = + D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}}); + D.addTail(BTF::BTFMember({D.addString("u"), Int, 0})); + uint32_t Bar = + D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_TYPEDEF), {Foo}}); + uint32_t CBar = + D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_CONST), {Bar}}); + uint32_t Buz = + D.addType({D.addString("buz"), mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}}); + D.addTail(BTF::BTFMember({D.addString("v"), CBar, 0})); + uint32_t Quux = + D.addType({D.addString("quux"), mkInfo(BTF::BTF_KIND_TYPEDEF), {Buz}}); + uint32_t RQuux = + D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_RESTRICT), {Quux}}); + uint32_t VRQuux = + D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_VOLATILE), {RQuux}}); + uint32_t CVRQuux = + D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_CONST), {VRQuux}}); + uint32_t CUnknown = + D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_CONST), {77}}); + uint32_t CVUnknown = + D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_VOLATILE), {CUnknown}}); + + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, Bar, D.addString("0:0"), BTF::FIELD_BYTE_OFFSET}); + D.addReloc({8, CVRQuux, D.addString("0:0:0"), BTF::FIELD_BYTE_OFFSET}); + D.addReloc({16, CVUnknown, D.addString("0:1:2"), BTF::FIELD_BYTE_OFFSET}); + + BTFParser BTF; + Error E = BTF.parse(D.makeObj()); + EXPECT_FALSE(E); + + // Should show modifiers / name of typedef. + SYMBOLIZE(SA({0, 1}), " [3] typedef bar::u (0:0)"); + SYMBOLIZE(SA({8, 1}), + " [9] const volatile restrict typedef quux::v.u (0:0:0)"); + SYMBOLIZE(SA({16, 1}), + " [11] '0:1:2' "); +} + +TEST(BTFParserTest, relocTypeTagAndVoid) { + MockData2 D; + // __attribute__((type_tag("tag"))) void + uint32_t Tag = + D.addType({D.addString("tag"), mkInfo(BTF::BTF_KIND_TYPE_TAG), {0}}); + + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, Tag, D.addString("0"), BTF::TYPE_EXISTENCE}); + D.addReloc({8, 0 /* void */, D.addString("0"), BTF::TYPE_EXISTENCE}); + + BTFParser BTF; + Error E = BTF.parse(D.makeObj()); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), " [1] type_tag(\"tag\") void"); + SYMBOLIZE(SA({8, 1}), " [0] void"); +} + +TEST(BTFParserTest, longRelocModifiersCycle) { + MockData2 D; + + D.addType( + {D.addString(""), mkInfo(BTF::BTF_KIND_CONST), {1 /* ourselves */}}); + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, 1, D.addString(""), BTF::TYPE_EXISTENCE}); + + BTFParser BTF; + Error E = BTF.parse(D.makeObj()); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), " [1] '' "); +} + +TEST(BTFParserTest, relocAnonFieldsAndTypes) { + MockData2 D; + + // struct { + // int :32; + // } v; + uint32_t Int = + D.addType({D.addString("int"), mkInfo(BTF::BTF_KIND_INT), {4}}); + D.addTail((uint32_t)0); + uint32_t Anon = + D.addType({D.addString(""), mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}}); + D.addTail(BTF::BTFMember({D.addString(""), Int, 0})); + + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, Anon, D.addString("0"), BTF::TYPE_EXISTENCE}); + D.addReloc({8, Anon, D.addString("0:0"), BTF::FIELD_BYTE_OFFSET}); + + BTFParser BTF; + Error E = BTF.parse(D.makeObj()); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), " [2] struct "); + SYMBOLIZE(SA({8, 1}), " [2] struct :: (0:0)"); +} + +TEST(BTFParserTest, miscBadRelos) { + MockData2 D; + + uint32_t S = D.addType({D.addString("S"), mkInfo(BTF::BTF_KIND_STRUCT), {0}}); + + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, 0, D.addString(""), 777}); + D.addReloc({8, S, D.addString("abc"), BTF::FIELD_BYTE_OFFSET}); + D.addReloc({16, S, D.addString("0#"), BTF::FIELD_BYTE_OFFSET}); + + BTFParser BTF; + Error E = BTF.parse(D.makeObj()); + EXPECT_FALSE(E); + + SYMBOLIZE(SA({0, 1}), + " [0] '' "); + SYMBOLIZE(SA({8, 1}), " [1] 'abc' "); + SYMBOLIZE(SA({16, 1}), + " [1] '0#' "); +} + +TEST(BTFParserTest, relocsMultipleSections) { + MockData2 D; + + uint32_t S = D.addType({D.addString("S"), mkInfo(BTF::BTF_KIND_STRUCT), {0}}); + uint32_t T = D.addType({D.addString("T"), mkInfo(BTF::BTF_KIND_STRUCT), {0}}); + + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, S, D.addString(""), BTF::TYPE_EXISTENCE}); + D.addReloc({8, S, D.addString(""), BTF::TYPE_EXISTENCE}); + + D.addRelocSec({D.addString("bar"), 0}); + D.addReloc({8, T, D.addString(""), BTF::TYPE_EXISTENCE}); + D.addReloc({16, T, D.addString(""), BTF::TYPE_EXISTENCE}); + + BTFParser BTF; + Error E = BTF.parse(D.makeObj()); + EXPECT_FALSE(E); + + EXPECT_TRUE(BTF.findFieldReloc({0, 1})); + EXPECT_TRUE(BTF.findFieldReloc({8, 1})); + EXPECT_FALSE(BTF.findFieldReloc({16, 1})); + + EXPECT_FALSE(BTF.findFieldReloc({0, 2})); + EXPECT_TRUE(BTF.findFieldReloc({8, 2})); + EXPECT_TRUE(BTF.findFieldReloc({16, 2})); + + EXPECT_FALSE(BTF.findFieldReloc({0, 3})); + EXPECT_FALSE(BTF.findFieldReloc({8, 3})); + EXPECT_FALSE(BTF.findFieldReloc({16, 3})); + + auto AssertReloType = [&](const SectionedAddress &A, const char *Name) { + const BTF::BPFFieldReloc *Relo = BTF.findFieldReloc(A); + ASSERT_TRUE(Relo); + const BTF::CommonType *Type = BTF.findType(Relo->TypeID); + ASSERT_TRUE(Type); + EXPECT_EQ(BTF.findString(Type->NameOff), Name); + }; + + AssertReloType({8, 1}, "S"); + AssertReloType({8, 2}, "T"); +} + +TEST(BTFParserTest, parserResetReloAndTypes) { + BTFParser BTF; + MockData2 D; + + // First time: two types, two relocations. + D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_STRUCT), {0}}); + D.addType({D.addString("bar"), mkInfo(BTF::BTF_KIND_STRUCT), {0}}); + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, 1, D.addString(""), BTF::TYPE_EXISTENCE}); + D.addReloc({8, 2, D.addString(""), BTF::TYPE_EXISTENCE}); + + Error E1 = BTF.parse(D.makeObj()); + EXPECT_FALSE(E1); + + ASSERT_TRUE(BTF.findType(1)); + EXPECT_EQ(BTF.findString(BTF.findType(1)->NameOff), "foo"); + EXPECT_TRUE(BTF.findType(2)); + EXPECT_TRUE(BTF.findFieldReloc({0, 1})); + EXPECT_TRUE(BTF.findFieldReloc({8, 1})); + + // Second time: one type, one relocation. + D.reset(); + D.addType({D.addString("buz"), mkInfo(BTF::BTF_KIND_STRUCT), {0}}); + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, 1, D.addString(""), BTF::TYPE_EXISTENCE}); + + Error E2 = BTF.parse(D.makeObj()); + EXPECT_FALSE(E2); + + ASSERT_TRUE(BTF.findType(1)); + EXPECT_EQ(BTF.findString(BTF.findType(1)->NameOff), "buz"); + EXPECT_FALSE(BTF.findType(2)); + EXPECT_TRUE(BTF.findFieldReloc({0, 1})); + EXPECT_FALSE(BTF.findFieldReloc({8, 1})); +} + +TEST(BTFParserTest, selectiveLoad) { + BTFParser BTF1, BTF2, BTF3; + MockData2 D; + + D.addType({D.addString("foo"), mkInfo(BTF::BTF_KIND_STRUCT), {0}}); + D.addRelocSec({D.addString("foo"), 0}); + D.addReloc({0, 1, D.addString(""), BTF::TYPE_EXISTENCE}); + D.addLinesSec({D.addString("foo"), 0}); + D.addLine({0, D.addString("file.c"), D.addString("some line"), LC(2, 3)}); + + BTFParser::ParseOptions Opts; + + ObjectFile &Obj1 = D.makeObj(); + Opts = {}; + Opts.LoadLines = true; + ASSERT_SUCCEEDED(BTF1.parse(Obj1, Opts)); + + Opts = {}; + Opts.LoadTypes = true; + ASSERT_SUCCEEDED(BTF2.parse(Obj1, Opts)); + + Opts = {}; + Opts.LoadRelocs = true; + ASSERT_SUCCEEDED(BTF3.parse(Obj1, Opts)); + + EXPECT_TRUE(BTF1.findLineInfo({0, 1})); + EXPECT_FALSE(BTF2.findLineInfo({0, 1})); + EXPECT_FALSE(BTF3.findLineInfo({0, 1})); + + EXPECT_FALSE(BTF1.findType(1)); + EXPECT_TRUE(BTF2.findType(1)); + EXPECT_FALSE(BTF3.findType(1)); + + EXPECT_FALSE(BTF1.findFieldReloc({0, 1})); + EXPECT_FALSE(BTF2.findFieldReloc({0, 1})); + EXPECT_TRUE(BTF3.findFieldReloc({0, 1})); +} + } // namespace