Commit 6a7b517e authored by Mike Wittman's avatar Mike Wittman Committed by Commit Bot

[base] Add ElfReader tests for relocatable ELF files with load bias

Updates the ELF image builder to support building relocatable ELF images
with virtual address mapping subject to load bias. Parameterizes tests
based on mapping state. Fixes ReadElfLibraryName() for the load bias case.

Bug: 1105170
Change-Id: I74c37342e50636fb875c2e0a463a987d7312adc9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2305569Reviewed-by: default avatarWez <wez@chromium.org>
Reviewed-by: default avatarKevin Marshall <kmarshall@chromium.org>
Commit-Queue: Mike Wittman <wittman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#794616}
parent 26b39a48
......@@ -146,7 +146,7 @@ size_t ReadElfBuildId(const void* elf_mapped_base,
Optional<StringPiece> ReadElfLibraryName(const void* elf_mapped_base) {
// NOTE: Function should use async signal safe calls only.
const char* elf_base = reinterpret_cast<const char*>(elf_mapped_base);
const char* elf_virtual_base = GetElfBaseVirtualAddress(elf_mapped_base);
const Ehdr* elf_header = GetElfHeader(elf_mapped_base);
if (!elf_header)
return {};
......@@ -159,9 +159,9 @@ Optional<StringPiece> ReadElfLibraryName(const void* elf_mapped_base) {
// SONAME offsets, which are used to compute the offset of the library
// name string.
const Dyn* dynamic_start =
reinterpret_cast<const Dyn*>(elf_base + header.p_vaddr);
reinterpret_cast<const Dyn*>(elf_virtual_base + header.p_vaddr);
const Dyn* dynamic_end = reinterpret_cast<const Dyn*>(
elf_base + header.p_vaddr + header.p_memsz);
elf_virtual_base + header.p_vaddr + header.p_memsz);
Word soname_strtab_offset = 0;
const char* strtab_addr = 0;
for (const Dyn* dynamic_iter = dynamic_start; dynamic_iter < dynamic_end;
......@@ -169,7 +169,8 @@ Optional<StringPiece> ReadElfLibraryName(const void* elf_mapped_base) {
if (dynamic_iter->d_tag == DT_STRTAB) {
#if defined(OS_FUCHSIA) || defined(OS_ANDROID)
// Fuchsia and Android do not relocate the symtab pointer on ELF load.
strtab_addr = (size_t)dynamic_iter->d_un.d_ptr + (const char*)elf_base;
strtab_addr =
(size_t)dynamic_iter->d_un.d_ptr + (const char*)elf_virtual_base;
#else
strtab_addr = (const char*)dynamic_iter->d_un.d_ptr;
#endif
......
......@@ -24,11 +24,26 @@ namespace {
constexpr uint8_t kBuildIdBytes[] = {0xab, 0xcd, 0x12, 0x34};
constexpr const char kBuildIdHexString[] = "ABCD1234";
constexpr const char kBuildIdHexStringLower[] = "ABCD1234";
std::string ParamInfoToString(
const ::testing::TestParamInfo<base::TestElfImageBuilder::MappingType>&
param_info) {
switch (param_info.param) {
case TestElfImageBuilder::RELOCATABLE:
return "Relocatable";
case TestElfImageBuilder::RELOCATABLE_WITH_BIAS:
return "RelocatableWithBias";
}
}
} // namespace
TEST(ElfReaderTest, ReadElfBuildIdUppercase) {
using ElfReaderTest =
::testing::TestWithParam<TestElfImageBuilder::MappingType>;
TEST_P(ElfReaderTest, ReadElfBuildIdUppercase) {
TestElfImage image =
TestElfImageBuilder()
TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID, "GNU", kBuildIdBytes)
.Build();
......@@ -39,9 +54,9 @@ TEST(ElfReaderTest, ReadElfBuildIdUppercase) {
EXPECT_EQ(kBuildIdHexString, StringPiece(&build_id[0], build_id_size));
}
TEST(ElfReaderTest, ReadElfBuildIdLowercase) {
TEST_P(ElfReaderTest, ReadElfBuildIdLowercase) {
TestElfImage image =
TestElfImageBuilder()
TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID, "GNU", kBuildIdBytes)
.Build();
......@@ -53,11 +68,11 @@ TEST(ElfReaderTest, ReadElfBuildIdLowercase) {
StringPiece(&build_id[0], build_id_size));
}
TEST(ElfReaderTest, ReadElfBuildIdMultipleNotes) {
TEST_P(ElfReaderTest, ReadElfBuildIdMultipleNotes) {
constexpr uint8_t kOtherNoteBytes[] = {0xef, 0x56};
TestElfImage image =
TestElfImageBuilder()
TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID + 1, "ABC", kOtherNoteBytes)
.AddNoteSegment(NT_GNU_BUILD_ID, "GNU", kBuildIdBytes)
......@@ -69,9 +84,9 @@ TEST(ElfReaderTest, ReadElfBuildIdMultipleNotes) {
EXPECT_EQ(kBuildIdHexString, StringPiece(&build_id[0], build_id_size));
}
TEST(ElfReaderTest, ReadElfBuildIdWrongName) {
TEST_P(ElfReaderTest, ReadElfBuildIdWrongName) {
TestElfImage image =
TestElfImageBuilder()
TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID, "ABC", kBuildIdBytes)
.Build();
......@@ -81,9 +96,9 @@ TEST(ElfReaderTest, ReadElfBuildIdWrongName) {
EXPECT_EQ(0u, build_id_size);
}
TEST(ElfReaderTest, ReadElfBuildIdWrongType) {
TEST_P(ElfReaderTest, ReadElfBuildIdWrongType) {
TestElfImage image =
TestElfImageBuilder()
TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID + 1, "GNU", kBuildIdBytes)
.Build();
......@@ -93,8 +108,8 @@ TEST(ElfReaderTest, ReadElfBuildIdWrongType) {
EXPECT_EQ(0u, build_id_size);
}
TEST(ElfReaderTest, ReadElfBuildIdNoBuildId) {
TestElfImage image = TestElfImageBuilder()
TEST_P(ElfReaderTest, ReadElfBuildIdNoBuildId) {
TestElfImage image = TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.Build();
......@@ -103,7 +118,34 @@ TEST(ElfReaderTest, ReadElfBuildIdNoBuildId) {
EXPECT_EQ(0u, build_id_size);
}
TEST(ElfReaderTest, ReadElfBuildIdForCurrentElfImage) {
TEST_P(ElfReaderTest, ReadElfLibraryName) {
TestElfImage image = TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddSoName("mysoname")
.Build();
Optional<StringPiece> library_name = ReadElfLibraryName(image.elf_start());
ASSERT_NE(nullopt, library_name);
EXPECT_EQ("mysoname", *library_name);
}
TEST_P(ElfReaderTest, ReadElfLibraryNameNoSoName) {
TestElfImage image = TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.Build();
Optional<StringPiece> library_name = ReadElfLibraryName(image.elf_start());
EXPECT_EQ(nullopt, library_name);
}
INSTANTIATE_TEST_SUITE_P(
MappingTypes,
ElfReaderTest,
::testing::Values(TestElfImageBuilder::RELOCATABLE,
TestElfImageBuilder::RELOCATABLE_WITH_BIAS),
&ParamInfoToString);
TEST(ElfReaderTestWithCurrentElfImage, ReadElfBuildId) {
ElfBuildIdBuffer build_id;
size_t build_id_size = ReadElfBuildId(&__executable_start, true, build_id);
ASSERT_NE(build_id_size, 0u);
......@@ -122,27 +164,7 @@ TEST(ElfReaderTest, ReadElfBuildIdForCurrentElfImage) {
}
}
TEST(ElfReaderTest, ReadElfLibraryName) {
TestElfImage image = TestElfImageBuilder()
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddSoName("mysoname")
.Build();
Optional<StringPiece> library_name = ReadElfLibraryName(image.elf_start());
ASSERT_NE(nullopt, library_name);
EXPECT_EQ("mysoname", *library_name);
}
TEST(ElfReaderTest, ReadElfLibraryNameNoSoName) {
TestElfImage image = TestElfImageBuilder()
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.Build();
Optional<StringPiece> library_name = ReadElfLibraryName(image.elf_start());
EXPECT_EQ(nullopt, library_name);
}
TEST(ElfReaderTest, ReadElfLibraryNameForCurrentElfImage) {
TEST(ElfReaderTestWithCurrentImage, ReadElfBuildId) {
#if defined(OS_ANDROID)
// On Android the library loader memory maps the full so file.
const char kLibraryName[] = "libbase_unittests__library";
......
......@@ -10,6 +10,7 @@
#include "base/bits.h"
#include "base/check.h"
#include "base/notreached.h"
#include "build/build_config.h"
#if __SIZEOF_POINTER__ == 4
......@@ -47,7 +48,8 @@ TestElfImage::TestElfImage(TestElfImage&&) = default;
TestElfImage& TestElfImage::operator=(TestElfImage&&) = default;
TestElfImageBuilder::TestElfImageBuilder() = default;
TestElfImageBuilder::TestElfImageBuilder(MappingType mapping_type)
: mapping_type_(mapping_type) {}
TestElfImageBuilder::~TestElfImageBuilder() = default;
......@@ -103,6 +105,16 @@ struct TestElfImageBuilder::ImageMeasures {
size_t total_size;
};
Addr TestElfImageBuilder::GetVirtualAddressForOffset(Off offset) const {
switch (mapping_type_) {
case RELOCATABLE:
return static_cast<Addr>(offset);
case RELOCATABLE_WITH_BIAS:
return static_cast<Addr>(offset + kLoadBias);
}
}
TestElfImageBuilder::ImageMeasures TestElfImageBuilder::MeasureSizesAndOffsets()
const {
ImageMeasures measures;
......@@ -163,9 +175,15 @@ TestElfImageBuilder::ImageMeasures TestElfImageBuilder::MeasureSizesAndOffsets()
TestElfImage TestElfImageBuilder::Build() {
ImageMeasures measures = MeasureSizesAndOffsets();
// Write the ELF contents into |buffer|.
std::vector<uint8_t> buffer(measures.total_size + kPageSize - 1, '\0');
uint8_t* const elf_start = bits::Align(&buffer.front(), kPageSize);
// Write the ELF contents into |buffer|. Extends the buffer back to the 0
// address in the case of load bias, so that the memory between the 0 address
// and the image start is zero-initialized.
const size_t load_bias =
mapping_type_ == RELOCATABLE_WITH_BIAS ? kLoadBias : 0;
std::vector<uint8_t> buffer(load_bias + (kPageSize - 1) + measures.total_size,
'\0');
uint8_t* const elf_start =
bits::Align(&buffer.front() + load_bias, kPageSize);
uint8_t* loc = elf_start;
// Add the ELF header.
......@@ -173,7 +191,8 @@ TestElfImage TestElfImageBuilder::Build() {
// Add the program header table.
loc = bits::Align(loc, kPhdrAlign);
loc = AppendHdr(CreatePhdr(PT_PHDR, PF_R, kPhdrAlign, loc - elf_start,
loc = AppendHdr(CreatePhdr(PT_PHDR, PF_R, kPhdrAlign,
GetVirtualAddressForOffset(loc - elf_start),
sizeof(Phdr) * measures.phdrs_required),
loc);
for (size_t i = 0; i < load_segments_.size(); ++i) {
......@@ -183,19 +202,24 @@ TestElfImage TestElfImageBuilder::Build() {
// encompass all the preceding headers.
if (i == 0)
size += loc - elf_start;
loc = AppendHdr(CreatePhdr(PT_LOAD, load_segment.flags, kLoadAlign,
measures.load_segment_start[i], size),
loc);
loc = AppendHdr(
CreatePhdr(PT_LOAD, load_segment.flags, kLoadAlign,
GetVirtualAddressForOffset(measures.load_segment_start[i]),
size),
loc);
}
if (measures.note_size != 0) {
loc = AppendHdr(CreatePhdr(PT_NOTE, PF_R, kNoteAlign, measures.note_start,
loc = AppendHdr(CreatePhdr(PT_NOTE, PF_R, kNoteAlign,
GetVirtualAddressForOffset(measures.note_start),
measures.note_size),
loc);
}
if (soname_) {
loc = AppendHdr(CreatePhdr(PT_DYNAMIC, PF_R | PF_W, kDynamicAlign,
measures.dynamic_start, sizeof(Dyn) * 2),
loc);
loc =
AppendHdr(CreatePhdr(PT_DYNAMIC, PF_R | PF_W, kDynamicAlign,
GetVirtualAddressForOffset(measures.dynamic_start),
sizeof(Dyn) * 2),
loc);
}
// Add the notes.
......@@ -227,9 +251,12 @@ TestElfImage TestElfImageBuilder::Build() {
Dyn* strtab_dyn = reinterpret_cast<Dyn*>(loc);
strtab_dyn->d_tag = DT_STRTAB;
#if defined(OS_FUCHSIA) || defined(OS_ANDROID)
// Fuchsia and Android do not relocate the symtab pointer on ELF load.
strtab_dyn->d_un.d_ptr = measures.strtab_start;
// Fuchsia and Android do not alter the symtab pointer on ELF load -- it's
// expected to remain a 'virutal address'.
strtab_dyn->d_un.d_ptr = GetVirtualAddressForOffset(measures.strtab_start);
#else
// Linux relocates this value on ELF load, so produce the pointer value after
// relocation. That value will always be equal to the actual memory address.
strtab_dyn->d_un.d_ptr =
reinterpret_cast<uintptr_t>(elf_start + measures.strtab_start);
#endif
......
......@@ -16,12 +16,14 @@
#include "base/strings/string_piece.h"
#if __SIZEOF_POINTER__ == 4
using Addr = Elf32_Addr;
using Ehdr = Elf32_Ehdr;
using Half = Elf32_Half;
using Off = Elf32_Off;
using Phdr = Elf32_Phdr;
using Word = Elf32_Word;
#else
using Addr = Elf64_Addr;
using Ehdr = Elf64_Ehdr;
using Half = Elf64_Half;
using Off = Elf64_Off;
......@@ -53,7 +55,13 @@ class TestElfImage {
// Builds an in-memory image of an ELF file for testing.
class TestElfImageBuilder {
public:
TestElfImageBuilder();
// The type of mapping to use for virtual addresses in the ELF file.
enum MappingType {
RELOCATABLE, // Virtual address == file offset.
RELOCATABLE_WITH_BIAS // Virtual address == file offset + load bias.
};
explicit TestElfImageBuilder(MappingType mapping_type);
~TestElfImageBuilder();
TestElfImageBuilder(const TestElfImageBuilder&) = delete;
......@@ -81,6 +89,13 @@ class TestElfImageBuilder {
// Computed sizing state for parts of the ELF image.
struct ImageMeasures;
// Gets the 'virtual address' corresponding to |offset| to write into the
// image, according to |mapping_type_|. Relocatable ELF images have virtual
// addresses equal to the offset with a possible constant load bias.
// Non-relocatable ELF images have virtual addresses equal to the actual
// memory address.
Addr GetVirtualAddressForOffset(Off offset) const;
// Measures sizes/start offset of segments in the image.
ImageMeasures MeasureSizesAndOffsets() const;
......@@ -92,6 +107,11 @@ class TestElfImageBuilder {
Ehdr CreateEhdr(Half phnum);
Phdr CreatePhdr(Word type, Word flags, size_t align, Off offset, size_t size);
// The load bias to use for RELOCATABLE_WITH_BIAS. 0xc000 is a commonly used
// load bias for Android system ELF images.
static constexpr size_t kLoadBias = 0xc000;
const MappingType mapping_type_;
std::vector<std::vector<uint8_t>> note_contents_;
std::vector<LoadSegment> load_segments_;
Optional<std::string> soname_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment