Zequan Wu 6dbe82f061
[NFC][DebugInfo] Wrap DILineInfo return type with std::optional to handle missing debug info. (#129792)
Currently, `DIContext::getLineInfoForAddress` and
`DIContext::getLineInfoForDataAddress` returns empty DILineInfo when the
debug info is missing for the given address. This is not differentiable
with the case when debug info is found for the given address but the
debug info is default value (filename:linenum is <invalid>:0).

This change wraps the return types of `DIContext::getLineInfoForAddress`
and `DIContext::getLineInfoForDataAddress` with `std::optional`.
2025-03-17 17:01:06 -04:00

1086 lines
36 KiB
C++

//===-- 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 <typename T>
static yaml::BinaryRef makeBinRef(const T *Ptr, size_t Size = sizeof(T)) {
return yaml::BinaryRef(ArrayRef<uint8_t>((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<ObjectFile> 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;
}
};
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<BTFContext> Ctx = BTFContext::create(Mock.makeObj());
std::optional<DILineInfo> I1 = Ctx->getLineInfoForAddress({16, 1});
EXPECT_TRUE(I1.has_value());
EXPECT_EQ(I1->Line, 7u);
EXPECT_EQ(I1->Column, 1u);
EXPECT_EQ(I1->FileName, "a.c");
EXPECT_EQ(I1->LineSource, "first line");
std::optional<DILineInfo> I2 = Ctx->getLineInfoForAddress({24, 1});
EXPECT_FALSE(I2.has_value());
}
static uint32_t mkInfo(uint32_t Kind) { return Kind << 24; }
template <typename T> static void append(std::string &S, const T &What) {
S.append((const char *)&What, sizeof(What));
}
class MockData2 {
SmallString<0> ObjStorage;
std::unique_ptr<ObjectFile> 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 <typename T> 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;
}
};
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}), "<local_type_id> [1] struct foo");
SYMBOLIZE(SA({8, 1}), "<target_type_id> [1] struct foo");
SYMBOLIZE(SA({16, 1}), "<type_exists> [1] struct foo");
SYMBOLIZE(SA({24, 1}), "<type_matches> [1] struct foo");
SYMBOLIZE(SA({32, 1}), "<type_size> [1] struct foo");
SYMBOLIZE(SA({40, 1}), "<type_size> [2] fwd union bar");
SYMBOLIZE(SA({48, 1}), "<type_size> [3] fwd struct buz");
SYMBOLIZE(SA({56, 1}), "<type_size> [42] '0' <unknown type id: 42>");
SYMBOLIZE(SA({64, 1}),
"<type_size> [1] '10' "
"<unexpected type-based relocation spec: should be '0'>");
}
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}), "<enumval_exists> [1] enum foo::U = 1");
SYMBOLIZE(SA({8, 1}), "<enumval_value> [1] enum foo::V = 2");
SYMBOLIZE(
SA({16, 1}),
"<enumval_value> [1] '1:0' <unexpected enumval relocation spec size>");
SYMBOLIZE(
SA({24, 1}),
"<enumval_value> [2] '1' <unexpected type kind for enum relocation: 1>");
SYMBOLIZE(SA({32, 1}), "<enumval_value> [1] '42' <bad value index: 42>");
SYMBOLIZE(SA({40, 1}), "<enumval_exists> [3] enum bar::A = 1");
SYMBOLIZE(SA({48, 1}), "<enumval_value> [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}), "<enumval_value> [2] typedef a::U = 1");
SYMBOLIZE(SA({8, 1}), "<enumval_value> [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 { <bad type> u[2]; <bad type> 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.<something>.
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}), "<byte_off> [4] struct bar::v (0:1)");
SYMBOLIZE(SA({8, 1}), "<byte_sz> [4] struct bar::v (0:1)");
SYMBOLIZE(SA({16, 1}), "<field_exists> [4] struct bar::v (0:1)");
SYMBOLIZE(SA({24, 1}), "<signed> [4] struct bar::v (0:1)");
SYMBOLIZE(SA({32, 1}), "<lshift_u64> [4] struct bar::v (0:1)");
SYMBOLIZE(SA({40, 1}), "<rshift_u64> [4] struct bar::v (0:1)");
SYMBOLIZE(SA({48, 1}), "<byte_off> [4] struct bar::[7].v (7:1)");
SYMBOLIZE(SA({56, 1}), "<byte_off> [4] struct bar::u[1].a (0:0:1:0)");
SYMBOLIZE(SA({64, 1}), "<byte_off> [4] struct bar::u[1].b (0:0:1:1)");
SYMBOLIZE(SA({72, 1}), "<byte_off> [4] '' <field spec too short>");
SYMBOLIZE(SA({80, 1}),
"<byte_off> [4] '0:2' "
"<member index 2 for spec sub-string 1 is out of range>");
SYMBOLIZE(SA({88, 1}), "<byte_off> [6] '0:0:0' "
"<unknown element type id 42 for spec sub-string 2>");
SYMBOLIZE(SA({96, 1}), "<byte_off> [6] '0:1:0' "
"<unknown member type id 42 for spec sub-string 1>");
SYMBOLIZE(SA({104, 1}), "<byte_off> [4] '0:1:0' "
"<unexpected type kind 1 for spec sub-string 2>");
}
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 <some-var>;
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}), "<byte_off> [3] typedef bar::u (0:0)");
SYMBOLIZE(SA({8, 1}),
"<byte_off> [9] const volatile restrict typedef quux::v.u (0:0:0)");
SYMBOLIZE(SA({16, 1}),
"<byte_off> [11] '0:1:2' <unknown type id: 77 in modifiers chain>");
}
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}), "<type_exists> [1] type_tag(\"tag\") void");
SYMBOLIZE(SA({8, 1}), "<type_exists> [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}), "<type_exists> [1] '' <modifiers chain is too long>");
}
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}), "<type_exists> [2] struct <anon 2>");
SYMBOLIZE(SA({8, 1}), "<byte_off> [2] struct <anon 2>::<anon 0> (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}),
"<reloc kind #777> [0] '' <unknown relocation kind: 777>");
SYMBOLIZE(SA({8, 1}), "<byte_off> [1] 'abc' <spec string is not a number>");
SYMBOLIZE(SA({16, 1}),
"<byte_off> [1] '0#' <unexpected spec string delimiter: '#'>");
}
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