mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-29 02:06:06 +00:00

llvm/include/llvm/Support/VersionTuple.h doesn't need anything from llvm/Support/Endian.h, but llvm/lib/DebugInfo/BTF/BTFParser.cpp relies on a transitive inclusion of llvm/Support/Endian.h.
858 lines
26 KiB
C++
858 lines
26 KiB
C++
//===- BTFParser.cpp ------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// BTFParser reads/interprets .BTF and .BTF.ext ELF sections.
|
|
// Refer to BTFParser.h for API description.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/DebugInfo/BTF/BTFParser.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Support/Endian.h"
|
|
#include "llvm/Support/Errc.h"
|
|
|
|
#define DEBUG_TYPE "debug-info-btf-parser"
|
|
|
|
using namespace llvm;
|
|
using object::ObjectFile;
|
|
using object::SectionedAddress;
|
|
using object::SectionRef;
|
|
|
|
const char BTFSectionName[] = ".BTF";
|
|
const char BTFExtSectionName[] = ".BTF.ext";
|
|
|
|
// Utility class with API similar to raw_ostream but can be cast
|
|
// to Error, e.g.:
|
|
//
|
|
// Error foo(...) {
|
|
// ...
|
|
// if (Error E = bar(...))
|
|
// return Err("error while foo(): ") << E;
|
|
// ...
|
|
// }
|
|
//
|
|
namespace {
|
|
class Err {
|
|
std::string Buffer;
|
|
raw_string_ostream Stream;
|
|
|
|
public:
|
|
Err(const char *InitialMsg) : Buffer(InitialMsg), Stream(Buffer) {}
|
|
Err(const char *SectionName, DataExtractor::Cursor &C)
|
|
: Buffer(), Stream(Buffer) {
|
|
*this << "error while reading " << SectionName
|
|
<< " section: " << C.takeError();
|
|
};
|
|
|
|
template <typename T> Err &operator<<(T Val) {
|
|
Stream << Val;
|
|
return *this;
|
|
}
|
|
|
|
Err &write_hex(unsigned long long Val) {
|
|
Stream.write_hex(Val);
|
|
return *this;
|
|
}
|
|
|
|
Err &operator<<(Error Val) {
|
|
handleAllErrors(std::move(Val),
|
|
[=](ErrorInfoBase &Info) { Stream << Info.message(); });
|
|
return *this;
|
|
}
|
|
|
|
operator Error() const {
|
|
return make_error<StringError>(Buffer, errc::invalid_argument);
|
|
}
|
|
};
|
|
} // anonymous namespace
|
|
|
|
// ParseContext wraps information that is only necessary while parsing
|
|
// ObjectFile and can be discarded once parsing is done.
|
|
// Used by BTFParser::parse* auxiliary functions.
|
|
struct BTFParser::ParseContext {
|
|
const ObjectFile &Obj;
|
|
const ParseOptions &Opts;
|
|
// Map from ELF section name to SectionRef
|
|
DenseMap<StringRef, SectionRef> Sections;
|
|
|
|
public:
|
|
ParseContext(const ObjectFile &Obj, const ParseOptions &Opts)
|
|
: Obj(Obj), Opts(Opts) {}
|
|
|
|
Expected<DataExtractor> makeExtractor(SectionRef Sec) {
|
|
Expected<StringRef> Contents = Sec.getContents();
|
|
if (!Contents)
|
|
return Contents.takeError();
|
|
return DataExtractor(Contents.get(), Obj.isLittleEndian(),
|
|
Obj.getBytesInAddress());
|
|
}
|
|
|
|
std::optional<SectionRef> findSection(StringRef Name) const {
|
|
auto It = Sections.find(Name);
|
|
if (It != Sections.end())
|
|
return It->second;
|
|
return std::nullopt;
|
|
}
|
|
};
|
|
|
|
Error BTFParser::parseBTF(ParseContext &Ctx, SectionRef BTF) {
|
|
Expected<DataExtractor> MaybeExtractor = Ctx.makeExtractor(BTF);
|
|
if (!MaybeExtractor)
|
|
return MaybeExtractor.takeError();
|
|
|
|
DataExtractor &Extractor = MaybeExtractor.get();
|
|
DataExtractor::Cursor C = DataExtractor::Cursor(0);
|
|
uint16_t Magic = Extractor.getU16(C);
|
|
if (!C)
|
|
return Err(".BTF", C);
|
|
if (Magic != BTF::MAGIC)
|
|
return Err("invalid .BTF magic: ").write_hex(Magic);
|
|
uint8_t Version = Extractor.getU8(C);
|
|
if (!C)
|
|
return Err(".BTF", C);
|
|
if (Version != 1)
|
|
return Err("unsupported .BTF version: ") << (unsigned)Version;
|
|
(void)Extractor.getU8(C); // flags
|
|
uint32_t HdrLen = Extractor.getU32(C);
|
|
if (!C)
|
|
return Err(".BTF", C);
|
|
if (HdrLen < 8)
|
|
return Err("unexpected .BTF header length: ") << HdrLen;
|
|
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() < BytesExpected)
|
|
return Err("invalid .BTF section size, expecting at-least ")
|
|
<< 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;
|
|
}
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
Error BTFParser::parseBTFExt(ParseContext &Ctx, SectionRef BTFExt) {
|
|
Expected<DataExtractor> MaybeExtractor = Ctx.makeExtractor(BTFExt);
|
|
if (!MaybeExtractor)
|
|
return MaybeExtractor.takeError();
|
|
|
|
DataExtractor &Extractor = MaybeExtractor.get();
|
|
DataExtractor::Cursor C = DataExtractor::Cursor(0);
|
|
uint16_t Magic = Extractor.getU16(C);
|
|
if (!C)
|
|
return Err(".BTF.ext", C);
|
|
if (Magic != BTF::MAGIC)
|
|
return Err("invalid .BTF.ext magic: ").write_hex(Magic);
|
|
uint8_t Version = Extractor.getU8(C);
|
|
if (!C)
|
|
return Err(".BTF", C);
|
|
if (Version != 1)
|
|
return Err("unsupported .BTF.ext version: ") << (unsigned)Version;
|
|
(void)Extractor.getU8(C); // flags
|
|
uint32_t HdrLen = Extractor.getU32(C);
|
|
if (!C)
|
|
return Err(".BTF.ext", C);
|
|
if (HdrLen < 8)
|
|
return Err("unexpected .BTF.ext header length: ") << HdrLen;
|
|
(void)Extractor.getU32(C); // func_info_off
|
|
(void)Extractor.getU32(C); // func_info_len
|
|
uint32_t LineInfoOff = Extractor.getU32(C);
|
|
uint32_t LineInfoLen = Extractor.getU32(C);
|
|
uint32_t RelocInfoOff = Extractor.getU32(C);
|
|
uint32_t RelocInfoLen = Extractor.getU32(C);
|
|
if (!C)
|
|
return Err(".BTF.ext", C);
|
|
|
|
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();
|
|
}
|
|
|
|
Error BTFParser::parseLineInfo(ParseContext &Ctx, DataExtractor &Extractor,
|
|
uint64_t LineInfoStart, uint64_t LineInfoEnd) {
|
|
DataExtractor::Cursor C = DataExtractor::Cursor(LineInfoStart);
|
|
uint32_t RecSize = Extractor.getU32(C);
|
|
if (!C)
|
|
return Err(".BTF.ext", C);
|
|
if (RecSize < 16)
|
|
return Err("unexpected .BTF.ext line info record length: ") << RecSize;
|
|
|
|
while (C && C.tell() < LineInfoEnd) {
|
|
uint32_t SecNameOff = Extractor.getU32(C);
|
|
uint32_t NumInfo = Extractor.getU32(C);
|
|
StringRef SecName = findString(SecNameOff);
|
|
std::optional<SectionRef> Sec = Ctx.findSection(SecName);
|
|
if (!C)
|
|
return Err(".BTF.ext", C);
|
|
if (!Sec)
|
|
return Err("") << "can't find section '" << SecName
|
|
<< "' while parsing .BTF.ext line info";
|
|
BTFLinesVector &Lines = SectionLines[Sec->getIndex()];
|
|
for (uint32_t I = 0; C && I < NumInfo; ++I) {
|
|
uint64_t RecStart = C.tell();
|
|
uint32_t InsnOff = Extractor.getU32(C);
|
|
uint32_t FileNameOff = Extractor.getU32(C);
|
|
uint32_t LineOff = Extractor.getU32(C);
|
|
uint32_t LineCol = Extractor.getU32(C);
|
|
if (!C)
|
|
return Err(".BTF.ext", C);
|
|
Lines.push_back({InsnOff, FileNameOff, LineOff, LineCol});
|
|
C.seek(RecStart + RecSize);
|
|
}
|
|
llvm::stable_sort(Lines,
|
|
[](const BTF::BPFLineInfo &L, const BTF::BPFLineInfo &R) {
|
|
return L.InsnOffset < R.InsnOffset;
|
|
});
|
|
}
|
|
if (!C)
|
|
return Err(".BTF.ext", C);
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
Error BTFParser::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, Opts);
|
|
std::optional<SectionRef> BTF;
|
|
std::optional<SectionRef> BTFExt;
|
|
for (SectionRef Sec : Obj.sections()) {
|
|
Expected<StringRef> MaybeName = Sec.getName();
|
|
if (!MaybeName)
|
|
return Err("error while reading section name: ") << MaybeName.takeError();
|
|
Ctx.Sections[*MaybeName] = Sec;
|
|
if (*MaybeName == BTFSectionName)
|
|
BTF = Sec;
|
|
if (*MaybeName == BTFExtSectionName)
|
|
BTFExt = Sec;
|
|
}
|
|
if (!BTF)
|
|
return Err("can't find .BTF section");
|
|
if (!BTFExt)
|
|
return Err("can't find .BTF.ext section");
|
|
if (Error E = parseBTF(Ctx, *BTF))
|
|
return E;
|
|
if (Error E = parseBTFExt(Ctx, *BTFExt))
|
|
return E;
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
bool BTFParser::hasBTFSections(const ObjectFile &Obj) {
|
|
bool HasBTF = false;
|
|
bool HasBTFExt = false;
|
|
for (SectionRef Sec : Obj.sections()) {
|
|
Expected<StringRef> Name = Sec.getName();
|
|
if (Error E = Name.takeError()) {
|
|
logAllUnhandledErrors(std::move(E), errs());
|
|
continue;
|
|
}
|
|
HasBTF |= *Name == BTFSectionName;
|
|
HasBTFExt |= *Name == BTFExtSectionName;
|
|
if (HasBTF && HasBTFExt)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
StringRef BTFParser::findString(uint32_t Offset) const {
|
|
return StringsTable.slice(Offset, StringsTable.find(0, Offset));
|
|
}
|
|
|
|
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 SmallVector<T, 0> &SecInfo = MaybeSecInfo->second;
|
|
const uint64_t TargetOffset = 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 &*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));
|
|
}
|