Commit f1ff2df0 authored by Mike Wittman's avatar Mike Wittman Committed by Commit Bot

Add an ELF image builder and use for ElfReader tests

Adds a simple builder to create in-memory ELF images to support more
comprehensive unit tests, and adds tests for scenarios which were
previously untested. The ELF images produced by the builder have been
validated by dumping their contents using eu-readelf.

Currently the builder and tests only cover the relocatable case where
virtual addresses in the file are the same as the corresponding file
offsets.  The builder and tests will be extended in follow-on CLs to
support relocatable images loaded at an offset, and non-relocatable
images.

Also corrects a comment in the .cc which wrongly implied position
independent code is not used within non-Fuchsia non-Android ELF files.

Bug: 1105170
Change-Id: I35ae76beeb2ddb50602b0d28757673671990bb9c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2304664
Commit-Queue: Mike Wittman <wittman@chromium.org>
Reviewed-by: default avatarWez <wez@chromium.org>
Cr-Commit-Position: refs/heads/master@{#792384}
parent d2c0e890
......@@ -3195,7 +3195,11 @@ test("base_unittests") {
}
if (is_fuchsia || is_linux) {
sources += [ "debug/elf_reader_unittest.cc" ]
sources += [
"debug/elf_reader_unittest.cc",
"debug/test_elf_image_builder.cc",
"debug/test_elf_image_builder.h",
]
deps += [ "//base/test:malloc_wrapper" ]
defines += [
......@@ -3271,6 +3275,8 @@ test("base_unittests") {
sources += [
"debug/elf_reader_unittest.cc",
"debug/proc_maps_linux_unittest.cc",
"debug/test_elf_image_builder.cc",
"debug/test_elf_image_builder.h",
]
}
......
......@@ -168,9 +168,7 @@ Optional<StringPiece> ReadElfLibraryName(const void* elf_mapped_base) {
++dynamic_iter) {
if (dynamic_iter->d_tag == DT_STRTAB) {
#if defined(OS_FUCHSIA) || defined(OS_ANDROID)
// Fuchsia and Android executables are position-independent, so treat
// pointers in the ELF header as offsets into the address space instead
// of absolute addresses.
// 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;
#else
strtab_addr = (const char*)dynamic_iter->d_un.d_ptr;
......
......@@ -6,8 +6,9 @@
#include <dlfcn.h>
#include <string>
#include <cstdint>
#include "base/debug/test_elf_image_builder.h"
#include "base/files/memory_mapped_file.h"
#include "base/native_library.h"
#include "base/strings/string_util.h"
......@@ -19,17 +20,100 @@ extern char __executable_start;
namespace base {
namespace debug {
#if defined(OFFICIAL_BUILD)
constexpr size_t kExpectedBuildIdStringLength = 40; // SHA1 hash in hex.
#else
constexpr size_t kExpectedBuildIdStringLength = 16; // 64-bit int in hex.
#endif
namespace {
constexpr uint8_t kBuildIdBytes[] = {0xab, 0xcd, 0x12, 0x34};
constexpr const char kBuildIdHexString[] = "ABCD1234";
constexpr const char kBuildIdHexStringLower[] = "ABCD1234";
} // namespace
TEST(ElfReaderTest, ReadElfBuildIdUppercase) {
TestElfImage image =
TestElfImageBuilder()
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID, "GNU", kBuildIdBytes)
.Build();
ElfBuildIdBuffer build_id;
size_t build_id_size = ReadElfBuildId(image.elf_start(), true, build_id);
EXPECT_EQ(8u, build_id_size);
EXPECT_EQ(kBuildIdHexString, StringPiece(&build_id[0], build_id_size));
}
TEST(ElfReaderTest, ReadElfBuildIdLowercase) {
TestElfImage image =
TestElfImageBuilder()
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID, "GNU", kBuildIdBytes)
.Build();
ElfBuildIdBuffer build_id;
size_t build_id_size = ReadElfBuildId(image.elf_start(), false, build_id);
EXPECT_EQ(8u, build_id_size);
EXPECT_EQ(ToLowerASCII(kBuildIdHexStringLower),
StringPiece(&build_id[0], build_id_size));
}
TEST(ElfReaderTest, ReadElfBuildIdMultipleNotes) {
constexpr uint8_t kOtherNoteBytes[] = {0xef, 0x56};
TestElfImage image =
TestElfImageBuilder()
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID + 1, "ABC", kOtherNoteBytes)
.AddNoteSegment(NT_GNU_BUILD_ID, "GNU", kBuildIdBytes)
.Build();
ElfBuildIdBuffer build_id;
size_t build_id_size = ReadElfBuildId(image.elf_start(), true, build_id);
EXPECT_EQ(8u, build_id_size);
EXPECT_EQ(kBuildIdHexString, StringPiece(&build_id[0], build_id_size));
}
TEST(ElfReaderTest, ReadElfBuildIdWrongName) {
TestElfImage image =
TestElfImageBuilder()
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID, "ABC", kBuildIdBytes)
.Build();
ElfBuildIdBuffer build_id;
size_t build_id_size = ReadElfBuildId(image.elf_start(), true, build_id);
EXPECT_EQ(0u, build_id_size);
}
TEST(ElfReaderTest, ReadElfBuildIdWrongType) {
TestElfImage image =
TestElfImageBuilder()
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.AddNoteSegment(NT_GNU_BUILD_ID + 1, "GNU", kBuildIdBytes)
.Build();
ElfBuildIdBuffer build_id;
size_t build_id_size = ReadElfBuildId(image.elf_start(), true, build_id);
EXPECT_EQ(0u, build_id_size);
}
TEST(ElfReaderTest, ReadElfBuildIdNoBuildId) {
TestElfImage image = TestElfImageBuilder()
.AddLoadSegment(PF_R | PF_X, /* size = */ 2000)
.Build();
ElfBuildIdBuffer build_id;
size_t build_id_size = ReadElfBuildId(image.elf_start(), true, build_id);
EXPECT_EQ(0u, build_id_size);
}
TEST(ElfReaderTest, ReadElfBuildIdForCurrentElfImage) {
ElfBuildIdBuffer build_id;
size_t build_id_size = ReadElfBuildId(&__executable_start, true, build_id);
ASSERT_NE(build_id_size, 0u);
#if defined(OFFICIAL_BUILD)
constexpr size_t kExpectedBuildIdStringLength = 40; // SHA1 hash in hex.
#else
constexpr size_t kExpectedBuildIdStringLength = 16; // 64-bit int in hex.
#endif
EXPECT_EQ(kExpectedBuildIdStringLength, build_id_size);
for (size_t i = 0; i < build_id_size; ++i) {
char c = build_id[i];
......@@ -38,20 +122,27 @@ TEST(ElfReaderTest, ReadElfBuildIdUppercase) {
}
}
TEST(ElfReaderTest, ReadElfBuildIdLowercase) {
ElfBuildIdBuffer build_id;
size_t build_id_size = ReadElfBuildId(&__executable_start, false, build_id);
ASSERT_NE(build_id_size, 0u);
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);
}
EXPECT_EQ(kExpectedBuildIdStringLength, build_id_size);
for (size_t i = 0; i < kExpectedBuildIdStringLength; ++i) {
char c = build_id[i];
EXPECT_TRUE(IsHexDigit(c));
EXPECT_TRUE(!IsAsciiAlpha(c) || IsAsciiLower(c));
}
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, ReadElfLibraryName) {
TEST(ElfReaderTest, ReadElfLibraryNameForCurrentElfImage) {
#if defined(OS_ANDROID)
// On Android the library loader memory maps the full so file.
const char kLibraryName[] = "libbase_unittests__library";
......
// Copyright 2018 The Chromium 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 "base/debug/test_elf_image_builder.h"
#include <cstring>
#include <type_traits>
#include <utility>
#include "base/bits.h"
#include "base/check.h"
#include "build/build_config.h"
#if __SIZEOF_POINTER__ == 4
using Dyn = Elf32_Dyn;
using Nhdr = Elf32_Nhdr;
using Shdr = Elf32_Shdr;
#else
using Dyn = Elf64_Dyn;
using Nhdr = Elf64_Nhdr;
using Shdr = Elf64_Shdr;
#endif
namespace base {
namespace {
// Sizes/alignments to use in the ELF image.
static constexpr size_t kPageSize = 4096;
static constexpr size_t kPhdrAlign = 0x4;
static constexpr size_t kNoteAlign = 0x4;
static constexpr size_t kLoadAlign = 0x1000;
static constexpr size_t kDynamicAlign = 0x4;
} // namespace
struct TestElfImageBuilder::LoadSegment {
Word flags;
Word size;
};
TestElfImage::TestElfImage(std::vector<uint8_t> buffer, const void* elf_start)
: buffer_(std::move(buffer)), elf_start_(elf_start) {}
TestElfImage::~TestElfImage() = default;
TestElfImage::TestElfImage(TestElfImage&&) = default;
TestElfImage& TestElfImage::operator=(TestElfImage&&) = default;
TestElfImageBuilder::TestElfImageBuilder() = default;
TestElfImageBuilder::~TestElfImageBuilder() = default;
TestElfImageBuilder& TestElfImageBuilder::AddLoadSegment(Word flags,
size_t size) {
load_segments_.push_back({flags, size});
return *this;
}
TestElfImageBuilder& TestElfImageBuilder::AddNoteSegment(
Word type,
StringPiece name,
span<const uint8_t> desc) {
const size_t name_with_null_size = name.size() + 1;
std::vector<uint8_t> buffer(sizeof(Nhdr) +
bits::Align(name_with_null_size, 4) +
bits::Align(desc.size(), 4),
'\0');
uint8_t* loc = &buffer.front();
Nhdr* nhdr = reinterpret_cast<Nhdr*>(loc);
nhdr->n_namesz = name_with_null_size;
nhdr->n_descsz = desc.size();
nhdr->n_type = type;
loc += sizeof(Nhdr);
memcpy(loc, name.data(), name.size());
*(loc + name.size()) = '\0';
loc += bits::Align(name_with_null_size, 4);
memcpy(loc, &desc.front(), desc.size());
loc += bits::Align(desc.size(), 4);
DCHECK_EQ(&buffer.front() + buffer.size(), loc);
note_contents_.push_back(std::move(buffer));
return *this;
}
TestElfImageBuilder& TestElfImageBuilder::AddSoName(StringPiece soname) {
DCHECK(!soname_.has_value());
soname_.emplace(soname);
return *this;
}
struct TestElfImageBuilder::ImageMeasures {
size_t phdrs_required;
size_t note_start;
size_t note_size;
std::vector<size_t> load_segment_start;
size_t dynamic_start;
size_t strtab_start;
size_t total_size;
};
TestElfImageBuilder::ImageMeasures TestElfImageBuilder::MeasureSizesAndOffsets()
const {
ImageMeasures measures;
measures.phdrs_required = 1 + load_segments_.size();
if (!note_contents_.empty())
++measures.phdrs_required;
if (soname_.has_value())
++measures.phdrs_required;
// The current offset into the image, where the next bytes are to be written.
// Starts after the ELF header.
size_t offset = sizeof(Ehdr);
// Add space for the program header table.
offset = bits::Align(offset, kPhdrAlign);
offset += sizeof(Phdr) * measures.phdrs_required;
// Add space for the notes.
measures.note_start = offset;
if (!note_contents_.empty())
offset = bits::Align(offset, kNoteAlign);
for (const std::vector<uint8_t>& contents : note_contents_)
offset += contents.size();
measures.note_size = offset - measures.note_start;
// Add space for the load segments.
for (auto it = load_segments_.begin(); it != load_segments_.end(); ++it) {
size_t size = 0;
// The first non PT_PHDR program header is expected to be a PT_LOAD and
// start at the already-aligned start of the ELF header.
if (it == load_segments_.begin()) {
size = offset + it->size;
measures.load_segment_start.push_back(0);
} else {
offset = bits::Align(offset, kLoadAlign);
size = it->size;
measures.load_segment_start.push_back(offset);
}
offset += it->size;
}
// Add space for the dynamic segment.
measures.dynamic_start = bits::Align(offset, kDynamicAlign);
offset += sizeof(Dyn) * (soname_ ? 2 : 1);
measures.strtab_start = offset;
// Add space for the string table.
++offset; // The first string table byte holds a null character.
if (soname_)
offset += soname_->size() + 1;
measures.total_size = offset;
return measures;
}
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);
uint8_t* loc = elf_start;
// Add the ELF header.
loc = AppendHdr(CreateEhdr(measures.phdrs_required), loc);
// Add the program header table.
loc = bits::Align(loc, kPhdrAlign);
loc = AppendHdr(CreatePhdr(PT_PHDR, PF_R, kPhdrAlign, loc - elf_start,
sizeof(Phdr) * measures.phdrs_required),
loc);
for (size_t i = 0; i < load_segments_.size(); ++i) {
const LoadSegment& load_segment = load_segments_[i];
size_t size = load_segment.size;
// The first non PT_PHDR program header is expected to be a PT_LOAD and
// 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);
}
if (measures.note_size != 0) {
loc = AppendHdr(CreatePhdr(PT_NOTE, PF_R, kNoteAlign, 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);
}
// Add the notes.
loc = bits::Align(loc, kNoteAlign);
for (const std::vector<uint8_t>& contents : note_contents_) {
memcpy(loc, &contents.front(), contents.size());
loc += contents.size();
}
// Add the load segments.
for (auto it = load_segments_.begin(); it != load_segments_.end(); ++it) {
if (it != load_segments_.begin())
loc = bits::Align(loc, kLoadAlign);
memset(loc, 0, it->size);
loc += it->size;
}
loc = bits::Align(loc, kDynamicAlign);
// Add the soname state.
if (soname_) {
// Add a DYNAMIC section for the soname.
Dyn* soname_dyn = reinterpret_cast<Dyn*>(loc);
soname_dyn->d_tag = DT_SONAME;
soname_dyn->d_un.d_val = 1; // One char into the string table.
loc += sizeof(Dyn);
}
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;
#else
strtab_dyn->d_un.d_ptr =
reinterpret_cast<uintptr_t>(elf_start + measures.strtab_start);
#endif
loc += sizeof(Dyn);
// Add a string table with one entry for the soname, if necessary.
*loc++ = '\0'; // The first byte holds a null character.
if (soname_) {
memcpy(loc, soname_->data(), soname_->size());
*(loc + soname_->size()) = '\0';
loc += soname_->size() + 1;
}
// The offset past the end of the contents should be consistent with the size
// mmeasurement above.
DCHECK_EQ(loc, elf_start + measures.total_size);
return TestElfImage(std::move(buffer), elf_start);
}
// static
template <typename T>
uint8_t* TestElfImageBuilder::AppendHdr(const T& hdr, uint8_t* loc) {
static_assert(std::is_trivially_copyable<T>::value,
"T should be a plain struct");
memcpy(loc, &hdr, sizeof(T));
return loc + sizeof(T);
}
Ehdr TestElfImageBuilder::CreateEhdr(Half phnum) {
Ehdr ehdr;
ehdr.e_ident[EI_MAG0] = ELFMAG0;
ehdr.e_ident[EI_MAG1] = ELFMAG1;
ehdr.e_ident[EI_MAG2] = ELFMAG2;
ehdr.e_ident[EI_MAG3] = ELFMAG3;
ehdr.e_ident[EI_CLASS] = __SIZEOF_POINTER__ == 4 ? 1 : 2;
ehdr.e_ident[EI_DATA] = 1; // Little endian.
ehdr.e_ident[EI_VERSION] = 1;
ehdr.e_ident[EI_OSABI] = 0x00;
ehdr.e_ident[EI_ABIVERSION] = 0;
ehdr.e_ident[EI_PAD] = 0;
ehdr.e_type = ET_DYN;
ehdr.e_machine = 0x28; // ARM.
ehdr.e_version = 1;
ehdr.e_entry = 0;
ehdr.e_phoff = sizeof(Ehdr);
ehdr.e_shoff = 0;
ehdr.e_flags = 0;
ehdr.e_ehsize = sizeof(Ehdr);
ehdr.e_phentsize = sizeof(Phdr);
ehdr.e_phnum = phnum;
ehdr.e_shentsize = sizeof(Shdr);
ehdr.e_shnum = 0;
ehdr.e_shstrndx = 0;
return ehdr;
}
Phdr TestElfImageBuilder::CreatePhdr(Word type,
Word flags,
size_t align,
Off offset,
size_t size) {
Phdr phdr;
phdr.p_type = type;
phdr.p_flags = flags;
phdr.p_offset = offset;
phdr.p_filesz = size;
phdr.p_vaddr = phdr.p_offset;
phdr.p_paddr = 0;
phdr.p_memsz = phdr.p_filesz;
phdr.p_align = align;
return phdr;
}
} // namespace base
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_DEBUG_TEST_ELF_IMAGE_BUILDER_H_
#define BASE_DEBUG_TEST_ELF_IMAGE_BUILDER_H_
#include <elf.h>
#include <cstdint>
#include <string>
#include <vector>
#include "base/containers/span.h"
#include "base/optional.h"
#include "base/strings/string_piece.h"
#if __SIZEOF_POINTER__ == 4
using Ehdr = Elf32_Ehdr;
using Half = Elf32_Half;
using Off = Elf32_Off;
using Phdr = Elf32_Phdr;
using Word = Elf32_Word;
#else
using Ehdr = Elf64_Ehdr;
using Half = Elf64_Half;
using Off = Elf64_Off;
using Phdr = Elf64_Phdr;
using Word = Elf64_Word;
#endif
namespace base {
// In-memory ELF image constructed by TestElfImageBuilder.
class TestElfImage {
public:
// |buffer| is a memory buffer containing the ELF image. |elf_start| is the
// start address of the ELF image within the buffer.
TestElfImage(std::vector<uint8_t> buffer, const void* elf_start);
~TestElfImage();
TestElfImage(TestElfImage&&);
TestElfImage& operator=(TestElfImage&&);
// The start address of the ELF image.
const void* elf_start() const { return elf_start_; }
private:
std::vector<uint8_t> buffer_;
const void* elf_start_;
};
// Builds an in-memory image of an ELF file for testing.
class TestElfImageBuilder {
public:
TestElfImageBuilder();
~TestElfImageBuilder();
TestElfImageBuilder(const TestElfImageBuilder&) = delete;
TestElfImageBuilder& operator=(const TestElfImageBuilder&) = delete;
// Add a PT_LOAD segment with the specified rwx |flags|. The contents will be
// filled with |size| bytes of zeros.
TestElfImageBuilder& AddLoadSegment(Word flags, size_t size);
// Add a PT_NOTE segment with the specified state.
TestElfImageBuilder& AddNoteSegment(Word type,
StringPiece name,
span<const uint8_t> desc);
// Adds a DT_SONAME dynamic section and the necessary state to support it. May
// be invoked at most once.
TestElfImageBuilder& AddSoName(StringPiece soname);
TestElfImage Build();
private:
// Properties of a load segment to create.
struct LoadSegment;
// Computed sizing state for parts of the ELF image.
struct ImageMeasures;
// Measures sizes/start offset of segments in the image.
ImageMeasures MeasureSizesAndOffsets() const;
// Appends a header of type |T| at |loc|, a memory address within the ELF
// image being constructed, and returns the address past the header.
template <typename T>
static uint8_t* AppendHdr(const T& hdr, uint8_t* loc);
Ehdr CreateEhdr(Half phnum);
Phdr CreatePhdr(Word type, Word flags, size_t align, Off offset, size_t size);
std::vector<std::vector<uint8_t>> note_contents_;
std::vector<LoadSegment> load_segments_;
Optional<std::string> soname_;
};
} // namespace base
#endif // BASE_DEBUG_TEST_ELF_IMAGE_BUILDER_H_
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