llvm-project/llvm/unittests/Object/ELFObjectFileTest.cpp
Rahman Lavaee acec6419e8
[SHT_LLVM_BB_ADDR_MAP] Allow basic-block-sections and labels be used together by decoupling the handling of the two features. (#74128)
Today `-split-machine-functions` and `-fbasic-block-sections={all,list}`
cannot be combined with `-basic-block-sections=labels` (the labels
option will be ignored).
The inconsistency comes from the way basic block address map -- the
underlying mechanism for basic block labels -- encodes basic block
addresses
(https://lists.llvm.org/pipermail/llvm-dev/2020-July/143512.html).
Specifically, basic block offsets are computed relative to the function
begin symbol. This relies on functions being contiguous which is not the
case for MFS and basic block section binaries. This means Propeller
cannot use binary profiles collected from these binaries, which limits
the applicability of Propeller for iterative optimization.
    
To make the `SHT_LLVM_BB_ADDR_MAP` feature work with basic block section
binaries, we propose modifying the encoding of this section as follows.

First let us review the current encoding which emits the address of each
function and its number of basic blocks, followed by basic block entries
for each basic block.

| | |
|--|--|
| Address of the function | Function Address |
|  Number of basic blocks in this function | NumBlocks |
|  BB entry 1
|  BB entry 2
|   ...
|  BB entry #NumBlocks
    
To make this work for basic block sections, we treat each basic block
section similar to a function, except that basic block sections of the
same function must be encapsulated in the same structure so we can map
all of them to their single function.
    
We modify the encoding to first emit the number of basic block sections
(BB ranges) in the function. Then we emit the address map of each basic
block section section as before: the base address of the section, its
number of blocks, and BB entries for its basic block. The first section
in the BB address map is always the function entry section.
| | |
|--|--|
|  Number of sections for this function   | NumBBRanges |
| Section 1 begin address                     | BaseAddress[1]  |
| Number of basic blocks in section 1 | NumBlocks[1]    |
| BB entries for Section 1
|..................|
| Section #NumBBRanges begin address | BaseAddress[NumBBRanges] |
| Number of basic blocks in section #NumBBRanges |
NumBlocks[NumBBRanges] |
| BB entries for Section #NumBBRanges
    
The encoding of basic block entries remains as before with the minor
change that each basic block offset is now computed relative to the
begin symbol of its containing BB section.
    
This patch adds a new boolean codegen option `-basic-block-address-map`.
Correspondingly, the front-end flag `-fbasic-block-address-map` and LLD
flag `--lto-basic-block-address-map` are introduced.
Analogously, we add a new TargetOption field `BBAddrMap`. This means BB
address maps are either generated for all functions in the compiling
unit, or for none (depending on `TargetOptions::BBAddrMap`).
    
This patch keeps the functionality of the old
`-fbasic-block-sections=labels` option but does not remove it. A
subsequent patch will remove the obsolete option.

We refactor the `BasicBlockSections` pass by separating the BB address
map and BB sections handing to their own functions (named
`handleBBAddrMap` and `handleBBSections`). `handleBBSections` renumbers
basic blocks and places them in their assigned sections.
`handleBBAddrMap` is invoked after `handleBBSections` (if requested) and
only renumbers the blocks.
  - New tests added:
- Two tests basic-block-address-map-with-basic-block-sections.ll and
basic-block-address-map-with-mfs.ll to exercise the combination of
`-basic-block-address-map` with `-basic-block-sections=list` and
'-split-machine-functions`.
- A driver sanity test for the `-fbasic-block-address-map` option
(basic-block-address-map.c).
- An LLD test for testing the `--lto-basic-block-address-map` option.
This reuses the LLVM IR from `lld/test/ELF/lto/basic-block-sections.ll`.
- Renamed and modified the two existing codegen tests for basic block
address map (`basic-block-sections-labels-functions-sections.ll` and
`basic-block-sections-labels.ll`)
- Removed `SHT_LLVM_BB_ADDR_MAP_V0` tests. Full deprecation of
`SHT_LLVM_BB_ADDR_MAP_V0` and `SHT_LLVM_BB_ADDR_MAP` version less than 2
will happen in a separate PR in a few months.
2024-02-01 17:50:46 -08:00

1507 lines
54 KiB
C++

//===- ELFObjectFileTest.cpp - Tests for ELFObjectFile --------------------===//
//
// 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/Object/ELFObjectFile.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ObjectYAML/yaml2obj.h"
#include "llvm/Support/BlockFrequency.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
#include "llvm/Support/thread.h"
#include "llvm/TargetParser/Host.h"
using namespace llvm;
using namespace llvm::object;
namespace {
// A struct to initialize a buffer to represent an ELF object file.
struct DataForTest {
std::vector<uint8_t> Data;
template <typename T>
std::vector<uint8_t> makeElfData(uint8_t Class, uint8_t Encoding,
uint16_t Machine, uint8_t OS,
uint16_t Flags) {
T Ehdr{}; // Zero-initialise the header.
Ehdr.e_ident[ELF::EI_MAG0] = 0x7f;
Ehdr.e_ident[ELF::EI_MAG1] = 'E';
Ehdr.e_ident[ELF::EI_MAG2] = 'L';
Ehdr.e_ident[ELF::EI_MAG3] = 'F';
Ehdr.e_ident[ELF::EI_CLASS] = Class;
Ehdr.e_ident[ELF::EI_DATA] = Encoding;
Ehdr.e_ident[ELF::EI_VERSION] = 1;
Ehdr.e_ident[ELF::EI_OSABI] = OS;
Ehdr.e_type = ELF::ET_REL;
Ehdr.e_machine = Machine;
Ehdr.e_version = 1;
Ehdr.e_flags = Flags;
Ehdr.e_ehsize = sizeof(T);
bool IsLittleEndian = Encoding == ELF::ELFDATA2LSB;
if (sys::IsLittleEndianHost != IsLittleEndian) {
sys::swapByteOrder(Ehdr.e_type);
sys::swapByteOrder(Ehdr.e_machine);
sys::swapByteOrder(Ehdr.e_version);
sys::swapByteOrder(Ehdr.e_ehsize);
}
uint8_t *EhdrBytes = reinterpret_cast<uint8_t *>(&Ehdr);
std::vector<uint8_t> Bytes;
std::copy(EhdrBytes, EhdrBytes + sizeof(Ehdr), std::back_inserter(Bytes));
return Bytes;
}
DataForTest(uint8_t Class, uint8_t Encoding, uint16_t Machine,
uint8_t OS = ELF::ELFOSABI_NONE, uint16_t Flags = 0) {
if (Class == ELF::ELFCLASS64)
Data = makeElfData<ELF::Elf64_Ehdr>(Class, Encoding, Machine, OS, Flags);
else {
assert(Class == ELF::ELFCLASS32);
Data = makeElfData<ELF::Elf32_Ehdr>(Class, Encoding, Machine, OS, Flags);
}
}
};
void checkFormatAndArch(const DataForTest &D, StringRef Fmt,
Triple::ArchType Arch) {
Expected<std::unique_ptr<ObjectFile>> ELFObjOrErr =
object::ObjectFile::createELFObjectFile(
MemoryBufferRef(toStringRef(D.Data), "dummyELF"));
ASSERT_THAT_EXPECTED(ELFObjOrErr, Succeeded());
const ObjectFile &File = *(*ELFObjOrErr).get();
EXPECT_EQ(Fmt, File.getFileFormatName());
EXPECT_EQ(Arch, File.getArch());
}
std::array<DataForTest, 4> generateData(uint16_t Machine) {
return {DataForTest(ELF::ELFCLASS32, ELF::ELFDATA2LSB, Machine),
DataForTest(ELF::ELFCLASS32, ELF::ELFDATA2MSB, Machine),
DataForTest(ELF::ELFCLASS64, ELF::ELFDATA2LSB, Machine),
DataForTest(ELF::ELFCLASS64, ELF::ELFDATA2MSB, Machine)};
}
} // namespace
TEST(ELFObjectFileTest, MachineTestForNoneOrUnused) {
std::array<StringRef, 4> Formats = {"elf32-unknown", "elf32-unknown",
"elf64-unknown", "elf64-unknown"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_NONE)))
checkFormatAndArch(Data, Formats[Idx], Triple::UnknownArch);
// Test an arbitrary unused EM_* value (255).
for (auto [Idx, Data] : enumerate(generateData(255)))
checkFormatAndArch(Data, Formats[Idx], Triple::UnknownArch);
}
TEST(ELFObjectFileTest, MachineTestForVE) {
std::array<StringRef, 4> Formats = {"elf32-unknown", "elf32-unknown",
"elf64-ve", "elf64-ve"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_VE)))
checkFormatAndArch(Data, Formats[Idx], Triple::ve);
}
TEST(ELFObjectFileTest, MachineTestForX86_64) {
std::array<StringRef, 4> Formats = {"elf32-x86-64", "elf32-x86-64",
"elf64-x86-64", "elf64-x86-64"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_X86_64)))
checkFormatAndArch(Data, Formats[Idx], Triple::x86_64);
}
TEST(ELFObjectFileTest, MachineTestFor386) {
std::array<StringRef, 4> Formats = {"elf32-i386", "elf32-i386", "elf64-i386",
"elf64-i386"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_386)))
checkFormatAndArch(Data, Formats[Idx], Triple::x86);
}
TEST(ELFObjectFileTest, MachineTestForMIPS) {
std::array<StringRef, 4> Formats = {"elf32-mips", "elf32-mips", "elf64-mips",
"elf64-mips"};
std::array<Triple::ArchType, 4> Archs = {Triple::mipsel, Triple::mips,
Triple::mips64el, Triple::mips64};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_MIPS)))
checkFormatAndArch(Data, Formats[Idx], Archs[Idx]);
}
TEST(ELFObjectFileTest, MachineTestForAMDGPU) {
std::array<StringRef, 4> Formats = {"elf32-amdgpu", "elf32-amdgpu",
"elf64-amdgpu", "elf64-amdgpu"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_AMDGPU)))
checkFormatAndArch(Data, Formats[Idx], Triple::UnknownArch);
}
TEST(ELFObjectFileTest, MachineTestForIAMCU) {
std::array<StringRef, 4> Formats = {"elf32-iamcu", "elf32-iamcu",
"elf64-unknown", "elf64-unknown"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_IAMCU)))
checkFormatAndArch(Data, Formats[Idx], Triple::x86);
}
TEST(ELFObjectFileTest, MachineTestForAARCH64) {
std::array<StringRef, 4> Formats = {"elf32-unknown", "elf32-unknown",
"elf64-littleaarch64",
"elf64-bigaarch64"};
std::array<Triple::ArchType, 4> Archs = {Triple::aarch64, Triple::aarch64_be,
Triple::aarch64, Triple::aarch64_be};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_AARCH64)))
checkFormatAndArch(Data, Formats[Idx], Archs[Idx]);
}
TEST(ELFObjectFileTest, MachineTestForPPC64) {
std::array<StringRef, 4> Formats = {"elf32-unknown", "elf32-unknown",
"elf64-powerpcle", "elf64-powerpc"};
std::array<Triple::ArchType, 4> Archs = {Triple::ppc64le, Triple::ppc64,
Triple::ppc64le, Triple::ppc64};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_PPC64)))
checkFormatAndArch(Data, Formats[Idx], Archs[Idx]);
}
TEST(ELFObjectFileTest, MachineTestForPPC) {
std::array<StringRef, 4> Formats = {"elf32-powerpcle", "elf32-powerpc",
"elf64-unknown", "elf64-unknown"};
std::array<Triple::ArchType, 4> Archs = {Triple::ppcle, Triple::ppc,
Triple::ppcle, Triple::ppc};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_PPC)))
checkFormatAndArch(Data, Formats[Idx], Archs[Idx]);
}
TEST(ELFObjectFileTest, MachineTestForRISCV) {
std::array<StringRef, 4> Formats = {"elf32-littleriscv", "elf32-littleriscv",
"elf64-littleriscv", "elf64-littleriscv"};
std::array<Triple::ArchType, 4> Archs = {Triple::riscv32, Triple::riscv32,
Triple::riscv64, Triple::riscv64};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_RISCV)))
checkFormatAndArch(Data, Formats[Idx], Archs[Idx]);
}
TEST(ELFObjectFileTest, MachineTestForARM) {
std::array<StringRef, 4> Formats = {"elf32-littlearm", "elf32-bigarm",
"elf64-unknown", "elf64-unknown"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_ARM)))
checkFormatAndArch(Data, Formats[Idx], Triple::arm);
}
TEST(ELFObjectFileTest, MachineTestForS390) {
std::array<StringRef, 4> Formats = {"elf32-unknown", "elf32-unknown",
"elf64-s390", "elf64-s390"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_S390)))
checkFormatAndArch(Data, Formats[Idx], Triple::systemz);
}
TEST(ELFObjectFileTest, MachineTestForSPARCV9) {
std::array<StringRef, 4> Formats = {"elf32-unknown", "elf32-unknown",
"elf64-sparc", "elf64-sparc"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_SPARCV9)))
checkFormatAndArch(Data, Formats[Idx], Triple::sparcv9);
}
TEST(ELFObjectFileTest, MachineTestForSPARC) {
std::array<StringRef, 4> Formats = {"elf32-sparc", "elf32-sparc",
"elf64-unknown", "elf64-unknown"};
std::array<Triple::ArchType, 4> Archs = {Triple::sparcel, Triple::sparc,
Triple::sparcel, Triple::sparc};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_SPARC)))
checkFormatAndArch(Data, Formats[Idx], Archs[Idx]);
}
TEST(ELFObjectFileTest, MachineTestForSPARC32PLUS) {
std::array<StringRef, 4> Formats = {"elf32-sparc", "elf32-sparc",
"elf64-unknown", "elf64-unknown"};
std::array<Triple::ArchType, 4> Archs = {Triple::sparcel, Triple::sparc,
Triple::sparcel, Triple::sparc};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_SPARC32PLUS)))
checkFormatAndArch(Data, Formats[Idx], Archs[Idx]);
}
TEST(ELFObjectFileTest, MachineTestForBPF) {
std::array<StringRef, 4> Formats = {"elf32-unknown", "elf32-unknown",
"elf64-bpf", "elf64-bpf"};
std::array<Triple::ArchType, 4> Archs = {Triple::bpfel, Triple::bpfeb,
Triple::bpfel, Triple::bpfeb};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_BPF)))
checkFormatAndArch(Data, Formats[Idx], Archs[Idx]);
}
TEST(ELFObjectFileTest, MachineTestForAVR) {
std::array<StringRef, 4> Formats = {"elf32-avr", "elf32-avr", "elf64-unknown",
"elf64-unknown"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_AVR)))
checkFormatAndArch(Data, Formats[Idx], Triple::avr);
}
TEST(ELFObjectFileTest, MachineTestForHEXAGON) {
std::array<StringRef, 4> Formats = {"elf32-hexagon", "elf32-hexagon",
"elf64-unknown", "elf64-unknown"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_HEXAGON)))
checkFormatAndArch(Data, Formats[Idx], Triple::hexagon);
}
TEST(ELFObjectFileTest, MachineTestForLANAI) {
std::array<StringRef, 4> Formats = {"elf32-lanai", "elf32-lanai",
"elf64-unknown", "elf64-unknown"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_LANAI)))
checkFormatAndArch(Data, Formats[Idx], Triple::lanai);
}
TEST(ELFObjectFileTest, MachineTestForMSP430) {
std::array<StringRef, 4> Formats = {"elf32-msp430", "elf32-msp430",
"elf64-unknown", "elf64-unknown"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_MSP430)))
checkFormatAndArch(Data, Formats[Idx], Triple::msp430);
}
TEST(ELFObjectFileTest, MachineTestForLoongArch) {
std::array<StringRef, 4> Formats = {"elf32-loongarch", "elf32-loongarch",
"elf64-loongarch", "elf64-loongarch"};
std::array<Triple::ArchType, 4> Archs = {
Triple::loongarch32, Triple::loongarch32, Triple::loongarch64,
Triple::loongarch64};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_LOONGARCH)))
checkFormatAndArch(Data, Formats[Idx], Archs[Idx]);
}
TEST(ELFObjectFileTest, MachineTestForCSKY) {
std::array<StringRef, 4> Formats = {"elf32-csky", "elf32-csky",
"elf64-unknown", "elf64-unknown"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_CSKY)))
checkFormatAndArch(Data, Formats[Idx], Triple::csky);
}
TEST(ELFObjectFileTest, MachineTestForXtensa) {
std::array<StringRef, 4> Formats = {"elf32-xtensa", "elf32-xtensa",
"elf64-unknown", "elf64-unknown"};
for (auto [Idx, Data] : enumerate(generateData(ELF::EM_XTENSA)))
checkFormatAndArch(Data, Formats[Idx], Triple::xtensa);
}
TEST(ELFObjectFileTest, CheckOSAndTriple) {
std::tuple<uint16_t, uint8_t, StringRef> Formats[] = {
{ELF::EM_AMDGPU, ELF::ELFOSABI_AMDGPU_HSA, "amdgcn-amd-amdhsa"},
{ELF::EM_X86_64, ELF::ELFOSABI_LINUX, "x86_64--linux"},
{ELF::EM_X86_64, ELF::ELFOSABI_NETBSD, "x86_64--netbsd"},
{ELF::EM_X86_64, ELF::ELFOSABI_HURD, "x86_64--hurd"},
{ELF::EM_X86_64, ELF::ELFOSABI_SOLARIS, "x86_64--solaris"},
{ELF::EM_X86_64, ELF::ELFOSABI_AIX, "x86_64--aix"},
{ELF::EM_X86_64, ELF::ELFOSABI_FREEBSD, "x86_64--freebsd"},
{ELF::EM_X86_64, ELF::ELFOSABI_OPENBSD, "x86_64--openbsd"},
{ELF::EM_CUDA, ELF::ELFOSABI_CUDA, "nvptx64-nvidia-cuda"}};
for (auto [Machine, OS, Triple] : Formats) {
const DataForTest D(ELF::ELFCLASS64, ELF::ELFDATA2LSB, Machine, OS,
ELF::EF_AMDGPU_MACH_AMDGCN_LAST);
Expected<ELF64LEObjectFile> ELFObjOrErr = ELF64LEObjectFile::create(
MemoryBufferRef(toStringRef(D.Data), "dummyELF"));
ASSERT_THAT_EXPECTED(ELFObjOrErr, Succeeded());
auto &ELFObj = *ELFObjOrErr;
llvm::Triple TheTriple = ELFObj.makeTriple();
// The AMDGPU architecture will be unknown on big-endian testers.
if (TheTriple.getArch() == Triple::UnknownArch)
continue;
EXPECT_EQ(Triple, TheTriple.getTriple());
}
}
// ELF relative relocation type test.
TEST(ELFObjectFileTest, RelativeRelocationTypeTest) {
EXPECT_EQ(ELF::R_CKCORE_RELATIVE, getELFRelativeRelocationType(ELF::EM_CSKY));
}
template <class ELFT>
static Expected<ELFObjectFile<ELFT>> toBinary(SmallVectorImpl<char> &Storage,
StringRef Yaml) {
raw_svector_ostream OS(Storage);
yaml::Input YIn(Yaml);
if (!yaml::convertYAML(YIn, OS, [](const Twine &Msg) {}))
return createStringError(std::errc::invalid_argument,
"unable to convert YAML");
return ELFObjectFile<ELFT>::create(MemoryBufferRef(OS.str(), "dummyELF"));
}
// Check we are able to create an ELFObjectFile even when the content of the
// SHT_SYMTAB_SHNDX section can't be read properly.
TEST(ELFObjectFileTest, InvalidSymtabShndxTest) {
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ExpectedFile = toBinary<ELF64LE>(Storage, R"(
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_REL
Sections:
- Name: .symtab_shndx
Type: SHT_SYMTAB_SHNDX
Entries: [ 0 ]
ShSize: 0xFFFFFFFF
)");
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
}
// Test that we are able to create an ELFObjectFile even when loadable segments
// are unsorted by virtual address.
// Test that ELFFile<ELFT>::toMappedAddr works properly in this case.
TEST(ELFObjectFileTest, InvalidLoadSegmentsOrderTest) {
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ExpectedFile = toBinary<ELF64LE>(Storage, R"(
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_EXEC
Sections:
- Name: .foo
Type: SHT_PROGBITS
Address: 0x1000
Offset: 0x3000
ContentArray: [ 0x11 ]
- Name: .bar
Type: SHT_PROGBITS
Address: 0x2000
Offset: 0x4000
ContentArray: [ 0x99 ]
ProgramHeaders:
- Type: PT_LOAD
VAddr: 0x2000
FirstSec: .bar
LastSec: .bar
- Type: PT_LOAD
VAddr: 0x1000
FirstSec: .foo
LastSec: .foo
)");
ASSERT_THAT_EXPECTED(ExpectedFile, Succeeded());
std::string WarnString;
auto ToMappedAddr = [&](uint64_t Addr) -> const uint8_t * {
Expected<const uint8_t *> DataOrErr =
ExpectedFile->getELFFile().toMappedAddr(Addr, [&](const Twine &Msg) {
EXPECT_TRUE(WarnString.empty());
WarnString = Msg.str();
return Error::success();
});
if (!DataOrErr) {
ADD_FAILURE() << toString(DataOrErr.takeError());
return nullptr;
}
EXPECT_TRUE(WarnString ==
"loadable segments are unsorted by virtual address");
WarnString = "";
return *DataOrErr;
};
const uint8_t *Data = ToMappedAddr(0x1000);
ASSERT_TRUE(Data);
MemoryBufferRef Buf = ExpectedFile->getMemoryBufferRef();
EXPECT_EQ((const char *)Data - Buf.getBufferStart(), 0x3000);
EXPECT_EQ(Data[0], 0x11);
Data = ToMappedAddr(0x2000);
ASSERT_TRUE(Data);
Buf = ExpectedFile->getMemoryBufferRef();
EXPECT_EQ((const char *)Data - Buf.getBufferStart(), 0x4000);
EXPECT_EQ(Data[0], 0x99);
}
// This is a test for API that is related to symbols.
// We check that errors are properly reported here.
TEST(ELFObjectFileTest, InvalidSymbolTest) {
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr = toBinary<ELF64LE>(Storage, R"(
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_DYN
Machine: EM_X86_64
Sections:
- Name: .symtab
Type: SHT_SYMTAB
)");
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
const ELFFile<ELF64LE> &Elf = ElfOrErr->getELFFile();
const ELFObjectFile<ELF64LE> &Obj = *ElfOrErr;
Expected<const typename ELF64LE::Shdr *> SymtabSecOrErr = Elf.getSection(1);
ASSERT_THAT_EXPECTED(SymtabSecOrErr, Succeeded());
ASSERT_EQ((*SymtabSecOrErr)->sh_type, ELF::SHT_SYMTAB);
auto DoCheck = [&](unsigned BrokenSymIndex, const char *ErrMsg) {
ELFSymbolRef BrokenSym = Obj.toSymbolRef(*SymtabSecOrErr, BrokenSymIndex);
// 1) Check the behavior of ELFObjectFile<ELFT>::getSymbolName().
// SymbolRef::getName() calls it internally. We can't test it directly,
// because it is protected.
EXPECT_THAT_ERROR(BrokenSym.getName().takeError(),
FailedWithMessage(ErrMsg));
// 2) Check the behavior of ELFObjectFile<ELFT>::getSymbol().
EXPECT_THAT_ERROR(Obj.getSymbol(BrokenSym.getRawDataRefImpl()).takeError(),
FailedWithMessage(ErrMsg));
// 3) Check the behavior of ELFObjectFile<ELFT>::getSymbolSection().
// SymbolRef::getSection() calls it internally. We can't test it
// directly, because it is protected.
EXPECT_THAT_ERROR(BrokenSym.getSection().takeError(),
FailedWithMessage(ErrMsg));
// 4) Check the behavior of ELFObjectFile<ELFT>::getSymbolFlags().
// SymbolRef::getFlags() calls it internally. We can't test it directly,
// because it is protected.
EXPECT_THAT_ERROR(BrokenSym.getFlags().takeError(),
FailedWithMessage(ErrMsg));
// 5) Check the behavior of ELFObjectFile<ELFT>::getSymbolType().
// SymbolRef::getType() calls it internally. We can't test it directly,
// because it is protected.
EXPECT_THAT_ERROR(BrokenSym.getType().takeError(),
FailedWithMessage(ErrMsg));
// 6) Check the behavior of ELFObjectFile<ELFT>::getSymbolAddress().
// SymbolRef::getAddress() calls it internally. We can't test it
// directly, because it is protected.
EXPECT_THAT_ERROR(BrokenSym.getAddress().takeError(),
FailedWithMessage(ErrMsg));
// Finally, check the `ELFFile<ELFT>::getEntry` API. This is an underlying
// method that generates errors for all cases above.
EXPECT_THAT_EXPECTED(
Elf.getEntry<typename ELF64LE::Sym>(**SymtabSecOrErr, 0), Succeeded());
EXPECT_THAT_ERROR(
Elf.getEntry<typename ELF64LE::Sym>(**SymtabSecOrErr, BrokenSymIndex)
.takeError(),
FailedWithMessage(ErrMsg));
};
// We create a symbol with an index that is too large to exist in the symbol
// table.
DoCheck(0x1, "can't read an entry at 0x18: it goes past the end of the "
"section (0x18)");
// We create a symbol with an index that is too large to exist in the object.
DoCheck(0xFFFFFFFF, "can't read an entry at 0x17ffffffe8: it goes past the "
"end of the section (0x18)");
}
// Tests for error paths of the ELFFile::decodeBBAddrMap API.
TEST(ELFObjectFileTest, InvalidDecodeBBAddrMap) {
StringRef CommonYamlString(R"(
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_EXEC
Sections:
- Type: SHT_LLVM_BB_ADDR_MAP
Name: .llvm_bb_addr_map
Entries:
)");
auto DoCheck = [&](StringRef YamlString, const char *ErrMsg) {
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr =
toBinary<ELF64LE>(Storage, YamlString);
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
const ELFFile<ELF64LE> &Elf = ElfOrErr->getELFFile();
Expected<const typename ELF64LE::Shdr *> BBAddrMapSecOrErr =
Elf.getSection(1);
ASSERT_THAT_EXPECTED(BBAddrMapSecOrErr, Succeeded());
EXPECT_THAT_ERROR(Elf.decodeBBAddrMap(**BBAddrMapSecOrErr).takeError(),
FailedWithMessage(ErrMsg));
};
// Check that we can detect unsupported versions.
SmallString<128> UnsupportedVersionYamlString(CommonYamlString);
UnsupportedVersionYamlString += R"(
- Version: 3
BBRanges:
- BaseAddress: 0x11111
BBEntries:
- AddressOffset: 0x0
Size: 0x1
Metadata: 0x2
)";
{
SCOPED_TRACE("unsupported version");
DoCheck(UnsupportedVersionYamlString,
"unsupported SHT_LLVM_BB_ADDR_MAP version: 3");
}
SmallString<128> ZeroBBRangesYamlString(CommonYamlString);
ZeroBBRangesYamlString += R"(
- Version: 2
Feature: 0x8
BBRanges: []
)";
{
SCOPED_TRACE("zero bb ranges");
DoCheck(ZeroBBRangesYamlString,
"invalid zero number of BB ranges at offset 3 in "
"SHT_LLVM_BB_ADDR_MAP section with index 1");
}
SmallString<128> CommonVersionedYamlString(CommonYamlString);
CommonVersionedYamlString += R"(
- Version: 2
BBRanges:
- BaseAddress: 0x11111
BBEntries:
- AddressOffset: 0x0
Size: 0x1
Metadata: 0x2
)";
// Check that we can detect the malformed encoding when the section is
// truncated.
SmallString<128> TruncatedYamlString(CommonVersionedYamlString);
TruncatedYamlString += R"(
ShSize: 0xb
)";
{
SCOPED_TRACE("truncated section");
DoCheck(TruncatedYamlString,
"unable to decode LEB128 at offset 0x0000000b: "
"malformed uleb128, extends past end");
}
// Check that we can detect when the encoded BB entry fields exceed the UINT32
// limit.
SmallVector<SmallString<128>, 3> OverInt32LimitYamlStrings(
3, CommonVersionedYamlString);
OverInt32LimitYamlStrings[0] += R"(
- ID: 1
AddressOffset: 0x100000000
Size: 0xFFFFFFFF
Metadata: 0xFFFFFFFF
)";
OverInt32LimitYamlStrings[1] += R"(
- ID: 2
AddressOffset: 0xFFFFFFFF
Size: 0x100000000
Metadata: 0xFFFFFFFF
)";
OverInt32LimitYamlStrings[2] += R"(
- ID: 3
AddressOffset: 0xFFFFFFFF
Size: 0xFFFFFFFF
Metadata: 0x100000000
)";
{
SCOPED_TRACE("overlimit fields");
DoCheck(OverInt32LimitYamlStrings[0],
"ULEB128 value at offset 0x10 exceeds UINT32_MAX (0x100000000)");
DoCheck(OverInt32LimitYamlStrings[1],
"ULEB128 value at offset 0x15 exceeds UINT32_MAX (0x100000000)");
DoCheck(OverInt32LimitYamlStrings[2],
"ULEB128 value at offset 0x1a exceeds UINT32_MAX (0x100000000)");
}
// Check the proper error handling when the section has fields exceeding
// UINT32 and is also truncated. This is for checking that we don't generate
// unhandled errors.
SmallVector<SmallString<128>, 3> OverInt32LimitAndTruncated(
3, OverInt32LimitYamlStrings[1]);
// Truncate before the end of the 5-byte field.
OverInt32LimitAndTruncated[0] += R"(
ShSize: 0x19
)";
// Truncate at the end of the 5-byte field.
OverInt32LimitAndTruncated[1] += R"(
ShSize: 0x1a
)";
// Truncate after the end of the 5-byte field.
OverInt32LimitAndTruncated[2] += R"(
ShSize: 0x1b
)";
{
SCOPED_TRACE("overlimit fields, truncated section");
DoCheck(OverInt32LimitAndTruncated[0],
"unable to decode LEB128 at offset 0x00000015: malformed uleb128, "
"extends past end");
DoCheck(OverInt32LimitAndTruncated[1],
"ULEB128 value at offset 0x15 exceeds UINT32_MAX (0x100000000)");
DoCheck(OverInt32LimitAndTruncated[2],
"ULEB128 value at offset 0x15 exceeds UINT32_MAX (0x100000000)");
}
// Check for proper error handling when the 'NumBlocks' field is overridden
// with an out-of-range value.
SmallString<128> OverLimitNumBlocks(CommonVersionedYamlString);
OverLimitNumBlocks += R"(
NumBlocks: 0x100000000
)";
{
SCOPED_TRACE("overlimit 'NumBlocks' field");
DoCheck(OverLimitNumBlocks,
"ULEB128 value at offset 0xa exceeds UINT32_MAX (0x100000000)");
}
// Check for proper error handling when the 'NumBBRanges' field is overridden
// with an out-of-range value.
SmallString<128> OverLimitNumBBRanges(CommonVersionedYamlString);
OverLimitNumBBRanges += R"(
NumBBRanges: 0x100000000
Feature: 0x8
)";
DoCheck(OverLimitNumBBRanges,
"ULEB128 value at offset 0x2 exceeds UINT32_MAX (0x100000000)");
}
// Test for the ELFObjectFile::readBBAddrMap API.
TEST(ELFObjectFileTest, ReadBBAddrMap) {
StringRef CommonYamlString(R"(
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_EXEC
Sections:
- Name: .llvm_bb_addr_map_1
Type: SHT_LLVM_BB_ADDR_MAP
Link: 1
Entries:
- Version: 2
BBRanges:
- BaseAddress: 0x11111
BBEntries:
- ID: 1
AddressOffset: 0x0
Size: 0x1
Metadata: 0x2
- Name: .llvm_bb_addr_map_2
Type: SHT_LLVM_BB_ADDR_MAP
Link: 1
Entries:
- Version: 2
Feature: 0x8
BBRanges:
- BaseAddress: 0x22222
BBEntries:
- ID: 2
AddressOffset: 0x0
Size: 0x2
Metadata: 0x4
- BaseAddress: 0xFFFFF
BBEntries:
- ID: 15
AddressOffset: 0xF0
Size: 0xF1
Metadata: 0x1F
- Name: .llvm_bb_addr_map_3
Type: SHT_LLVM_BB_ADDR_MAP
Link: 2
Entries:
- Version: 1
BBRanges:
- BaseAddress: 0x33333
BBEntries:
- ID: 0
AddressOffset: 0x0
Size: 0x3
Metadata: 0x6
- Name: .llvm_bb_addr_map_4
Type: SHT_LLVM_BB_ADDR_MAP
# Link: 0 (by default, can be overriden)
Entries:
- Version: 2
BBRanges:
- BaseAddress: 0x44444
BBEntries:
- ID: 0
AddressOffset: 0x0
Size: 0x4
Metadata: 0x18
)");
BBAddrMap E1 = {
{{0x11111, {{1, 0x0, 0x1, {false, true, false, false, false}}}}}};
BBAddrMap E2 = {
{{0x22222, {{2, 0x0, 0x2, {false, false, true, false, false}}}},
{0xFFFFF, {{15, 0xF0, 0xF1, {true, true, true, true, true}}}}}};
BBAddrMap E3 = {
{{0x33333, {{0, 0x0, 0x3, {false, true, true, false, false}}}}}};
BBAddrMap E4 = {
{{0x44444, {{0, 0x0, 0x4, {false, false, false, true, true}}}}}};
std::vector<BBAddrMap> Section0BBAddrMaps = {E4};
std::vector<BBAddrMap> Section1BBAddrMaps = {E3};
std::vector<BBAddrMap> Section2BBAddrMaps = {E1, E2};
std::vector<BBAddrMap> AllBBAddrMaps = {E1, E2, E3, E4};
auto DoCheckSucceeds = [&](StringRef YamlString,
std::optional<unsigned> TextSectionIndex,
std::vector<BBAddrMap> ExpectedResult) {
SCOPED_TRACE("for TextSectionIndex: " +
(TextSectionIndex ? llvm::Twine(*TextSectionIndex) : "{}") +
" and object yaml:\n" + YamlString);
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr =
toBinary<ELF64LE>(Storage, YamlString);
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
Expected<const typename ELF64LE::Shdr *> BBAddrMapSecOrErr =
ElfOrErr->getELFFile().getSection(1);
ASSERT_THAT_EXPECTED(BBAddrMapSecOrErr, Succeeded());
auto BBAddrMaps = ElfOrErr->readBBAddrMap(TextSectionIndex);
ASSERT_THAT_EXPECTED(BBAddrMaps, Succeeded());
EXPECT_EQ(*BBAddrMaps, ExpectedResult);
};
auto DoCheckFails = [&](StringRef YamlString,
std::optional<unsigned> TextSectionIndex,
const char *ErrMsg) {
SCOPED_TRACE("for TextSectionIndex: " +
(TextSectionIndex ? llvm::Twine(*TextSectionIndex) : "{}") +
" and object yaml:\n" + YamlString);
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr =
toBinary<ELF64LE>(Storage, YamlString);
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
Expected<const typename ELF64LE::Shdr *> BBAddrMapSecOrErr =
ElfOrErr->getELFFile().getSection(1);
ASSERT_THAT_EXPECTED(BBAddrMapSecOrErr, Succeeded());
EXPECT_THAT_ERROR(ElfOrErr->readBBAddrMap(TextSectionIndex).takeError(),
FailedWithMessage(ErrMsg));
};
{
SCOPED_TRACE("normal sections");
// Check that we can retrieve the data in the normal case.
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/std::nullopt,
AllBBAddrMaps);
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/0,
Section0BBAddrMaps);
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/2,
Section1BBAddrMaps);
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/1,
Section2BBAddrMaps);
// Check that when no bb-address-map section is found for a text section,
// we return an empty result.
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/3, {});
}
// Check that we detect when a bb-addr-map section is linked to an invalid
// (not present) section.
SmallString<128> InvalidLinkedYamlString(CommonYamlString);
InvalidLinkedYamlString += R"(
Link: 121
)";
DoCheckFails(InvalidLinkedYamlString, /*TextSectionIndex=*/4,
"unable to get the linked-to section for "
"SHT_LLVM_BB_ADDR_MAP section with index 4: invalid section "
"index: 121");
{
SCOPED_TRACE("invalid linked section");
// Linked sections are not checked when we don't target a specific text
// section.
DoCheckSucceeds(InvalidLinkedYamlString, /*TextSectionIndex=*/std::nullopt,
AllBBAddrMaps);
}
// Check that we can detect when bb-address-map decoding fails.
SmallString<128> TruncatedYamlString(CommonYamlString);
TruncatedYamlString += R"(
ShSize: 0xa
)";
{
SCOPED_TRACE("truncated section");
DoCheckFails(TruncatedYamlString, /*TextSectionIndex=*/std::nullopt,
"unable to read SHT_LLVM_BB_ADDR_MAP section with index 4: "
"unable to decode LEB128 at offset 0x0000000a: malformed "
"uleb128, extends past end");
// Check that we can read the other section's bb-address-maps which are
// valid.
DoCheckSucceeds(TruncatedYamlString, /*TextSectionIndex=*/2,
Section1BBAddrMaps);
}
}
// Tests for error paths of the ELFFile::decodeBBAddrMap with PGOAnalysisMap
// API.
TEST(ELFObjectFileTest, InvalidDecodePGOAnalysisMap) {
StringRef CommonYamlString(R"(
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_EXEC
Sections:
- Type: SHT_LLVM_BB_ADDR_MAP
Name: .llvm_bb_addr_map
Entries:
)");
auto DoCheck = [&](StringRef YamlString, const char *ErrMsg) {
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr =
toBinary<ELF64LE>(Storage, YamlString);
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
const ELFFile<ELF64LE> &Elf = ElfOrErr->getELFFile();
Expected<const typename ELF64LE::Shdr *> BBAddrMapSecOrErr =
Elf.getSection(1);
ASSERT_THAT_EXPECTED(BBAddrMapSecOrErr, Succeeded());
std::vector<PGOAnalysisMap> PGOAnalyses;
EXPECT_THAT_ERROR(
Elf.decodeBBAddrMap(**BBAddrMapSecOrErr, nullptr, &PGOAnalyses)
.takeError(),
FailedWithMessage(ErrMsg));
};
// Check that we can detect unsupported versions that are too old.
SmallString<128> UnsupportedLowVersionYamlString(CommonYamlString);
UnsupportedLowVersionYamlString += R"(
- Version: 1
Feature: 0x4
BBRanges:
- BBEntries:
- AddressOffset: 0x0
Size: 0x1
Metadata: 0x2
)";
{
SCOPED_TRACE("unsupported version");
DoCheck(UnsupportedLowVersionYamlString,
"version should be >= 2 for SHT_LLVM_BB_ADDR_MAP when PGO features "
"are enabled: version = 1 feature = 4");
}
SmallString<128> CommonVersionedYamlString(CommonYamlString);
CommonVersionedYamlString += R"(
- Version: 2
BBRanges:
- BBEntries:
- ID: 1
AddressOffset: 0x0
Size: 0x1
Metadata: 0x2
)";
// Check that we fail when function entry count is enabled but not provided.
SmallString<128> MissingFuncEntryCount(CommonYamlString);
MissingFuncEntryCount += R"(
- Version: 2
Feature: 0x01
)";
{
SCOPED_TRACE("missing function entry count");
DoCheck(MissingFuncEntryCount,
"unexpected end of data at offset 0x2 while reading [0x2, 0xa)");
}
// Check that we fail when basic block frequency is enabled but not provided.
SmallString<128> MissingBBFreq(CommonYamlString);
MissingBBFreq += R"(
- Version: 2
Feature: 0x02
BBRanges:
- BBEntries:
- ID: 1
AddressOffset: 0x0
Size: 0x1
Metadata: 0x2
)";
{
SCOPED_TRACE("missing bb frequency");
DoCheck(MissingBBFreq, "unable to decode LEB128 at offset 0x0000000f: "
"malformed uleb128, extends past end");
}
// Check that we fail when branch probability is enabled but not provided.
SmallString<128> MissingBrProb(CommonYamlString);
MissingBrProb += R"(
- Version: 2
Feature: 0x04
BBRanges:
- BBEntries:
- ID: 1
AddressOffset: 0x0
Size: 0x1
Metadata: 0x6
- ID: 2
AddressOffset: 0x1
Size: 0x1
Metadata: 0x2
- ID: 3
AddressOffset: 0x2
Size: 0x1
Metadata: 0x2
PGOAnalyses:
- PGOBBEntries:
- Successors:
- ID: 2
BrProb: 0x80000000
- ID: 3
BrProb: 0x80000000
- Successors:
- ID: 3
BrProb: 0xF0000000
)";
{
SCOPED_TRACE("missing branch probability");
DoCheck(MissingBrProb, "unable to decode LEB128 at offset 0x00000017: "
"malformed uleb128, extends past end");
}
}
// Test for the ELFObjectFile::readBBAddrMap API with PGOAnalysisMap.
TEST(ELFObjectFileTest, ReadPGOAnalysisMap) {
StringRef CommonYamlString(R"(
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_EXEC
Sections:
- Name: .llvm_bb_addr_map_1
Type: SHT_LLVM_BB_ADDR_MAP
Link: 1
Entries:
- Version: 2
Feature: 0x1
BBRanges:
- BaseAddress: 0x11111
BBEntries:
- ID: 1
AddressOffset: 0x0
Size: 0x1
Metadata: 0x2
PGOAnalyses:
- FuncEntryCount: 892
- Name: .llvm_bb_addr_map_2
Type: SHT_LLVM_BB_ADDR_MAP
Link: 1
Entries:
- Version: 2
Feature: 0x2
BBRanges:
- BaseAddress: 0x22222
BBEntries:
- ID: 2
AddressOffset: 0x0
Size: 0x2
Metadata: 0x4
PGOAnalyses:
- PGOBBEntries:
- BBFreq: 343
- Name: .llvm_bb_addr_map_3
Type: SHT_LLVM_BB_ADDR_MAP
Link: 2
Entries:
- Version: 2
Feature: 0x4
BBRanges:
- BaseAddress: 0x33333
BBEntries:
- ID: 0
AddressOffset: 0x0
Size: 0x3
Metadata: 0x6
- ID: 1
AddressOffset: 0x0
Size: 0x3
Metadata: 0x4
- ID: 2
AddressOffset: 0x0
Size: 0x3
Metadata: 0x0
PGOAnalyses:
- PGOBBEntries:
- Successors:
- ID: 1
BrProb: 0x11111111
- ID: 2
BrProb: 0xeeeeeeee
- Successors:
- ID: 2
BrProb: 0xffffffff
- Successors: []
- Name: .llvm_bb_addr_map_4
Type: SHT_LLVM_BB_ADDR_MAP
# Link: 0 (by default, can be overriden)
Entries:
- Version: 2
Feature: 0x7
BBRanges:
- BaseAddress: 0x44444
BBEntries:
- ID: 0
AddressOffset: 0x0
Size: 0x4
Metadata: 0x18
- ID: 1
AddressOffset: 0x0
Size: 0x4
Metadata: 0x0
- ID: 2
AddressOffset: 0x0
Size: 0x4
Metadata: 0x0
- ID: 3
AddressOffset: 0x0
Size: 0x4
Metadata: 0x0
PGOAnalyses:
- FuncEntryCount: 1000
PGOBBEntries:
- BBFreq: 1000
Successors:
- ID: 1
BrProb: 0x22222222
- ID: 2
BrProb: 0x33333333
- ID: 3
BrProb: 0xaaaaaaaa
- BBFreq: 133
Successors:
- ID: 2
BrProb: 0x11111111
- ID: 3
BrProb: 0xeeeeeeee
- BBFreq: 18
Successors:
- ID: 3
BrProb: 0xffffffff
- BBFreq: 1000
Successors: []
- Name: .llvm_bb_addr_map_5
Type: SHT_LLVM_BB_ADDR_MAP
# Link: 0 (by default, can be overriden)
Entries:
- Version: 2
Feature: 0x0
BBRanges:
- BaseAddress: 0x55555
BBEntries:
- ID: 2
AddressOffset: 0x0
Size: 0x2
Metadata: 0x4
PGOAnalyses: [{}]
- Name: .llvm_bb_addr_map_6
Type: SHT_LLVM_BB_ADDR_MAP
# Link: 0 (by default, can be overriden)
Entries:
- Version: 2
Feature: 0xc
BBRanges:
- BaseAddress: 0x66666
BBEntries:
- ID: 0
AddressOffset: 0x0
Size: 0x6
Metadata: 0x6
- ID: 1
AddressOffset: 0x0
Size: 0x6
Metadata: 0x4
- BaseAddress: 0x666661
BBEntries:
- ID: 2
AddressOffset: 0x0
Size: 0x6
Metadata: 0x0
PGOAnalyses:
- PGOBBEntries:
- Successors:
- ID: 1
BrProb: 0x22222222
- ID: 2
BrProb: 0xcccccccc
- Successors:
- ID: 2
BrProb: 0x88888888
- Successors: []
)");
BBAddrMap E1 = {
{{0x11111, {{1, 0x0, 0x1, {false, true, false, false, false}}}}}};
PGOAnalysisMap P1 = {892, {}, {true, false, false, false}};
BBAddrMap E2 = {
{{0x22222, {{2, 0x0, 0x2, {false, false, true, false, false}}}}}};
PGOAnalysisMap P2 = {
{}, {{BlockFrequency(343), {}}}, {false, true, false, false}};
BBAddrMap E3 = {{{0x33333,
{{0, 0x0, 0x3, {false, true, true, false, false}},
{1, 0x3, 0x3, {false, false, true, false, false}},
{2, 0x6, 0x3, {false, false, false, false, false}}}}}};
PGOAnalysisMap P3 = {{},
{{{},
{{1, BranchProbability::getRaw(0x1111'1111)},
{2, BranchProbability::getRaw(0xeeee'eeee)}}},
{{}, {{2, BranchProbability::getRaw(0xffff'ffff)}}},
{{}, {}}},
{false, false, true, false}};
BBAddrMap E4 = {{{0x44444,
{{0, 0x0, 0x4, {false, false, false, true, true}},
{1, 0x4, 0x4, {false, false, false, false, false}},
{2, 0x8, 0x4, {false, false, false, false, false}},
{3, 0xc, 0x4, {false, false, false, false, false}}}}}};
PGOAnalysisMap P4 = {
1000,
{{BlockFrequency(1000),
{{1, BranchProbability::getRaw(0x2222'2222)},
{2, BranchProbability::getRaw(0x3333'3333)},
{3, BranchProbability::getRaw(0xaaaa'aaaa)}}},
{BlockFrequency(133),
{{2, BranchProbability::getRaw(0x1111'1111)},
{3, BranchProbability::getRaw(0xeeee'eeee)}}},
{BlockFrequency(18), {{3, BranchProbability::getRaw(0xffff'ffff)}}},
{BlockFrequency(1000), {}}},
{true, true, true, false}};
BBAddrMap E5 = {
{{0x55555, {{2, 0x0, 0x2, {false, false, true, false, false}}}}}};
PGOAnalysisMap P5 = {{}, {}, {false, false, false, false}};
BBAddrMap E6 = {
{{0x66666,
{{0, 0x0, 0x6, {false, true, true, false, false}},
{1, 0x6, 0x6, {false, false, true, false, false}}}},
{0x666661, {{2, 0x0, 0x6, {false, false, false, false, false}}}}}};
PGOAnalysisMap P6 = {{},
{{{},
{{1, BranchProbability::getRaw(0x2222'2222)},
{2, BranchProbability::getRaw(0xcccc'cccc)}}},
{{}, {{2, BranchProbability::getRaw(0x8888'8888)}}},
{{}, {}}},
{false, false, true, true}};
std::vector<BBAddrMap> Section0BBAddrMaps = {E4, E5, E6};
std::vector<BBAddrMap> Section1BBAddrMaps = {E3};
std::vector<BBAddrMap> Section2BBAddrMaps = {E1, E2};
std::vector<BBAddrMap> AllBBAddrMaps = {E1, E2, E3, E4, E5, E6};
std::vector<PGOAnalysisMap> Section0PGOAnalysisMaps = {P4, P5, P6};
std::vector<PGOAnalysisMap> Section1PGOAnalysisMaps = {P3};
std::vector<PGOAnalysisMap> Section2PGOAnalysisMaps = {P1, P2};
std::vector<PGOAnalysisMap> AllPGOAnalysisMaps = {P1, P2, P3, P4, P5, P6};
auto DoCheckSucceeds =
[&](StringRef YamlString, std::optional<unsigned> TextSectionIndex,
std::vector<BBAddrMap> ExpectedResult,
std::optional<std::vector<PGOAnalysisMap>> ExpectedPGO) {
SCOPED_TRACE(
"for TextSectionIndex: " +
(TextSectionIndex ? llvm::Twine(*TextSectionIndex) : "{}") +
" and object yaml:\n" + YamlString);
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr =
toBinary<ELF64LE>(Storage, YamlString);
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
Expected<const typename ELF64LE::Shdr *> BBAddrMapSecOrErr =
ElfOrErr->getELFFile().getSection(1);
ASSERT_THAT_EXPECTED(BBAddrMapSecOrErr, Succeeded());
std::vector<PGOAnalysisMap> PGOAnalyses;
auto BBAddrMaps = ElfOrErr->readBBAddrMap(
TextSectionIndex, ExpectedPGO ? &PGOAnalyses : nullptr);
ASSERT_THAT_EXPECTED(BBAddrMaps, Succeeded());
EXPECT_EQ(*BBAddrMaps, ExpectedResult);
if (ExpectedPGO) {
EXPECT_EQ(BBAddrMaps->size(), PGOAnalyses.size());
for (const auto &PGO : PGOAnalyses) {
errs() << "FuncEntryCount: " << PGO.FuncEntryCount << "\n";
for (const auto &PGOBB : PGO.BBEntries)
errs() << "\tBB: " << PGOBB.BlockFreq.getFrequency() << "\n";
}
errs() << " To expected:\n";
for (const auto &PGO : *ExpectedPGO) {
errs() << "FuncEntryCount: " << PGO.FuncEntryCount << "\n";
for (const auto &PGOBB : PGO.BBEntries)
errs() << "\tBB: " << PGOBB.BlockFreq.getFrequency() << "\n";
}
EXPECT_EQ(PGOAnalyses, *ExpectedPGO);
for (auto &&[BB, PGO] : llvm::zip(*BBAddrMaps, PGOAnalyses)) {
if (PGO.FeatEnable.BBFreq || PGO.FeatEnable.BrProb)
EXPECT_EQ(BB.getNumBBEntries(), PGO.BBEntries.size());
}
}
};
auto DoCheckFails = [&](StringRef YamlString,
std::optional<unsigned> TextSectionIndex,
const char *ErrMsg) {
SCOPED_TRACE("for TextSectionIndex: " +
(TextSectionIndex ? llvm::Twine(*TextSectionIndex) : "{}") +
" and object yaml:\n" + YamlString);
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr =
toBinary<ELF64LE>(Storage, YamlString);
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
Expected<const typename ELF64LE::Shdr *> BBAddrMapSecOrErr =
ElfOrErr->getELFFile().getSection(1);
ASSERT_THAT_EXPECTED(BBAddrMapSecOrErr, Succeeded());
std::vector<PGOAnalysisMap> PGOAnalyses;
EXPECT_THAT_ERROR(
ElfOrErr->readBBAddrMap(TextSectionIndex, &PGOAnalyses).takeError(),
FailedWithMessage(ErrMsg));
};
{
SCOPED_TRACE("normal sections");
// Check that we can retrieve the data in the normal case.
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/std::nullopt,
AllBBAddrMaps, std::nullopt);
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/0,
Section0BBAddrMaps, std::nullopt);
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/2,
Section1BBAddrMaps, std::nullopt);
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/1,
Section2BBAddrMaps, std::nullopt);
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/std::nullopt,
AllBBAddrMaps, AllPGOAnalysisMaps);
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/0,
Section0BBAddrMaps, Section0PGOAnalysisMaps);
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/2,
Section1BBAddrMaps, Section1PGOAnalysisMaps);
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/1,
Section2BBAddrMaps, Section2PGOAnalysisMaps);
// Check that when no bb-address-map section is found for a text section,
// we return an empty result.
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/3, {}, std::nullopt);
DoCheckSucceeds(CommonYamlString, /*TextSectionIndex=*/3, {},
std::vector<PGOAnalysisMap>{});
}
// Check that we detect when a bb-addr-map section is linked to an invalid
// (not present) section.
SmallString<128> InvalidLinkedYamlString(CommonYamlString);
InvalidLinkedYamlString += R"(
Link: 121
)";
{
SCOPED_TRACE("invalid linked section");
DoCheckFails(InvalidLinkedYamlString, /*TextSectionIndex=*/5,
"unable to get the linked-to section for "
"SHT_LLVM_BB_ADDR_MAP section with index 6: invalid section "
"index: 121");
// Linked sections are not checked when we don't target a specific text
// section.
DoCheckSucceeds(InvalidLinkedYamlString, /*TextSectionIndex=*/std::nullopt,
AllBBAddrMaps, std::nullopt);
DoCheckSucceeds(InvalidLinkedYamlString, /*TextSectionIndex=*/std::nullopt,
AllBBAddrMaps, AllPGOAnalysisMaps);
}
// Check that we can detect when bb-address-map decoding fails.
SmallString<128> TruncatedYamlString(CommonYamlString);
TruncatedYamlString += R"(
ShSize: 0xa
)";
{
SCOPED_TRACE("truncated section");
DoCheckFails(
TruncatedYamlString, /*TextSectionIndex=*/std::nullopt,
"unable to read SHT_LLVM_BB_ADDR_MAP section with index 6: "
"unexpected end of data at offset 0xa while reading [0x3, 0xb)");
// Check that we can read the other section's bb-address-maps which are
// valid.
DoCheckSucceeds(TruncatedYamlString, /*TextSectionIndex=*/2,
Section1BBAddrMaps, std::nullopt);
DoCheckSucceeds(TruncatedYamlString, /*TextSectionIndex=*/2,
Section1BBAddrMaps, Section1PGOAnalysisMaps);
}
}
// Test for ObjectFile::getRelocatedSection: check that it returns a relocated
// section for executable and relocatable files.
TEST(ELFObjectFileTest, ExecutableWithRelocs) {
StringRef HeaderString(R"(
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
)");
StringRef ContentsString(R"(
Sections:
- Name: .text
Type: SHT_PROGBITS
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
- Name: .rela.text
Type: SHT_RELA
Flags: [ SHF_INFO_LINK ]
Info: .text
)");
auto DoCheck = [&](StringRef YamlString) {
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr =
toBinary<ELF64LE>(Storage, YamlString);
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
const ELFObjectFile<ELF64LE> &Obj = *ElfOrErr;
bool FoundRela;
for (SectionRef Sec : Obj.sections()) {
Expected<StringRef> SecNameOrErr = Sec.getName();
ASSERT_THAT_EXPECTED(SecNameOrErr, Succeeded());
StringRef SecName = *SecNameOrErr;
if (SecName != ".rela.text")
continue;
FoundRela = true;
Expected<section_iterator> RelSecOrErr = Sec.getRelocatedSection();
ASSERT_THAT_EXPECTED(RelSecOrErr, Succeeded());
section_iterator RelSec = *RelSecOrErr;
ASSERT_NE(RelSec, Obj.section_end());
Expected<StringRef> TextSecNameOrErr = RelSec->getName();
ASSERT_THAT_EXPECTED(TextSecNameOrErr, Succeeded());
StringRef TextSecName = *TextSecNameOrErr;
EXPECT_EQ(TextSecName, ".text");
}
ASSERT_TRUE(FoundRela);
};
// Check ET_EXEC file (`ld --emit-relocs` use-case).
SmallString<128> ExecFileYamlString(HeaderString);
ExecFileYamlString += R"(
Type: ET_EXEC
)";
ExecFileYamlString += ContentsString;
DoCheck(ExecFileYamlString);
// Check ET_REL file.
SmallString<128> RelocatableFileYamlString(HeaderString);
RelocatableFileYamlString += R"(
Type: ET_REL
)";
RelocatableFileYamlString += ContentsString;
DoCheck(RelocatableFileYamlString);
}
TEST(ELFObjectFileTest, GetSectionAndRelocations) {
StringRef HeaderString(R"(
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_EXEC
)");
using Elf_Shdr = Elf_Shdr_Impl<ELF64LE>;
auto DoCheckSucceeds = [&](StringRef ContentsString,
std::function<Expected<bool>(const Elf_Shdr &)>
Matcher) {
SmallString<0> Storage;
SmallString<128> FullYamlString(HeaderString);
FullYamlString += ContentsString;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr =
toBinary<ELF64LE>(Storage, FullYamlString);
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
Expected<MapVector<const Elf_Shdr *, const Elf_Shdr *>> SecToRelocMapOrErr =
ElfOrErr->getELFFile().getSectionAndRelocations(Matcher);
ASSERT_THAT_EXPECTED(SecToRelocMapOrErr, Succeeded());
// Basic verification to make sure we have the correct section types.
for (auto const &[Sec, RelaSec] : *SecToRelocMapOrErr) {
ASSERT_EQ(Sec->sh_type, ELF::SHT_PROGBITS);
ASSERT_EQ(RelaSec->sh_type, ELF::SHT_RELA);
}
};
auto DoCheckFails = [&](StringRef ContentsString,
std::function<Expected<bool>(const Elf_Shdr &)>
Matcher,
const char *ErrorMessage) {
SmallString<0> Storage;
SmallString<128> FullYamlString(HeaderString);
FullYamlString += ContentsString;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr =
toBinary<ELF64LE>(Storage, FullYamlString);
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
Expected<MapVector<const Elf_Shdr *, const Elf_Shdr *>> SecToRelocMapOrErr =
ElfOrErr->getELFFile().getSectionAndRelocations(Matcher);
ASSERT_THAT_ERROR(SecToRelocMapOrErr.takeError(),
FailedWithMessage(ErrorMessage));
};
auto DefaultMatcher = [](const Elf_Shdr &Sec) -> bool {
return Sec.sh_type == ELF::SHT_PROGBITS;
};
StringRef TwoTextSections = R"(
Sections:
- Name: .text
Type: SHT_PROGBITS
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
- Name: .rela.text
Type: SHT_RELA
Flags: [ SHF_INFO_LINK ]
Info: .text
- Name: .text2
Type: SHT_PROGBITS
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
- Name: .rela.text2
Type: SHT_RELA
Flags: [ SHF_INFO_LINK ]
Info: .text2
)";
DoCheckSucceeds(TwoTextSections, DefaultMatcher);
StringRef OneTextSection = R"(
Sections:
- Name: .text
Type: SHT_PROGBITS
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
)";
auto ErroringMatcher = [](const Elf_Shdr &Sec) -> Expected<bool> {
if (Sec.sh_type == ELF::SHT_PROGBITS)
return createError("This was supposed to fail.");
return false;
};
DoCheckFails(OneTextSection, ErroringMatcher, "This was supposed to fail.");
StringRef MissingRelocatableContent = R"(
Sections:
- Name: .rela.text
Type: SHT_RELA
Flags: [ SHF_INFO_LINK ]
Info: 0xFF
)";
DoCheckFails(MissingRelocatableContent, DefaultMatcher,
"SHT_RELA section with index 1: failed to get a "
"relocated section: invalid section index: 255");
}