//===-- SourcePrinter.cpp - source interleaving utilities ----------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/DebugInfo/BTF/BTFContext.h" #include "llvm/ObjectYAML/YAML.h" #include "llvm/ObjectYAML/yaml2obj.h" #include "llvm/Support/SwapByteOrder.h" #include "llvm/Testing/Support/Error.h" using namespace llvm; 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"; const char BTFExtEndOfData[] = "error while reading .BTF.ext section: unexpected end of data"; static raw_ostream &operator<<(raw_ostream &OS, const yaml::BinaryRef &Ref) { Ref.writeAsHex(OS); return OS; } template static yaml::BinaryRef makeBinRef(const T *Ptr, size_t Size = sizeof(T)) { return yaml::BinaryRef(ArrayRef((const uint8_t *)Ptr, Size)); } namespace { // This is a mockup for an ELF file containing .BTF and .BTF.ext sections. // Binary content of these sections corresponds to the value of // MockData1::BTF and MockData1::Ext fields. // // The yaml::yaml2ObjectFile() is used to generate actual ELF, // see MockData1::makeObj(). // // The `BTF` and `Ext` fields are initialized with correct values // valid for a small example with a few sections, fields could be // modified before a call to `makeObj()` to test parser with invalid // input, etc. struct MockData1 { // Use "pragma pack" to model .BTF & .BTF.ext sections content using // 'struct' objects. This pragma is supported by CLANG, GCC & MSVC, // which matters for LLVM CI. #pragma pack(push, 1) struct B { BTF::Header Header = {}; // No types. struct S { char Foo[4] = "foo"; char Bar[4] = "bar"; char Buz[4] = "buz"; char Line1[11] = "first line"; char Line2[12] = "second line"; char File1[4] = "a.c"; char File2[4] = "b.c"; } Strings; B() { Header.Magic = BTF::MAGIC; Header.Version = 1; Header.HdrLen = sizeof(Header); Header.StrOff = offsetof(B, Strings) - sizeof(Header); Header.StrLen = sizeof(Strings); } } BTF; struct E { BTF::ExtHeader Header = {}; // No func info. struct { uint32_t LineRecSize = sizeof(BTF::BPFLineInfo); struct { BTF::SecLineInfo Sec = {offsetof(B::S, Foo), 2}; BTF::BPFLineInfo Lines[2] = { {16, offsetof(B::S, File1), offsetof(B::S, Line1), LC(7, 1)}, {32, offsetof(B::S, File1), offsetof(B::S, Line2), LC(14, 5)}, }; } Foo; struct { BTF::SecLineInfo Sec = {offsetof(B::S, Bar), 1}; BTF::BPFLineInfo Lines[1] = { {0, offsetof(B::S, File2), offsetof(B::S, Line1), LC(42, 4)}, }; } Bar; } Lines; E() { Header.Magic = BTF::MAGIC; Header.Version = 1; Header.HdrLen = sizeof(Header); Header.LineInfoOff = offsetof(E, Lines) - sizeof(Header); Header.LineInfoLen = sizeof(Lines); } } Ext; #pragma pack(pop) int BTFSectionLen = sizeof(BTF); int ExtSectionLen = sizeof(Ext); SmallString<0> Storage; std::unique_ptr Obj; ObjectFile &makeObj() { std::string Buffer; raw_string_ostream Yaml(Buffer); 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: 0x0 - Name: bar Type: SHT_PROGBITS Size: 0x0)"; if (BTFSectionLen >= 0) Yaml << R"( - Name: .BTF Type: SHT_PROGBITS Content: )" << makeBinRef(&BTF, BTFSectionLen); if (ExtSectionLen >= 0) Yaml << R"( - Name: .BTF.ext Type: SHT_PROGBITS Content: )" << makeBinRef(&Ext, ExtSectionLen); Obj = yaml::yaml2ObjectFile(Storage, Buffer, [](const Twine &Err) { errs() << Err; }); return *Obj.get(); } }; TEST(BTFParserTest, simpleCorrectInput) { BTFParser BTF; MockData1 Mock; Error Err = BTF.parse(Mock.makeObj()); EXPECT_FALSE(Err); EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, Foo)), "foo"); EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, Bar)), "bar"); EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, Line1)), "first line"); EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, Line2)), "second line"); EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, File1)), "a.c"); EXPECT_EQ(BTF.findString(offsetof(MockData1::B::S, File2)), "b.c"); // Invalid offset. EXPECT_EQ(BTF.findString(sizeof(MockData1::B::S)), StringRef()); const BTF::BPFLineInfo *I1 = BTF.findLineInfo({16, 1}); ASSERT_TRUE(I1); EXPECT_EQ(I1->getLine(), 7u); EXPECT_EQ(I1->getCol(), 1u); EXPECT_EQ(BTF.findString(I1->FileNameOff), "a.c"); EXPECT_EQ(BTF.findString(I1->LineOff), "first line"); const BTF::BPFLineInfo *I2 = BTF.findLineInfo({32, 1}); ASSERT_TRUE(I2); EXPECT_EQ(I2->getLine(), 14u); EXPECT_EQ(I2->getCol(), 5u); EXPECT_EQ(BTF.findString(I2->FileNameOff), "a.c"); EXPECT_EQ(BTF.findString(I2->LineOff), "second line"); const BTF::BPFLineInfo *I3 = BTF.findLineInfo({0, 2}); ASSERT_TRUE(I3); EXPECT_EQ(I3->getLine(), 42u); EXPECT_EQ(I3->getCol(), 4u); EXPECT_EQ(BTF.findString(I3->FileNameOff), "b.c"); EXPECT_EQ(BTF.findString(I3->LineOff), "first line"); // No info for insn address. EXPECT_FALSE(BTF.findLineInfo({24, 1})); EXPECT_FALSE(BTF.findLineInfo({8, 2})); // No info for section number. EXPECT_FALSE(BTF.findLineInfo({16, 3})); } TEST(BTFParserTest, badSectionNameOffset) { BTFParser BTF; MockData1 Mock; // "foo" is section #1, corrupting it's name offset will make impossible // to match section name with section index when BTF is parsed. Mock.Ext.Lines.Foo.Sec.SecNameOff = 100500; Error Err = BTF.parse(Mock.makeObj()); EXPECT_FALSE(Err); // "foo" line info should be corrupted. EXPECT_FALSE(BTF.findLineInfo({16, 1})); // "bar" line info should be ok. EXPECT_TRUE(BTF.findLineInfo({0, 2})); } // Keep this as macro to preserve line number info. #define EXPECT_PARSE_ERROR(Mock, Message) \ do { \ BTFParser BTF; \ EXPECT_THAT_ERROR(BTF.parse((Mock).makeObj()), \ FailedWithMessage(testing::HasSubstr(Message))); \ } while (false) TEST(BTFParserTest, badBTFMagic) { MockData1 Mock; Mock.BTF.Header.Magic = 42; EXPECT_PARSE_ERROR(Mock, "invalid .BTF magic: 2a"); } TEST(BTFParserTest, badBTFVersion) { MockData1 Mock; Mock.BTF.Header.Version = 42; EXPECT_PARSE_ERROR(Mock, "unsupported .BTF version: 42"); } TEST(BTFParserTest, badBTFHdrLen) { MockData1 Mock; Mock.BTF.Header.HdrLen = 5; EXPECT_PARSE_ERROR(Mock, "unexpected .BTF header length: 5"); } TEST(BTFParserTest, badBTFSectionLen) { MockData1 Mock1, Mock2; // Cut-off string section by one byte. Mock1.BTFSectionLen = offsetof(MockData1::B, Strings) + sizeof(MockData1::B::S) - 1; EXPECT_PARSE_ERROR(Mock1, "invalid .BTF section size"); // Cut-off header. Mock2.BTFSectionLen = offsetof(BTF::Header, StrOff); EXPECT_PARSE_ERROR(Mock2, BTFEndOfData); } TEST(BTFParserTest, badBTFExtMagic) { MockData1 Mock; Mock.Ext.Header.Magic = 42; EXPECT_PARSE_ERROR(Mock, "invalid .BTF.ext magic: 2a"); } TEST(BTFParserTest, badBTFExtVersion) { MockData1 Mock; Mock.Ext.Header.Version = 42; EXPECT_PARSE_ERROR(Mock, "unsupported .BTF.ext version: 42"); } TEST(BTFParserTest, badBTFExtHdrLen) { MockData1 Mock1, Mock2; Mock1.Ext.Header.HdrLen = 5; EXPECT_PARSE_ERROR(Mock1, "unexpected .BTF.ext header length: 5"); Mock2.Ext.Header.HdrLen = sizeof(Mock2.Ext); EXPECT_PARSE_ERROR(Mock2, BTFExtEndOfData); } TEST(BTFParserTest, badBTFExtSectionLen) { MockData1 Mock1, Mock2, Mock3; // Cut-off header before HdrLen. Mock1.ExtSectionLen = offsetof(BTF::ExtHeader, HdrLen); EXPECT_PARSE_ERROR(Mock1, BTFExtEndOfData); // 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. Mock3.ExtSectionLen = offsetof(MockData1::E, Lines) + 4; EXPECT_PARSE_ERROR(Mock3, BTFExtEndOfData); } TEST(BTFParserTest, badBTFExtLineInfoRecSize) { MockData1 Mock1, Mock2; Mock1.Ext.Lines.LineRecSize = 2; EXPECT_PARSE_ERROR(Mock1, "unexpected .BTF.ext line info record length: 2"); Mock2.Ext.Lines.LineRecSize = sizeof(Mock2.Ext.Lines.Foo.Lines[0]) + 1; EXPECT_PARSE_ERROR(Mock2, BTFExtEndOfData); } TEST(BTFParserTest, badBTFExtLineSectionName) { MockData1 Mock1; Mock1.Ext.Lines.Foo.Sec.SecNameOff = offsetof(MockData1::B::S, Buz); EXPECT_PARSE_ERROR( Mock1, "can't find section 'buz' while parsing .BTF.ext line info"); } TEST(BTFParserTest, missingSections) { MockData1 Mock1, Mock2, Mock3; Mock1.BTFSectionLen = -1; EXPECT_PARSE_ERROR(Mock1, "can't find .BTF section"); EXPECT_FALSE(BTFParser::hasBTFSections(Mock1.makeObj())); Mock2.ExtSectionLen = -1; EXPECT_PARSE_ERROR(Mock2, "can't find .BTF.ext section"); EXPECT_FALSE(BTFParser::hasBTFSections(Mock2.makeObj())); EXPECT_TRUE(BTFParser::hasBTFSections(Mock3.makeObj())); } // Check that BTFParser instance is reset when BTFParser::parse() is // called several times. TEST(BTFParserTest, parserReset) { BTFParser BTF; MockData1 Mock1, Mock2; EXPECT_FALSE(BTF.parse(Mock1.makeObj())); EXPECT_TRUE(BTF.findLineInfo({16, 1})); EXPECT_TRUE(BTF.findLineInfo({0, 2})); // Break the reference to "bar" section name, thus making // information about "bar" line numbers unavailable. Mock2.Ext.Lines.Bar.Sec.SecNameOff = 100500; EXPECT_FALSE(BTF.parse(Mock2.makeObj())); EXPECT_TRUE(BTF.findLineInfo({16, 1})); // Make sure that "bar" no longer available (its index is 2). EXPECT_FALSE(BTF.findLineInfo({0, 2})); } TEST(BTFParserTest, btfContext) { MockData1 Mock; BTFParser BTF; std::unique_ptr Ctx = BTFContext::create(Mock.makeObj()); DILineInfo I1 = Ctx->getLineInfoForAddress({16, 1}); EXPECT_EQ(I1.Line, 7u); EXPECT_EQ(I1.Column, 1u); EXPECT_EQ(I1.FileName, "a.c"); EXPECT_EQ(I1.LineSource, "first line"); DILineInfo I2 = Ctx->getLineInfoForAddress({24, 1}); EXPECT_EQ(I2.Line, 0u); EXPECT_EQ(I2.Column, 0u); EXPECT_EQ(I2.FileName, DILineInfo::BadString); EXPECT_EQ(I2.LineSource, std::nullopt); } 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