blob: 9ea931721534d0df4d7b1488b71aaa4c178639e1 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/elfldltl/diagnostics.h>
#include <lib/elfldltl/dynamic.h>
#include <lib/elfldltl/machine.h>
#include <lib/elfldltl/memory.h>
#include <string>
#include <type_traits>
#include <vector>
#include <zxtest/zxtest.h>
#include "symbol-tests.h"
namespace {
constexpr elfldltl::DiagnosticsFlags kDiagFlags = {.multiple_errors = true};
constexpr auto EmptyTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
std::vector<std::string> errors;
auto diag = elfldltl::CollectStringsDiagnostics(errors);
elfldltl::DirectMemory memory({}, 0);
// Nothing but the terminator.
constexpr typename Elf::Dyn dyn[] = {
{.tag = elfldltl::ElfDynTag::kNull},
};
// No matchers and nothing to match.
EXPECT_TRUE(elfldltl::DecodeDynamic(diag, memory, cpp20::span(dyn)));
EXPECT_EQ(0, diag.errors());
EXPECT_EQ(0, diag.warnings());
};
TEST(ElfldltlDynamicTests, Empty) { TestAllFormats(EmptyTest); }
constexpr auto MissingTerminatorTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
std::vector<std::string> errors;
auto diag = elfldltl::CollectStringsDiagnostics(errors, kDiagFlags);
elfldltl::DirectMemory memory({}, 0);
// Empty span has no terminator.
cpp20::span<const typename Elf::Dyn> dyn;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag, memory, dyn));
EXPECT_EQ(1, diag.errors());
EXPECT_EQ(0, diag.warnings());
ASSERT_GE(errors.size(), 1);
EXPECT_STREQ(errors.front(), "missing DT_NULL terminator in PT_DYNAMIC");
};
TEST(ElfldltlDynamicTests, MissingTerminator) { TestAllFormats(MissingTerminatorTest); }
constexpr auto RejectTextrelTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
std::vector<std::string> errors;
auto diag = elfldltl::CollectStringsDiagnostics(errors, kDiagFlags);
elfldltl::DirectMemory memory({}, 0);
// PT_DYNAMIC without DT_TEXTREL.
constexpr typename Elf::Dyn dyn_notextrel[] = {
{.tag = elfldltl::ElfDynTag::kNull},
};
EXPECT_TRUE(elfldltl::DecodeDynamic(diag, memory, cpp20::span(dyn_notextrel),
elfldltl::DynamicTextrelRejectObserver{}));
EXPECT_EQ(0, diag.errors());
EXPECT_EQ(0, diag.warnings());
EXPECT_TRUE(errors.empty());
// PT_DYNAMIC with DT_TEXTREL.
constexpr typename Elf::Dyn dyn_textrel[] = {
{.tag = elfldltl::ElfDynTag::kTextRel},
{.tag = elfldltl::ElfDynTag::kNull},
};
EXPECT_TRUE(elfldltl::DecodeDynamic(diag, memory, cpp20::span(dyn_textrel),
elfldltl::DynamicTextrelRejectObserver{}));
EXPECT_EQ(1, diag.errors());
EXPECT_EQ(0, diag.warnings());
ASSERT_GE(errors.size(), 1);
EXPECT_STREQ(errors.front(), elfldltl::DynamicTextrelRejectObserver::Message());
};
TEST(ElfldltlDynamicTests, RejectTextrel) { TestAllFormats(RejectTextrelTest); }
class TestDiagnostics {
public:
using DiagType = decltype(elfldltl::CollectStringsDiagnostics(
std::declval<std::vector<std::string>&>(), kDiagFlags));
DiagType& diag() { return diag_; }
const std::vector<std::string>& errors() const { return errors_; }
std::string ExplainErrors() const {
std::string str = std::to_string(diag_.errors()) + " errors, " +
std::to_string(diag_.warnings()) + " warnings:";
for (const std::string& line : errors_) {
str += "\n\t";
str += line;
}
return str;
}
private:
std::vector<std::string> errors_;
DiagType diag_ = elfldltl::CollectStringsDiagnostics(errors_, kDiagFlags);
};
constexpr auto RelocationInfoObserverEmptyTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using Dyn = typename Elf::Dyn;
TestDiagnostics diag;
elfldltl::DirectMemory empty_memory({}, 0);
// PT_DYNAMIC with no reloc info.
constexpr Dyn dyn_noreloc[] = {
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::RelocationInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), empty_memory, cpp20::span(dyn_noreloc),
elfldltl::DynamicRelocationInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(0, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_TRUE(diag.errors().empty());
EXPECT_TRUE(info.rel_relative().empty());
EXPECT_TRUE(info.rel_symbolic().empty());
EXPECT_TRUE(info.rela_relative().empty());
EXPECT_TRUE(info.rela_symbolic().empty());
EXPECT_TRUE(info.relr().empty());
std::visit([](const auto& table) { EXPECT_TRUE(table.empty()); }, info.jmprel());
};
TEST(ElfldltlDynamicTests, RelocationInfoObserverEmpty) {
TestAllFormats(RelocationInfoObserverEmptyTest);
}
// This synthesizes a memory image of relocation test data with known
// offsets and addresses that can be referenced in dynamic section entries in
// the specific test data. The same image contents are used for several tests
// below with different dynamic section data. Because the Memory API admits
// mutation of the image, the same image buffer shouldn't be reused for
// multiple tests just in case a test mutates the buffer (though they are meant
// not to). So this helper object is created in each test case to reconstruct
// the same data afresh.
template <typename Elf>
class RelocInfoTestImage {
public:
using size_type = typename Elf::size_type;
using Addr = typename Elf::Addr;
using Dyn = typename Elf::Dyn;
using Rel = typename Elf::Rel;
using Rela = typename Elf::Rela;
using Sym = typename Elf::Sym;
static size_type size_bytes() { return sizeof(image_); }
static size_type image_addr() { return kImageAddr; }
static size_type rel_size_bytes() { return sizeof(image_.rel); }
static size_type relent_size_bytes() { return sizeof(image_.rel[0]); }
static size_type rela_size_bytes() { return sizeof(image_.rela); }
static size_type relaent_size_bytes() { return sizeof(image_.rela[0]); }
static size_type relr_size_bytes() { return sizeof(image_.relr); }
static size_type relrent_size_bytes() { return sizeof(image_.relr[0]); }
size_type rel_addr() const { return ImageAddr(image_.rel); }
size_type rela_addr() const { return ImageAddr(image_.rela); }
size_type relr_addr() const { return ImageAddr(image_.relr); }
elfldltl::DirectMemory memory() { return elfldltl::DirectMemory(image_bytes(), kImageAddr); }
private:
// Build up some good relocation data in a memory image.
static constexpr size_type kImageAddr = 0x123400;
static constexpr auto kTestMachine = elfldltl::ElfMachine::kNone;
using TestType = elfldltl::RelocationTraits<kTestMachine>::Type;
static constexpr uint32_t kRelativeType = static_cast<uint32_t>(TestType::kRelative);
static constexpr uint32_t kAbsoluteType = static_cast<uint32_t>(TestType::kAbsolute);
template <typename T>
size_type ImageAddr(const T& data) const {
return static_cast<size_type>(reinterpret_cast<const std::byte*>(&data) -
reinterpret_cast<const std::byte*>(&image_)) +
kImageAddr;
}
struct ImageData {
Rel rel[3] = {
{8, kRelativeType},
{24, kRelativeType},
{4096, kAbsoluteType},
};
Rela rela[3] = {
{{8, kRelativeType}, 0x11111111},
{{24, kRelativeType}, 0x33333333},
{{4096, kAbsoluteType}, 0x1234},
};
Addr relr[3] = {
32,
0x55555555,
0xaaaaaaaa | 1,
};
} image_;
cpp20::span<std::byte> image_bytes() { return cpp20::as_writable_bytes(cpp20::span(&image_, 1)); }
};
constexpr auto RelocationInfoObserverFullValidTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
TestDiagnostics diag;
RelocInfoTestImage<Elf> test_image;
// PT_DYNAMIC with full valid reloc info.
const Dyn dyn_goodreloc[] = {
{
.tag = elfldltl::ElfDynTag::kRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{.tag = elfldltl::ElfDynTag::kRelSz, .val = test_image.rel_size_bytes()},
{
.tag = elfldltl::ElfDynTag::kRelEnt,
.val = test_image.relent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kRela,
.val = static_cast<size_type>(test_image.rela_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelaSz,
.val = test_image.rela_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelaEnt,
.val = test_image.relaent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelaCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kJmpRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{
.tag = elfldltl::ElfDynTag::kPltRelSz,
.val = test_image.rel_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kPltRel,
.val = static_cast<size_type>(elfldltl::ElfDynTag::kRel),
},
{
.tag = elfldltl::ElfDynTag::kRelr,
.val = static_cast<size_type>(test_image.relr_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelrSz,
.val = test_image.relr_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelrEnt,
.val = test_image.relrent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::RelocationInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), test_image.memory(), cpp20::span(dyn_goodreloc),
elfldltl::DynamicRelocationInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(0, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_TRUE(diag.errors().empty(), "%s", diag.ExplainErrors().c_str());
EXPECT_EQ(2, info.rel_relative().size());
EXPECT_EQ(1, info.rel_symbolic().size());
EXPECT_EQ(2, info.rela_relative().size());
EXPECT_EQ(1, info.rela_symbolic().size());
EXPECT_EQ(3, info.relr().size());
std::visit([](const auto& table) { EXPECT_EQ(3, table.size()); }, info.jmprel());
};
TEST(ElfldltlDynamicTests, RelocationInfoObserverFullValid) {
TestAllFormats(RelocationInfoObserverFullValidTest);
}
// We'll reuse that same image for the various error case tests.
// These cases only differ in their PT_DYNAMIC contents.
constexpr auto RelocationInfoObserverBadRelentTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
TestDiagnostics diag;
RelocInfoTestImage<Elf> test_image;
const Dyn dyn_bad_relent[] = {
{
.tag = elfldltl::ElfDynTag::kRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{.tag = elfldltl::ElfDynTag::kRelSz, .val = test_image.rel_size_bytes()},
{.tag = elfldltl::ElfDynTag::kRelEnt, .val = 17}, // Wrong size.
{.tag = elfldltl::ElfDynTag::kRelCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kRela,
.val = static_cast<size_type>(test_image.rela_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelaSz,
.val = test_image.rela_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelaEnt,
.val = test_image.relaent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelaCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kJmpRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{
.tag = elfldltl::ElfDynTag::kPltRelSz,
.val = test_image.rel_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kPltRel,
.val = static_cast<size_type>(elfldltl::ElfDynTag::kRel),
},
{
.tag = elfldltl::ElfDynTag::kRelr,
.val = static_cast<size_type>(test_image.relr_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelrSz,
.val = test_image.relr_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelrEnt,
.val = test_image.relrent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::RelocationInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), test_image.memory(), cpp20::span(dyn_bad_relent),
elfldltl::DynamicRelocationInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
// With keep-going, the data is delivered anyway.
EXPECT_EQ(2, info.rel_relative().size());
EXPECT_EQ(1, info.rel_symbolic().size());
EXPECT_EQ(2, info.rela_relative().size());
EXPECT_EQ(1, info.rela_symbolic().size());
EXPECT_EQ(3, info.relr().size());
std::visit([](const auto& table) { EXPECT_EQ(3, table.size()); }, info.jmprel());
};
TEST(ElfldltlDynamicTests, RelocationInfoObserverBadRelent) {
TestAllFormats(RelocationInfoObserverBadRelentTest);
}
constexpr auto RelocationInfoObserverBadRelaentTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
TestDiagnostics diag;
RelocInfoTestImage<Elf> test_image;
const Dyn dyn_bad_relaent[] = {
{
.tag = elfldltl::ElfDynTag::kRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{.tag = elfldltl::ElfDynTag::kRelSz, .val = test_image.rel_size_bytes()},
{
.tag = elfldltl::ElfDynTag::kRelEnt,
.val = test_image.relent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kRela,
.val = static_cast<size_type>(test_image.rela_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelaSz,
.val = test_image.rela_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelaEnt, .val = 17}, // Wrong size.
{.tag = elfldltl::ElfDynTag::kRelaCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kJmpRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{
.tag = elfldltl::ElfDynTag::kPltRelSz,
.val = test_image.rel_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kPltRel,
.val = static_cast<size_type>(elfldltl::ElfDynTag::kRel),
},
{
.tag = elfldltl::ElfDynTag::kRelr,
.val = static_cast<size_type>(test_image.relr_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelrSz,
.val = test_image.relr_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelrEnt,
.val = test_image.relrent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::RelocationInfo<Elf> info;
EXPECT_TRUE(
elfldltl::DecodeDynamic(diag.diag(), test_image.memory(), cpp20::span(dyn_bad_relaent),
elfldltl::DynamicRelocationInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
// With keep-going, the data is delivered anyway.
EXPECT_EQ(2, info.rel_relative().size());
EXPECT_EQ(1, info.rel_symbolic().size());
EXPECT_EQ(2, info.rela_relative().size());
EXPECT_EQ(1, info.rela_symbolic().size());
EXPECT_EQ(3, info.relr().size());
std::visit([](const auto& table) { EXPECT_EQ(3, table.size()); }, info.jmprel());
};
TEST(ElfldltlDynamicTests, RelocationInfoObserverBadRelaent) {
TestAllFormats(RelocationInfoObserverBadRelaentTest);
}
constexpr auto RelocationInfoObserverBadRelrentTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
TestDiagnostics diag;
RelocInfoTestImage<Elf> test_image;
const Dyn dyn_bad_relrent[] = {
{
.tag = elfldltl::ElfDynTag::kRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{.tag = elfldltl::ElfDynTag::kRelSz, .val = test_image.rel_size_bytes()},
{
.tag = elfldltl::ElfDynTag::kRelEnt,
.val = test_image.relent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kRela,
.val = static_cast<size_type>(test_image.rela_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelaSz,
.val = test_image.rela_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelaEnt,
.val = test_image.relaent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelaCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kJmpRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{
.tag = elfldltl::ElfDynTag::kPltRelSz,
.val = test_image.rel_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kPltRel,
.val = static_cast<size_type>(elfldltl::ElfDynTag::kRel),
},
{
.tag = elfldltl::ElfDynTag::kRelr,
.val = static_cast<size_type>(test_image.relr_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelrSz,
.val = test_image.relr_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelrEnt, .val = 3}, // Wrong size.
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::RelocationInfo<Elf> info;
EXPECT_TRUE(
elfldltl::DecodeDynamic(diag.diag(), test_image.memory(), cpp20::span(dyn_bad_relrent),
elfldltl::DynamicRelocationInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
// With keep-going, the data is delivered anyway.
EXPECT_EQ(2, info.rel_relative().size());
EXPECT_EQ(1, info.rel_symbolic().size());
EXPECT_EQ(2, info.rela_relative().size());
EXPECT_EQ(1, info.rela_symbolic().size());
EXPECT_EQ(3, info.relr().size());
std::visit([](const auto& table) { EXPECT_EQ(3, table.size()); }, info.jmprel());
};
TEST(ElfldltlDynamicTests, RelocationInfoObserverBadRelrent) {
TestAllFormats(RelocationInfoObserverBadRelrentTest);
}
constexpr auto RelocationInfoObserverMissingPltrelTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
TestDiagnostics diag;
RelocInfoTestImage<Elf> test_image;
const Dyn dyn_missing_pltrel[] = {
{
.tag = elfldltl::ElfDynTag::kRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelSz,
.val = test_image.rel_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelEnt,
.val = test_image.relent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kRela,
.val = static_cast<size_type>(test_image.rela_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelaSz,
.val = test_image.rela_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelaEnt,
.val = test_image.relaent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelaCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kJmpRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{
.tag = elfldltl::ElfDynTag::kPltRelSz,
.val = test_image.rel_size_bytes(),
},
// Missing DT_PLTREL.
{
.tag = elfldltl::ElfDynTag::kRelr,
.val = static_cast<size_type>(test_image.relr_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelrSz,
.val = test_image.relr_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelrEnt,
.val = test_image.relrent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::RelocationInfo<Elf> info;
EXPECT_TRUE(
elfldltl::DecodeDynamic(diag.diag(), test_image.memory(), cpp20::span(dyn_missing_pltrel),
elfldltl::DynamicRelocationInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
// DT_JMPREL was ignored but the rest is normal.
EXPECT_EQ(2, info.rel_relative().size());
EXPECT_EQ(1, info.rel_symbolic().size());
EXPECT_EQ(2, info.rela_relative().size());
EXPECT_EQ(1, info.rela_symbolic().size());
EXPECT_EQ(3, info.relr().size());
std::visit([](const auto& table) { EXPECT_EQ(0, table.size()); }, info.jmprel());
};
TEST(ElfldltlDynamicTests, RelocationInfoObserverMissingPltrel) {
TestAllFormats(RelocationInfoObserverMissingPltrelTest);
}
constexpr auto RelocationInfoObserverBadPltrelTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
TestDiagnostics diag;
RelocInfoTestImage<Elf> test_image;
const Dyn dyn_bad_pltrel[] = {
{
.tag = elfldltl::ElfDynTag::kRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{.tag = elfldltl::ElfDynTag::kRelSz, .val = test_image.rel_size_bytes()},
{
.tag = elfldltl::ElfDynTag::kRelEnt,
.val = test_image.relent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kRela,
.val = static_cast<size_type>(test_image.rela_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelaSz,
.val = test_image.rela_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelaEnt,
.val = test_image.relaent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelaCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kJmpRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{
.tag = elfldltl::ElfDynTag::kPltRelSz,
.val = test_image.rel_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kPltRel, .val = 0}, // Invalid value.
{
.tag = elfldltl::ElfDynTag::kRelr,
.val = static_cast<size_type>(test_image.relr_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelrSz,
.val = test_image.relr_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelrEnt,
.val = test_image.relrent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::RelocationInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), test_image.memory(), cpp20::span(dyn_bad_pltrel),
elfldltl::DynamicRelocationInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
// DT_JMPREL was ignored but the rest is normal.
EXPECT_EQ(2, info.rel_relative().size());
EXPECT_EQ(1, info.rel_symbolic().size());
EXPECT_EQ(2, info.rela_relative().size());
EXPECT_EQ(1, info.rela_symbolic().size());
EXPECT_EQ(3, info.relr().size());
std::visit([](const auto& table) { EXPECT_EQ(0, table.size()); }, info.jmprel());
};
TEST(ElfldltlDynamicTests, RelocationInfoObserverBadPltrel) {
TestAllFormats(RelocationInfoObserverBadPltrelTest);
}
// The bad address, size, and alignment cases are all the same template code
// paths for each table so we only test DT_REL to stand in for the rest.
constexpr auto RelocationInfoObserverBadRelAddrTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
TestDiagnostics diag;
RelocInfoTestImage<Elf> test_image;
const Dyn dyn_bad_rel_addr[] = {
{
.tag = elfldltl::ElfDynTag::kRel,
// This is an invalid address, before the image starts.
.val = test_image.image_addr() - 1,
},
{.tag = elfldltl::ElfDynTag::kRelSz, .val = test_image.rel_size_bytes()},
{
.tag = elfldltl::ElfDynTag::kRelEnt,
.val = test_image.relent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kRela,
.val = static_cast<size_type>(test_image.rela_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelaSz,
.val = test_image.rela_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelaEnt,
.val = test_image.relaent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelaCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kJmpRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{
.tag = elfldltl::ElfDynTag::kPltRelSz,
.val = test_image.rel_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kPltRel,
.val = static_cast<size_type>(elfldltl::ElfDynTag::kRel),
},
{
.tag = elfldltl::ElfDynTag::kRelr,
.val = static_cast<size_type>(test_image.relr_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelrSz,
.val = test_image.relr_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelrEnt,
.val = test_image.relrent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::RelocationInfo<Elf> info;
EXPECT_TRUE(
elfldltl::DecodeDynamic(diag.diag(), test_image.memory(), cpp20::span(dyn_bad_rel_addr),
elfldltl::DynamicRelocationInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
// DT_REL was ignored but the rest is normal.
EXPECT_EQ(0, info.rel_relative().size());
EXPECT_EQ(0, info.rel_symbolic().size());
EXPECT_EQ(2, info.rela_relative().size());
EXPECT_EQ(1, info.rela_symbolic().size());
EXPECT_EQ(3, info.relr().size());
std::visit([](const auto& table) { EXPECT_EQ(3, table.size()); }, info.jmprel());
};
TEST(ElfldltlDynamicTests, RelocationInfoObserverBadRelAddr) {
TestAllFormats(RelocationInfoObserverBadRelAddrTest);
}
constexpr auto RelocationInfoObserverBadRelSzTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
TestDiagnostics diag;
RelocInfoTestImage<Elf> test_image;
const Dyn dyn_bad_relsz[] = {
{.tag = elfldltl::ElfDynTag::kRel, .val = test_image.rel_addr()},
{
.tag = elfldltl::ElfDynTag::kRelSz,
// This is an invalid size, bigger than the whole image.
.val = test_image.size_bytes() + 1,
},
{
.tag = elfldltl::ElfDynTag::kRelEnt,
.val = test_image.relent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kRela,
.val = static_cast<size_type>(test_image.rela_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelaSz,
.val = test_image.rela_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelaEnt,
.val = test_image.relaent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelaCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kJmpRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{
.tag = elfldltl::ElfDynTag::kPltRelSz,
.val = test_image.rel_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kPltRel,
.val = static_cast<size_type>(elfldltl::ElfDynTag::kRel),
},
{
.tag = elfldltl::ElfDynTag::kRelr,
.val = static_cast<size_type>(test_image.relr_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelrSz,
.val = test_image.relr_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelrEnt,
.val = test_image.relrent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::RelocationInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), test_image.memory(), cpp20::span(dyn_bad_relsz),
elfldltl::DynamicRelocationInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
// DT_REL was ignored but the rest is normal.
EXPECT_EQ(0, info.rel_relative().size());
EXPECT_EQ(0, info.rel_symbolic().size());
EXPECT_EQ(2, info.rela_relative().size());
EXPECT_EQ(1, info.rela_symbolic().size());
EXPECT_EQ(3, info.relr().size());
std::visit([](const auto& table) { EXPECT_EQ(3, table.size()); }, info.jmprel());
};
TEST(ElfldltlDynamicTests, RelocationInfoObserverBadRelSz) {
TestAllFormats(RelocationInfoObserverBadRelSzTest);
}
constexpr auto RelocationInfoObserverBadRelSzAlignTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
TestDiagnostics diag;
RelocInfoTestImage<Elf> test_image;
const Dyn dyn_bad_relsz_align[] = {
{.tag = elfldltl::ElfDynTag::kRel, .val = test_image.rel_addr()},
{
.tag = elfldltl::ElfDynTag::kRelSz,
// This size is not a multiple of the entry size.
.val = test_image.rel_size_bytes() - 3,
},
{
.tag = elfldltl::ElfDynTag::kRelEnt,
.val = test_image.relent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kRela,
.val = static_cast<size_type>(test_image.rela_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelaSz,
.val = test_image.rela_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelaEnt,
.val = test_image.relaent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kRelaCount, .val = 2},
{
.tag = elfldltl::ElfDynTag::kJmpRel,
.val = static_cast<size_type>(test_image.rel_addr()),
},
{
.tag = elfldltl::ElfDynTag::kPltRelSz,
.val = test_image.rel_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kPltRel,
.val = static_cast<size_type>(elfldltl::ElfDynTag::kRel),
},
{
.tag = elfldltl::ElfDynTag::kRelr,
.val = static_cast<size_type>(test_image.relr_addr()),
},
{
.tag = elfldltl::ElfDynTag::kRelrSz,
.val = test_image.relr_size_bytes(),
},
{
.tag = elfldltl::ElfDynTag::kRelrEnt,
.val = test_image.relrent_size_bytes(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::RelocationInfo<Elf> info;
EXPECT_TRUE(
elfldltl::DecodeDynamic(diag.diag(), test_image.memory(), cpp20::span(dyn_bad_relsz_align),
elfldltl::DynamicRelocationInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
// DT_REL was ignored but the rest is normal.
EXPECT_EQ(0, info.rel_relative().size());
EXPECT_EQ(0, info.rel_symbolic().size());
EXPECT_EQ(2, info.rela_relative().size());
EXPECT_EQ(1, info.rela_symbolic().size());
EXPECT_EQ(3, info.relr().size());
std::visit([](const auto& table) { EXPECT_EQ(3, table.size()); }, info.jmprel());
};
TEST(ElfldltlDynamicTests, RelocationInfoObserverBadRelSzAlign) {
TestAllFormats(RelocationInfoObserverBadRelSzAlignTest);
}
// This synthesizes a memory image of symbol-related test data with known
// offsets and addresses that can be referenced in dynamic section entries in
// the specific test data. The same image contents are used for several tests
// below with different dynamic section data. Because the Memory API admits
// mutation of the image, the same image buffer shouldn't be reused for
// multiple tests just in case a test mutates the buffer (though they are meant
// not to). So this helper object is created in each test case to reconstruct
// the same data afresh.
template <typename Elf>
class SymbolInfoTestImage {
public:
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
using Sym = typename Elf::Sym;
SymbolInfoTestImage() {
// Build up some good symbol data in a memory image.
soname_offset_ = test_syms_.AddString("libfoo.so");
auto symtab_bytes = cpp20::as_bytes(test_syms_.symtab());
cpp20::span<const std::byte> strtab_bytes{
reinterpret_cast<const std::byte*>(test_syms_.strtab().data()),
test_syms_.strtab().size(),
};
image_ = std::vector<std::byte>(symtab_bytes.begin(), symtab_bytes.end());
auto next_addr = [this]() -> size_type {
size_t align_pad = sizeof(size_type) - (image_.size() % sizeof(size_type));
image_.insert(image_.end(), align_pad, std::byte{});
return kSymtabAddr + static_cast<size_type>(image_.size());
};
strtab_addr_ = next_addr();
image_.insert(image_.end(), strtab_bytes.begin(), strtab_bytes.end());
gnu_hash_addr_ = next_addr();
auto gnu_hash_data = cpp20::span(kTestGnuHash<typename Elf::Addr>);
auto gnu_hash_bytes = cpp20::as_bytes(gnu_hash_data);
image_.insert(image_.end(), gnu_hash_bytes.begin(), gnu_hash_bytes.end());
hash_addr_ = next_addr();
auto hash_data = cpp20::span(kTestCompatHash<typename Elf::Word>);
auto hash_bytes = cpp20::as_bytes(hash_data);
image_.insert(image_.end(), hash_bytes.begin(), hash_bytes.end());
}
size_type soname_offset() const { return soname_offset_; }
size_type strtab_addr() const { return strtab_addr_; }
size_t strtab_size_bytes() const { return test_syms_.strtab().size(); }
size_type symtab_addr() { return kSymtabAddr; }
size_type hash_addr() const { return hash_addr_; }
size_type gnu_hash_addr() const { return gnu_hash_addr_; }
const TestSymtab<Elf>& test_syms() const { return test_syms_; }
size_t size_bytes() const { return image_.size(); }
elfldltl::DirectMemory memory() { return elfldltl::DirectMemory(image_, kSymtabAddr); }
private:
static constexpr size_type kSymtabAddr = 0x1000;
std::vector<std::byte> image_;
TestSymtab<Elf> test_syms_ = kTestSymbols<Elf>;
size_type soname_offset_ = 0;
size_type strtab_addr_ = 0;
size_type hash_addr_ = 0;
size_type gnu_hash_addr_ = 0;
};
constexpr auto SymbolInfoObserverEmptyTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using Dyn = typename Elf::Dyn;
TestDiagnostics diag;
elfldltl::DirectMemory empty_memory({}, 0);
// PT_DYNAMIC with no symbol info.
constexpr Dyn dyn_nosyms[] = {
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), empty_memory, cpp20::span(dyn_nosyms),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(0, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_TRUE(diag.errors().empty());
EXPECT_TRUE(info.strtab().empty());
EXPECT_TRUE(info.symtab().empty());
EXPECT_TRUE(info.soname().empty());
EXPECT_FALSE(info.compat_hash());
EXPECT_FALSE(info.gnu_hash());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverEmpty) { TestAllFormats(SymbolInfoObserverEmptyTest); }
constexpr auto SymbolInfoObserverFullValidTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
using Sym = typename Elf::Sym;
TestDiagnostics diag;
SymbolInfoTestImage<Elf> test_image;
// PT_DYNAMIC with full valid symbol info.
const Dyn dyn_goodsyms[] = {
{.tag = elfldltl::ElfDynTag::kSoname, .val = test_image.soname_offset()},
{.tag = elfldltl::ElfDynTag::kSymTab, .val = test_image.symtab_addr()},
{.tag = elfldltl::ElfDynTag::kSymEnt, .val = sizeof(Sym)},
{.tag = elfldltl::ElfDynTag::kStrTab, .val = test_image.strtab_addr()},
{
.tag = elfldltl::ElfDynTag::kStrSz,
.val = static_cast<size_type>(test_image.strtab_size_bytes()),
},
{.tag = elfldltl::ElfDynTag::kHash, .val = test_image.hash_addr()},
{
.tag = elfldltl::ElfDynTag::kGnuHash,
.val = test_image.gnu_hash_addr(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), test_image.memory(), cpp20::span(dyn_goodsyms),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(0, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_TRUE(diag.errors().empty());
EXPECT_EQ(info.strtab().size(), test_image.test_syms().strtab().size());
EXPECT_EQ(info.strtab(), test_image.test_syms().strtab());
EXPECT_EQ(info.safe_symtab().size(), test_image.test_syms().symtab().size());
EXPECT_STREQ(info.soname(), "libfoo.so");
EXPECT_TRUE(info.compat_hash());
EXPECT_TRUE(info.gnu_hash());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverFullValid) {
TestAllFormats(SymbolInfoObserverFullValidTest);
}
// We'll reuse that same image for the various error case tests.
// These cases only differ in their PT_DYNAMIC contents.
constexpr auto SymbolInfoObserverBadSonameOffsetTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
using Sym = typename Elf::Sym;
TestDiagnostics diag;
SymbolInfoTestImage<Elf> test_image;
elfldltl::DirectMemory image_memory = test_image.memory();
const Dyn dyn_bad_soname_offset[] = {
{
.tag = elfldltl::ElfDynTag::kSoname,
// This is an invalid string table offset.
.val = static_cast<size_type>(test_image.test_syms().strtab().size()),
},
{.tag = elfldltl::ElfDynTag::kSymTab, .val = test_image.symtab_addr()},
{.tag = elfldltl::ElfDynTag::kSymEnt, .val = sizeof(Sym)},
{.tag = elfldltl::ElfDynTag::kStrTab, .val = test_image.strtab_addr()},
{
.tag = elfldltl::ElfDynTag::kStrSz,
.val = static_cast<size_type>(test_image.strtab_size_bytes()),
},
{.tag = elfldltl::ElfDynTag::kHash, .val = test_image.hash_addr()},
{.tag = elfldltl::ElfDynTag::kGnuHash, .val = test_image.gnu_hash_addr()},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), image_memory, cpp20::span(dyn_bad_soname_offset),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverBadSonameOffset) {
TestAllFormats(SymbolInfoObserverBadSonameOffsetTest);
}
constexpr auto SymbolInfoObserverBadSymentTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
TestDiagnostics diag;
SymbolInfoTestImage<Elf> test_image;
elfldltl::DirectMemory image_memory = test_image.memory();
const Dyn dyn_bad_syment[] = {
{.tag = elfldltl::ElfDynTag::kSoname, .val = test_image.soname_offset()},
{.tag = elfldltl::ElfDynTag::kSymTab, .val = test_image.symtab_addr()},
{.tag = elfldltl::ElfDynTag::kSymEnt, .val = 17}, // Wrong size.
{.tag = elfldltl::ElfDynTag::kStrTab, .val = test_image.strtab_addr()},
{
.tag = elfldltl::ElfDynTag::kStrSz,
.val = static_cast<size_type>(test_image.strtab_size_bytes()),
},
{.tag = elfldltl::ElfDynTag::kHash, .val = test_image.hash_addr()},
{
.tag = elfldltl::ElfDynTag::kGnuHash,
.val = test_image.gnu_hash_addr(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), image_memory, cpp20::span(dyn_bad_syment),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverBadSyment) {
TestAllFormats(SymbolInfoObserverBadSymentTest);
}
constexpr auto SymbolInfoObserverMissingStrszTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using Dyn = typename Elf::Dyn;
using Sym = typename Elf::Sym;
TestDiagnostics diag;
SymbolInfoTestImage<Elf> test_image;
elfldltl::DirectMemory image_memory = test_image.memory();
const Dyn dyn_missing_strsz[] = {
{.tag = elfldltl::ElfDynTag::kSymTab, .val = test_image.symtab_addr()},
{.tag = elfldltl::ElfDynTag::kSymEnt, .val = sizeof(Sym)},
{.tag = elfldltl::ElfDynTag::kStrTab, .val = test_image.strtab_addr()},
// DT_STRSZ omitted with DT_STRTAB present.
{.tag = elfldltl::ElfDynTag::kHash, .val = test_image.hash_addr()},
{
.tag = elfldltl::ElfDynTag::kGnuHash,
.val = test_image.gnu_hash_addr(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), image_memory, cpp20::span(dyn_missing_strsz),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverMissingStrsz) {
TestAllFormats(SymbolInfoObserverMissingStrszTest);
}
constexpr auto SymbolInfoObserverMissingStrtabTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
using Sym = typename Elf::Sym;
TestDiagnostics diag;
SymbolInfoTestImage<Elf> test_image;
elfldltl::DirectMemory image_memory = test_image.memory();
const Dyn dyn_missing_strtab[] = {
{.tag = elfldltl::ElfDynTag::kSymTab, .val = test_image.symtab_addr()},
// DT_STRTAB omitted with DT_STRSZ present.
{
.tag = elfldltl::ElfDynTag::kStrSz,
.val = static_cast<size_type>(test_image.strtab_size_bytes()),
},
{.tag = elfldltl::ElfDynTag::kSymEnt, .val = sizeof(Sym)},
{.tag = elfldltl::ElfDynTag::kHash, .val = test_image.hash_addr()},
{
.tag = elfldltl::ElfDynTag::kGnuHash,
.val = test_image.gnu_hash_addr(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), image_memory, cpp20::span(dyn_missing_strtab),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverMissingStrtab) {
TestAllFormats(SymbolInfoObserverMissingStrtabTest);
}
constexpr auto SymbolInfoObserverBadStrtabAddrTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
using Sym = typename Elf::Sym;
TestDiagnostics diag;
SymbolInfoTestImage<Elf> test_image;
elfldltl::DirectMemory image_memory = test_image.memory();
const Dyn dyn_bad_strtab_addr[] = {
{.tag = elfldltl::ElfDynTag::kSymTab, .val = test_image.symtab_addr()},
{.tag = elfldltl::ElfDynTag::kSymEnt, .val = sizeof(Sym)},
// This is an invalid address, before the image start.
{
.tag = elfldltl::ElfDynTag::kStrTab,
.val = test_image.symtab_addr() - 1,
},
{
.tag = elfldltl::ElfDynTag::kStrSz,
.val = static_cast<size_type>(test_image.strtab_size_bytes()),
},
{.tag = elfldltl::ElfDynTag::kHash, .val = test_image.hash_addr()},
{
.tag = elfldltl::ElfDynTag::kGnuHash,
.val = test_image.gnu_hash_addr(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), image_memory, cpp20::span(dyn_bad_strtab_addr),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverBadStrtabAddr) {
TestAllFormats(SymbolInfoObserverBadStrtabAddrTest);
}
constexpr auto SymbolInfoObserverBadSymtabAddrTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
using Sym = typename Elf::Sym;
TestDiagnostics diag;
SymbolInfoTestImage<Elf> test_image;
elfldltl::DirectMemory image_memory = test_image.memory();
// Since the symtab has no known bounds, bad addresses are only diagnosed via
// the memory object and cause hard failure, not via the diag object where
// keep_going causes success return.
const Dyn dyn_bad_symtab_addr[] = {
{.tag = elfldltl::ElfDynTag::kSoname, .val = test_image.soname_offset()},
{
.tag = elfldltl::ElfDynTag::kSymTab,
// This is an invalid address, past the image end.
.val = static_cast<size_type>(test_image.symtab_addr() + test_image.size_bytes()),
},
{.tag = elfldltl::ElfDynTag::kSymEnt, .val = sizeof(Sym)},
{.tag = elfldltl::ElfDynTag::kStrTab, .val = test_image.strtab_addr()},
{
.tag = elfldltl::ElfDynTag::kStrSz,
.val = static_cast<size_type>(test_image.strtab_size_bytes()),
},
{.tag = elfldltl::ElfDynTag::kHash, .val = test_image.hash_addr()},
{
.tag = elfldltl::ElfDynTag::kGnuHash,
.val = test_image.gnu_hash_addr(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_FALSE(elfldltl::DecodeDynamic(diag.diag(), image_memory, cpp20::span(dyn_bad_symtab_addr),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(0, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(0, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverBadSymtabAddr) {
TestAllFormats(SymbolInfoObserverBadSymtabAddrTest);
}
constexpr auto SymbolInfoObserverBadSymtabAlignTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
using Sym = typename Elf::Sym;
TestDiagnostics diag;
SymbolInfoTestImage<Elf> test_image;
elfldltl::DirectMemory image_memory = test_image.memory();
// A misaligned symtab becomes a hard failure after diagnosis because it's
// treated like a memory failure in addition to the diagnosed error.
const Dyn dyn_bad_symtab_align[] = {
{.tag = elfldltl::ElfDynTag::kSoname, .val = test_image.soname_offset()},
{
.tag = elfldltl::ElfDynTag::kSymTab,
// This is misaligned vs alignof(Sym).
.val = test_image.symtab_addr() + 2,
},
{.tag = elfldltl::ElfDynTag::kSymEnt, .val = sizeof(Sym)},
{.tag = elfldltl::ElfDynTag::kStrTab, .val = test_image.strtab_addr()},
{
.tag = elfldltl::ElfDynTag::kStrSz,
.val = static_cast<size_type>(test_image.strtab_size_bytes()),
},
{.tag = elfldltl::ElfDynTag::kHash, .val = test_image.hash_addr()},
{
.tag = elfldltl::ElfDynTag::kGnuHash,
.val = test_image.gnu_hash_addr(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_FALSE(elfldltl::DecodeDynamic(diag.diag(), image_memory, cpp20::span(dyn_bad_symtab_align),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverBadSymtabAlign) {
TestAllFormats(SymbolInfoObserverBadSymtabAlignTest);
}
constexpr auto SymbolInfoObserverBadHashAddrTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
using Sym = typename Elf::Sym;
TestDiagnostics diag;
SymbolInfoTestImage<Elf> test_image;
elfldltl::DirectMemory image_memory = test_image.memory();
// Since DT_HASH has no known bounds, bad addresses are only diagnosed via
// the memory object and cause hard failure, not via the diag object where
// keep_going causes success return.
const Dyn dyn_bad_hash_addr[] = {
{.tag = elfldltl::ElfDynTag::kSoname, .val = test_image.soname_offset()},
{.tag = elfldltl::ElfDynTag::kSymTab, .val = test_image.symtab_addr()},
{.tag = elfldltl::ElfDynTag::kSymEnt, .val = sizeof(Sym)},
{.tag = elfldltl::ElfDynTag::kStrTab, .val = test_image.strtab_addr()},
{
.tag = elfldltl::ElfDynTag::kStrSz,
.val = static_cast<size_type>(test_image.strtab_size_bytes()),
},
{
.tag = elfldltl::ElfDynTag::kHash,
// This is an invalid address, past the image end.
.val = static_cast<size_type>(test_image.symtab_addr() + test_image.size_bytes()),
},
{
.tag = elfldltl::ElfDynTag::kGnuHash,
.val = test_image.gnu_hash_addr(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_FALSE(elfldltl::DecodeDynamic(diag.diag(), image_memory, cpp20::span(dyn_bad_hash_addr),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(0, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(0, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverBadHashAddr) {
TestAllFormats(SymbolInfoObserverBadHashAddrTest);
}
constexpr auto SymbolInfoObserverBadHashAlignTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
using Sym = typename Elf::Sym;
TestDiagnostics diag;
SymbolInfoTestImage<Elf> test_image;
elfldltl::DirectMemory image_memory = test_image.memory();
const Dyn dyn_bad_hash_align[] = {
{.tag = elfldltl::ElfDynTag::kSoname, .val = test_image.soname_offset()},
{.tag = elfldltl::ElfDynTag::kSymTab, .val = test_image.symtab_addr()},
{.tag = elfldltl::ElfDynTag::kSymEnt, .val = sizeof(Sym)},
{.tag = elfldltl::ElfDynTag::kStrTab, .val = test_image.strtab_addr()},
{
.tag = elfldltl::ElfDynTag::kStrSz,
.val = static_cast<size_type>(test_image.strtab_size_bytes()),
},
{
.tag = elfldltl::ElfDynTag::kHash,
// This is misaligned vs alignof(Word).
.val = test_image.hash_addr() + 2,
},
{
.tag = elfldltl::ElfDynTag::kGnuHash,
.val = test_image.gnu_hash_addr(),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_TRUE(elfldltl::DecodeDynamic(diag.diag(), image_memory, cpp20::span(dyn_bad_hash_align),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverBadHashAlign) {
TestAllFormats(SymbolInfoObserverBadHashAlignTest);
}
constexpr auto SymbolInfoObserverBadGnuHashAddrTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
using Sym = typename Elf::Sym;
TestDiagnostics diag;
SymbolInfoTestImage<Elf> test_image;
elfldltl::DirectMemory image_memory = test_image.memory();
// Since DT_GNU_HASH has no known bounds, bad addresses are only diagnosed
// via the memory object and cause hard failure, not via the diag object
// where keep_going causes success return.
const Dyn dyn_bad_gnu_hash_addr[] = {
{.tag = elfldltl::ElfDynTag::kSoname, .val = test_image.soname_offset()},
{.tag = elfldltl::ElfDynTag::kSymTab, .val = test_image.symtab_addr()},
{.tag = elfldltl::ElfDynTag::kSymEnt, .val = sizeof(Sym)},
{.tag = elfldltl::ElfDynTag::kStrTab, .val = test_image.strtab_addr()},
{
.tag = elfldltl::ElfDynTag::kStrSz,
.val = static_cast<size_type>(test_image.strtab_size_bytes()),
},
{.tag = elfldltl::ElfDynTag::kHash, .val = test_image.hash_addr()},
{
.tag = elfldltl::ElfDynTag::kGnuHash,
// This is an invalid address, past the image end.
.val = static_cast<size_type>(test_image.symtab_addr() + test_image.size_bytes()),
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_FALSE(
elfldltl::DecodeDynamic(diag.diag(), image_memory, cpp20::span(dyn_bad_gnu_hash_addr),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(0, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(0, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverBadGnuHashAddr) {
TestAllFormats(SymbolInfoObserverBadGnuHashAddrTest);
}
constexpr auto SymbolInfoObserverBadGnuHashAlignTest = [](auto&& elf) {
using Elf = std::decay_t<decltype(elf)>;
using size_type = typename Elf::size_type;
using Dyn = typename Elf::Dyn;
using Sym = typename Elf::Sym;
TestDiagnostics diag;
SymbolInfoTestImage<Elf> test_image;
elfldltl::DirectMemory image_memory = test_image.memory();
const Dyn dyn_bad_gnu_hash_align[] = {
{.tag = elfldltl::ElfDynTag::kSoname, .val = test_image.soname_offset()},
{.tag = elfldltl::ElfDynTag::kSymTab, .val = test_image.symtab_addr()},
{.tag = elfldltl::ElfDynTag::kSymEnt, .val = sizeof(Sym)},
{.tag = elfldltl::ElfDynTag::kStrTab, .val = test_image.strtab_addr()},
{
.tag = elfldltl::ElfDynTag::kStrSz,
.val = static_cast<size_type>(test_image.strtab_size_bytes()),
},
{.tag = elfldltl::ElfDynTag::kHash, .val = test_image.hash_addr()},
{
.tag = elfldltl::ElfDynTag::kGnuHash,
// This is misaligned vs alignof(size_type).
.val = test_image.hash_addr() + sizeof(size_type) - 1,
},
{.tag = elfldltl::ElfDynTag::kNull},
};
elfldltl::SymbolInfo<Elf> info;
EXPECT_TRUE(
elfldltl::DecodeDynamic(diag.diag(), image_memory, cpp20::span(dyn_bad_gnu_hash_align),
elfldltl::DynamicSymbolInfoObserver(info)),
"%s", diag.ExplainErrors().c_str());
EXPECT_EQ(1, diag.diag().errors());
EXPECT_EQ(0, diag.diag().warnings());
EXPECT_EQ(1, diag.errors().size(), "%s", diag.ExplainErrors().c_str());
};
TEST(ElfldltlDynamicTests, SymbolInfoObserverBadGnuHashAlign) {
TestAllFormats(SymbolInfoObserverBadGnuHashAlignTest);
}
} // namespace