diff options
Diffstat (limited to 'llvm/lib/DebugInfo/BTF/BTFParser.cpp')
| -rw-r--r-- | llvm/lib/DebugInfo/BTF/BTFParser.cpp | 620 |
1 files changed, 597 insertions, 23 deletions
diff --git a/llvm/lib/DebugInfo/BTF/BTFParser.cpp b/llvm/lib/DebugInfo/BTF/BTFParser.cpp index 6151e1b15cbb..4fc31a445603 100644 --- a/llvm/lib/DebugInfo/BTF/BTFParser.cpp +++ b/llvm/lib/DebugInfo/BTF/BTFParser.cpp @@ -12,6 +12,8 @@ //===----------------------------------------------------------------------===// #include "llvm/DebugInfo/BTF/BTFParser.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Endian.h" #include "llvm/Support/Errc.h" #define DEBUG_TYPE "debug-info-btf-parser" @@ -74,11 +76,13 @@ public: // Used by BTFParser::parse* auxiliary functions. struct BTFParser::ParseContext { const ObjectFile &Obj; + const ParseOptions &Opts; // Map from ELF section name to SectionRef DenseMap<StringRef, SectionRef> Sections; public: - ParseContext(const ObjectFile &Obj) : Obj(Obj) {} + ParseContext(const ObjectFile &Obj, const ParseOptions &Opts) + : Obj(Obj), Opts(Opts) {} Expected<DataExtractor> makeExtractor(SectionRef Sec) { Expected<StringRef> Contents = Sec.getContents(); @@ -119,19 +123,126 @@ Error BTFParser::parseBTF(ParseContext &Ctx, SectionRef BTF) { 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::endian::byte_swap; + + TypesBuffer = OwningArrayRef<uint8_t>(arrayRefFromStringRef(RawData)); + // Switch endianness if necessary. + endianness Endianness = Ctx.Obj.isLittleEndian() ? llvm::endianness::little + : llvm::endianness::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 +273,24 @@ Error BTFParser::parseBTFExt(ParseContext &Ctx, SectionRef BTFExt) { (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 +337,52 @@ Error BTFParser::parseLineInfo(ParseContext &Ctx, DataExtractor &Extractor, 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<SectionRef> 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<uint8_t>(); - ParseContext Ctx(Obj); + ParseContext Ctx(Obj, Opts); std::optional<SectionRef> BTF; std::optional<SectionRef> BTFExt; for (SectionRef Sec : Obj.sections()) { @@ -264,20 +428,430 @@ StringRef BTFParser::findString(uint32_t Offset) const { return StringsTable.slice(Offset, StringsTable.find(0, Offset)); } -const BTF::BPFLineInfo * -BTFParser::findLineInfo(SectionedAddress Address) const { - auto MaybeSecInfo = SectionLines.find(Address.SectionIndex); - if (MaybeSecInfo == SectionLines.end()) +template <typename T> +static const T *findInfo(const DenseMap<uint64_t, SmallVector<T, 0>> &SecMap, + SectionedAddress Address) { + auto MaybeSecInfo = SecMap.find(Address.SectionIndex); + if (MaybeSecInfo == SecMap.end()) return nullptr; - const BTFLinesVector &SecInfo = MaybeSecInfo->second; + const SmallVector<T, 0> &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<T, 0>::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 << "<anon " << S.Idx << ">"; + 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: +// +// <relocation-kind> [<id>] <type-name> +// +// For example: +// - "<type_exists> [7] struct foo" +// - "<type_size> [7] struct foo" +// +// - load-time information about enums (literal existence, literal value), +// `BTFParser::symbolize()` output for such relocations uses the template: +// +// <relocation-kind> [<id>] <type-name>::<literal-name> = <original-value> +// +// For example: +// - "<enumval_exists> [5] enum foo::U = 1" +// - "<enumval_value> [5] enum foo::V = 2" +// +// - load-time information about fields (e.g. field offset), +// `BTFParser::symbolize()` output for such relocations uses the template: +// +// <relocation-kind> [<id>] \ +// <type-name>::[N].<field-1-name>...<field-M-name> \ +// (<access string>) +// +// For example: +// - "<byte_off> [8] struct bar::[7].v (7:1)" +// - "<field_exists> [8] struct bar::v (0:1)" +// +// If relocation description is not valid output follows the following pattern: +// +// <relocation-kind> <type-id>::<unprocessedaccess-string> <<error-msg>> +// +// For example: +// +// - "<type_sz> [42] '' <unknown type id: 42>" +// - "<byte_off> [4] '0:' <field spec too short>" +// +// 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<char> &Result) const { + raw_svector_ostream Stream(Result); + StringRef FullSpecStr = findString(Reloc->OffsetNameOff); + SmallVector<uint32_t, 8> 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<BTF::EnumType>(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<BTF::Enum64Type>(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<BTF::StructType>(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<BTF::ArrayType>(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)); } |
