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, ...@@ -146,7 +146,7 @@ size_t ReadElfBuildId(const void* elf_mapped_base,
Optional<StringPiece> ReadElfLibraryName(const void* elf_mapped_base) { Optional<StringPiece> ReadElfLibraryName(const void* elf_mapped_base) {
// NOTE: Function should use async signal safe calls only. // 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); const Ehdr* elf_header = GetElfHeader(elf_mapped_base);
if (!elf_header) if (!elf_header)
return {}; return {};
...@@ -159,9 +159,9 @@ Optional<StringPiece> ReadElfLibraryName(const void* elf_mapped_base) { ...@@ -159,9 +159,9 @@ Optional<StringPiece> ReadElfLibraryName(const void* elf_mapped_base) {
// SONAME offsets, which are used to compute the offset of the library // SONAME offsets, which are used to compute the offset of the library
// name string. // name string.
const Dyn* dynamic_start = 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*>( 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; Word soname_strtab_offset = 0;
const char* strtab_addr = 0; const char* strtab_addr = 0;
for (const Dyn* dynamic_iter = dynamic_start; dynamic_iter < dynamic_end; for (const Dyn* dynamic_iter = dynamic_start; dynamic_iter < dynamic_end;
...@@ -169,7 +169,8 @@ Optional<StringPiece> ReadElfLibraryName(const void* elf_mapped_base) { ...@@ -169,7 +169,8 @@ Optional<StringPiece> ReadElfLibraryName(const void* elf_mapped_base) {
if (dynamic_iter->d_tag == DT_STRTAB) { if (dynamic_iter->d_tag == DT_STRTAB) {
#if defined(OS_FUCHSIA) || defined(OS_ANDROID) #if defined(OS_FUCHSIA) || defined(OS_ANDROID)
// Fuchsia and Android do not relocate the symtab pointer on ELF load. // 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 #else
strtab_addr = (const char*)dynamic_iter->d_un.d_ptr; strtab_addr = (const char*)dynamic_iter->d_un.d_ptr;
#endif #endif
......
...@@ -24,11 +24,26 @@ namespace { ...@@ -24,11 +24,26 @@ namespace {
constexpr uint8_t kBuildIdBytes[] = {0xab, 0xcd, 0x12, 0x34}; constexpr uint8_t kBuildIdBytes[] = {0xab, 0xcd, 0x12, 0x34};
constexpr const char kBuildIdHexString[] = "ABCD1234"; constexpr const char kBuildIdHexString[] = "ABCD1234";
constexpr const char kBuildIdHexStringLower[] = "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 } // namespace
TEST(ElfReaderTest, ReadElfBuildIdUppercase) { using ElfReaderTest =
::testing::TestWithParam<TestElfImageBuilder::MappingType>;
TEST_P(ElfReaderTest, ReadElfBuildIdUppercase) {
TestElfImage image = TestElfImage image =
TestElfImageBuilder() TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000) .AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID, "GNU", kBuildIdBytes) .AddNoteSegment(NT_GNU_BUILD_ID, "GNU", kBuildIdBytes)
.Build(); .Build();
...@@ -39,9 +54,9 @@ TEST(ElfReaderTest, ReadElfBuildIdUppercase) { ...@@ -39,9 +54,9 @@ TEST(ElfReaderTest, ReadElfBuildIdUppercase) {
EXPECT_EQ(kBuildIdHexString, StringPiece(&build_id[0], build_id_size)); EXPECT_EQ(kBuildIdHexString, StringPiece(&build_id[0], build_id_size));
} }
TEST(ElfReaderTest, ReadElfBuildIdLowercase) { TEST_P(ElfReaderTest, ReadElfBuildIdLowercase) {
TestElfImage image = TestElfImage image =
TestElfImageBuilder() TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000) .AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID, "GNU", kBuildIdBytes) .AddNoteSegment(NT_GNU_BUILD_ID, "GNU", kBuildIdBytes)
.Build(); .Build();
...@@ -53,11 +68,11 @@ TEST(ElfReaderTest, ReadElfBuildIdLowercase) { ...@@ -53,11 +68,11 @@ TEST(ElfReaderTest, ReadElfBuildIdLowercase) {
StringPiece(&build_id[0], build_id_size)); StringPiece(&build_id[0], build_id_size));
} }
TEST(ElfReaderTest, ReadElfBuildIdMultipleNotes) { TEST_P(ElfReaderTest, ReadElfBuildIdMultipleNotes) {
constexpr uint8_t kOtherNoteBytes[] = {0xef, 0x56}; constexpr uint8_t kOtherNoteBytes[] = {0xef, 0x56};
TestElfImage image = TestElfImage image =
TestElfImageBuilder() TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000) .AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID + 1, "ABC", kOtherNoteBytes) .AddNoteSegment(NT_GNU_BUILD_ID + 1, "ABC", kOtherNoteBytes)
.AddNoteSegment(NT_GNU_BUILD_ID, "GNU", kBuildIdBytes) .AddNoteSegment(NT_GNU_BUILD_ID, "GNU", kBuildIdBytes)
...@@ -69,9 +84,9 @@ TEST(ElfReaderTest, ReadElfBuildIdMultipleNotes) { ...@@ -69,9 +84,9 @@ TEST(ElfReaderTest, ReadElfBuildIdMultipleNotes) {
EXPECT_EQ(kBuildIdHexString, StringPiece(&build_id[0], build_id_size)); EXPECT_EQ(kBuildIdHexString, StringPiece(&build_id[0], build_id_size));
} }
TEST(ElfReaderTest, ReadElfBuildIdWrongName) { TEST_P(ElfReaderTest, ReadElfBuildIdWrongName) {
TestElfImage image = TestElfImage image =
TestElfImageBuilder() TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000) .AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID, "ABC", kBuildIdBytes) .AddNoteSegment(NT_GNU_BUILD_ID, "ABC", kBuildIdBytes)
.Build(); .Build();
...@@ -81,9 +96,9 @@ TEST(ElfReaderTest, ReadElfBuildIdWrongName) { ...@@ -81,9 +96,9 @@ TEST(ElfReaderTest, ReadElfBuildIdWrongName) {
EXPECT_EQ(0u, build_id_size); EXPECT_EQ(0u, build_id_size);
} }
TEST(ElfReaderTest, ReadElfBuildIdWrongType) { TEST_P(ElfReaderTest, ReadElfBuildIdWrongType) {
TestElfImage image = TestElfImage image =
TestElfImageBuilder() TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000) .AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID + 1, "GNU", kBuildIdBytes) .AddNoteSegment(NT_GNU_BUILD_ID + 1, "GNU", kBuildIdBytes)
.Build(); .Build();
...@@ -93,8 +108,8 @@ TEST(ElfReaderTest, ReadElfBuildIdWrongType) { ...@@ -93,8 +108,8 @@ TEST(ElfReaderTest, ReadElfBuildIdWrongType) {
EXPECT_EQ(0u, build_id_size); EXPECT_EQ(0u, build_id_size);
} }
TEST(ElfReaderTest, ReadElfBuildIdNoBuildId) { TEST_P(ElfReaderTest, ReadElfBuildIdNoBuildId) {
TestElfImage image = TestElfImageBuilder() TestElfImage image = TestElfImageBuilder(GetParam())
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000) .AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.Build(); .Build();
...@@ -103,7 +118,34 @@ TEST(ElfReaderTest, ReadElfBuildIdNoBuildId) { ...@@ -103,7 +118,34 @@ TEST(ElfReaderTest, ReadElfBuildIdNoBuildId) {
EXPECT_EQ(0u, build_id_size); 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; ElfBuildIdBuffer build_id;
size_t build_id_size = ReadElfBuildId(&__executable_start, true, build_id); size_t build_id_size = ReadElfBuildId(&__executable_start, true, build_id);
ASSERT_NE(build_id_size, 0u); ASSERT_NE(build_id_size, 0u);
...@@ -122,27 +164,7 @@ TEST(ElfReaderTest, ReadElfBuildIdForCurrentElfImage) { ...@@ -122,27 +164,7 @@ TEST(ElfReaderTest, ReadElfBuildIdForCurrentElfImage) {
} }
} }
TEST(ElfReaderTest, ReadElfLibraryName) { TEST(ElfReaderTestWithCurrentImage, ReadElfBuildId) {
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) {
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
// On Android the library loader memory maps the full so file. // On Android the library loader memory maps the full so file.
const char kLibraryName[] = "libbase_unittests__library"; const char kLibraryName[] = "libbase_unittests__library";
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/bits.h" #include "base/bits.h"
#include "base/check.h" #include "base/check.h"
#include "base/notreached.h"
#include "build/build_config.h" #include "build/build_config.h"
#if __SIZEOF_POINTER__ == 4 #if __SIZEOF_POINTER__ == 4
...@@ -47,7 +48,8 @@ TestElfImage::TestElfImage(TestElfImage&&) = default; ...@@ -47,7 +48,8 @@ TestElfImage::TestElfImage(TestElfImage&&) = default;
TestElfImage& TestElfImage::operator=(TestElfImage&&) = default; TestElfImage& TestElfImage::operator=(TestElfImage&&) = default;
TestElfImageBuilder::TestElfImageBuilder() = default; TestElfImageBuilder::TestElfImageBuilder(MappingType mapping_type)
: mapping_type_(mapping_type) {}
TestElfImageBuilder::~TestElfImageBuilder() = default; TestElfImageBuilder::~TestElfImageBuilder() = default;
...@@ -103,6 +105,16 @@ struct TestElfImageBuilder::ImageMeasures { ...@@ -103,6 +105,16 @@ struct TestElfImageBuilder::ImageMeasures {
size_t total_size; 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() TestElfImageBuilder::ImageMeasures TestElfImageBuilder::MeasureSizesAndOffsets()
const { const {
ImageMeasures measures; ImageMeasures measures;
...@@ -163,9 +175,15 @@ TestElfImageBuilder::ImageMeasures TestElfImageBuilder::MeasureSizesAndOffsets() ...@@ -163,9 +175,15 @@ TestElfImageBuilder::ImageMeasures TestElfImageBuilder::MeasureSizesAndOffsets()
TestElfImage TestElfImageBuilder::Build() { TestElfImage TestElfImageBuilder::Build() {
ImageMeasures measures = MeasureSizesAndOffsets(); ImageMeasures measures = MeasureSizesAndOffsets();
// Write the ELF contents into |buffer|. // Write the ELF contents into |buffer|. Extends the buffer back to the 0
std::vector<uint8_t> buffer(measures.total_size + kPageSize - 1, '\0'); // address in the case of load bias, so that the memory between the 0 address
uint8_t* const elf_start = bits::Align(&buffer.front(), kPageSize); // 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; uint8_t* loc = elf_start;
// Add the ELF header. // Add the ELF header.
...@@ -173,7 +191,8 @@ TestElfImage TestElfImageBuilder::Build() { ...@@ -173,7 +191,8 @@ TestElfImage TestElfImageBuilder::Build() {
// Add the program header table. // Add the program header table.
loc = bits::Align(loc, kPhdrAlign); 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), sizeof(Phdr) * measures.phdrs_required),
loc); loc);
for (size_t i = 0; i < load_segments_.size(); ++i) { for (size_t i = 0; i < load_segments_.size(); ++i) {
...@@ -183,18 +202,23 @@ TestElfImage TestElfImageBuilder::Build() { ...@@ -183,18 +202,23 @@ TestElfImage TestElfImageBuilder::Build() {
// encompass all the preceding headers. // encompass all the preceding headers.
if (i == 0) if (i == 0)
size += loc - elf_start; size += loc - elf_start;
loc = AppendHdr(CreatePhdr(PT_LOAD, load_segment.flags, kLoadAlign, loc = AppendHdr(
measures.load_segment_start[i], size), CreatePhdr(PT_LOAD, load_segment.flags, kLoadAlign,
GetVirtualAddressForOffset(measures.load_segment_start[i]),
size),
loc); loc);
} }
if (measures.note_size != 0) { 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), measures.note_size),
loc); loc);
} }
if (soname_) { if (soname_) {
loc = AppendHdr(CreatePhdr(PT_DYNAMIC, PF_R | PF_W, kDynamicAlign, loc =
measures.dynamic_start, sizeof(Dyn) * 2), AppendHdr(CreatePhdr(PT_DYNAMIC, PF_R | PF_W, kDynamicAlign,
GetVirtualAddressForOffset(measures.dynamic_start),
sizeof(Dyn) * 2),
loc); loc);
} }
...@@ -227,9 +251,12 @@ TestElfImage TestElfImageBuilder::Build() { ...@@ -227,9 +251,12 @@ TestElfImage TestElfImageBuilder::Build() {
Dyn* strtab_dyn = reinterpret_cast<Dyn*>(loc); Dyn* strtab_dyn = reinterpret_cast<Dyn*>(loc);
strtab_dyn->d_tag = DT_STRTAB; strtab_dyn->d_tag = DT_STRTAB;
#if defined(OS_FUCHSIA) || defined(OS_ANDROID) #if defined(OS_FUCHSIA) || defined(OS_ANDROID)
// Fuchsia and Android do not relocate the symtab pointer on ELF load. // Fuchsia and Android do not alter the symtab pointer on ELF load -- it's
strtab_dyn->d_un.d_ptr = measures.strtab_start; // expected to remain a 'virutal address'.
strtab_dyn->d_un.d_ptr = GetVirtualAddressForOffset(measures.strtab_start);
#else #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 = strtab_dyn->d_un.d_ptr =
reinterpret_cast<uintptr_t>(elf_start + measures.strtab_start); reinterpret_cast<uintptr_t>(elf_start + measures.strtab_start);
#endif #endif
......
...@@ -16,12 +16,14 @@ ...@@ -16,12 +16,14 @@
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#if __SIZEOF_POINTER__ == 4 #if __SIZEOF_POINTER__ == 4
using Addr = Elf32_Addr;
using Ehdr = Elf32_Ehdr; using Ehdr = Elf32_Ehdr;
using Half = Elf32_Half; using Half = Elf32_Half;
using Off = Elf32_Off; using Off = Elf32_Off;
using Phdr = Elf32_Phdr; using Phdr = Elf32_Phdr;
using Word = Elf32_Word; using Word = Elf32_Word;
#else #else
using Addr = Elf64_Addr;
using Ehdr = Elf64_Ehdr; using Ehdr = Elf64_Ehdr;
using Half = Elf64_Half; using Half = Elf64_Half;
using Off = Elf64_Off; using Off = Elf64_Off;
...@@ -53,7 +55,13 @@ class TestElfImage { ...@@ -53,7 +55,13 @@ class TestElfImage {
// Builds an in-memory image of an ELF file for testing. // Builds an in-memory image of an ELF file for testing.
class TestElfImageBuilder { class TestElfImageBuilder {
public: 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();
TestElfImageBuilder(const TestElfImageBuilder&) = delete; TestElfImageBuilder(const TestElfImageBuilder&) = delete;
...@@ -81,6 +89,13 @@ class TestElfImageBuilder { ...@@ -81,6 +89,13 @@ class TestElfImageBuilder {
// Computed sizing state for parts of the ELF image. // Computed sizing state for parts of the ELF image.
struct ImageMeasures; 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. // Measures sizes/start offset of segments in the image.
ImageMeasures MeasureSizesAndOffsets() const; ImageMeasures MeasureSizesAndOffsets() const;
...@@ -92,6 +107,11 @@ class TestElfImageBuilder { ...@@ -92,6 +107,11 @@ class TestElfImageBuilder {
Ehdr CreateEhdr(Half phnum); Ehdr CreateEhdr(Half phnum);
Phdr CreatePhdr(Word type, Word flags, size_t align, Off offset, size_t size); 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<std::vector<uint8_t>> note_contents_;
std::vector<LoadSegment> load_segments_; std::vector<LoadSegment> load_segments_;
Optional<std::string> soname_; 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