Commit 87363cc1 authored by Mike Wittman's avatar Mike Wittman Committed by Commit Bot

[base] Add ElfReader tests for non-relocatable files

Updates the image builder to support non-relocatable ELF images and
instantiates the tests for the non-relocatable images.

Replaces GetElfBaseVirtualAddress() with GetRelocationOffset().
Relocation is a well defined concept in the ELF standard, while
'base virtual address' is not, and is at odds with the 'base address'
concept.  For clarity, avoids the implicit assumption that the first
load segment's file offset is 0.

Makes the handling of the ELF base address consistent among the
functions.

Bug: 1101023
Change-Id: I6eac949a79a02e7e3a662f604193bf414d5ad38f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2305570
Commit-Queue: Mike Wittman <wittman@chromium.org>
Reviewed-by: default avatarWez <wez@chromium.org>
Reviewed-by: default avatarKevin Marshall <kmarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#794659}
parent 16a63cb8
......@@ -22,6 +22,8 @@ namespace base {
namespace debug {
namespace {
// See https://refspecs.linuxbase.org/elf/elf.pdf for the ELF specification.
#if __SIZEOF_POINTER__ == 4
using Ehdr = Elf32_Ehdr;
using Dyn = Elf32_Dyn;
......@@ -38,29 +40,36 @@ using Word = Elf64_Word;
constexpr char kGnuNoteName[] = "GNU";
// Returns a pointer to the header of the ELF binary mapped into memory,
// or a null pointer if the header is invalid.
// Returns a pointer to the header of the ELF binary mapped into memory, or a
// null pointer if the header is invalid. Here and below |elf_mapped_base| is a
// pointer to the start of the ELF image.
const Ehdr* GetElfHeader(const void* elf_mapped_base) {
const char* elf_base = reinterpret_cast<const char*>(elf_mapped_base);
if (strncmp(elf_base, ELFMAG, SELFMAG) != 0)
if (strncmp(reinterpret_cast<const char*>(elf_mapped_base), ELFMAG,
SELFMAG) != 0)
return nullptr;
const Ehdr* elf_header = reinterpret_cast<const Ehdr*>(elf_base);
return elf_header;
return reinterpret_cast<const Ehdr*>(elf_mapped_base);
}
// Returns the ELF base address that should be used as a starting point to
// access other segments.
const char* GetElfBaseVirtualAddress(const void* elf_mapped_base) {
const char* elf_base = reinterpret_cast<const char*>(elf_mapped_base);
for (const Phdr& header : GetElfProgramHeaders(elf_mapped_base)) {
// Returns the offset to add to virtual addresses in the image to compute the
// mapped virtual address.
size_t GetRelocationOffset(const void* elf_mapped_base) {
span<const Phdr> headers = GetElfProgramHeaders(elf_mapped_base);
for (const Phdr& header : headers) {
if (header.p_type == PT_LOAD) {
size_t load_bias = static_cast<size_t>(header.p_vaddr);
CHECK_GE(reinterpret_cast<uintptr_t>(elf_base), load_bias);
return elf_base - load_bias;
// |elf_mapped_base| + |header.p_offset| is the mapped address of this
// segment. |header.p_vaddr| is the specified virtual address within the
// ELF image.
const char* const mapped_address =
reinterpret_cast<const char*>(elf_mapped_base) + header.p_offset;
return reinterpret_cast<uintptr_t>(mapped_address) - header.p_vaddr;
}
}
return elf_base;
// Assume the virtual addresses in the image start at 0, so the offset is
// from 0 to the actual mapped base address.
return static_cast<size_t>(reinterpret_cast<const char*>(elf_mapped_base) -
reinterpret_cast<const char*>(0));
}
} // namespace
......@@ -68,14 +77,14 @@ const char* GetElfBaseVirtualAddress(const void* elf_mapped_base) {
span<const Phdr> GetElfProgramHeaders(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 Ehdr* elf_header = GetElfHeader(elf_mapped_base);
if (!elf_header)
return {};
return span<const Phdr>(
reinterpret_cast<const Phdr*>(elf_base + elf_header->e_phoff),
elf_header->e_phnum);
const char* phdr_start =
reinterpret_cast<const char*>(elf_header) + elf_header->e_phoff;
return span<const Phdr>(reinterpret_cast<const Phdr*>(phdr_start),
elf_header->e_phnum);
}
size_t ReadElfBuildId(const void* elf_mapped_base,
......@@ -83,17 +92,18 @@ size_t ReadElfBuildId(const void* elf_mapped_base,
ElfBuildIdBuffer build_id) {
// NOTE: Function should use async signal safe calls only.
const char* elf_virtual_base = GetElfBaseVirtualAddress(elf_mapped_base);
const Ehdr* elf_header = GetElfHeader(elf_mapped_base);
if (!elf_header)
return 0;
const size_t relocation_offset = GetRelocationOffset(elf_mapped_base);
for (const Phdr& header : GetElfProgramHeaders(elf_mapped_base)) {
if (header.p_type != PT_NOTE)
continue;
// Look for a NT_GNU_BUILD_ID note with name == "GNU".
const char* current_section = elf_virtual_base + header.p_vaddr;
const char* current_section =
reinterpret_cast<const char*>(header.p_vaddr + relocation_offset);
const char* section_end = current_section + header.p_memsz;
const Nhdr* current_note = nullptr;
bool found = false;
......@@ -146,11 +156,11 @@ 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_virtual_base = GetElfBaseVirtualAddress(elf_mapped_base);
const Ehdr* elf_header = GetElfHeader(elf_mapped_base);
if (!elf_header)
return {};
const size_t relocation_offset = GetRelocationOffset(elf_mapped_base);
for (const Phdr& header : GetElfProgramHeaders(elf_mapped_base)) {
if (header.p_type != PT_DYNAMIC)
continue;
......@@ -159,9 +169,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_virtual_base + header.p_vaddr);
reinterpret_cast<const Dyn*>(header.p_vaddr + relocation_offset);
const Dyn* dynamic_end = reinterpret_cast<const Dyn*>(
elf_virtual_base + header.p_vaddr + header.p_memsz);
header.p_vaddr + relocation_offset + 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,10 +179,10 @@ 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_virtual_base;
strtab_addr = static_cast<size_t>(dynamic_iter->d_un.d_ptr) +
reinterpret_cast<const char*>(relocation_offset);
#else
strtab_addr = (const char*)dynamic_iter->d_un.d_ptr;
strtab_addr = reinterpret_cast<const char*>(dynamic_iter->d_un.d_ptr);
#endif
} else if (dynamic_iter->d_tag == DT_SONAME) {
soname_strtab_offset = dynamic_iter->d_un.d_val;
......
......@@ -13,8 +13,8 @@
#include "base/optional.h"
#include "base/strings/string_piece.h"
// Functions for querying metadata from ELF binaries.
// All functions require that the file be fully memory mapped.
// Functions for querying metadata from ELF binaries. All functions are signal
// safe and require that the file be fully memory mapped.
#if __SIZEOF_POINTER__ == 4
using Phdr = Elf32_Phdr;
......@@ -25,24 +25,25 @@ using Phdr = Elf64_Phdr;
namespace base {
namespace debug {
// Hex-encodes the build ID from the ELF binary located at |elf_base|.
// Hex-encodes the build ID from the ELF binary located at |elf_mapped_base|.
// Returns the length of the build ID in bytes, or zero if the build ID couldn't
// be read.
// When |uppercase| is |true|, the output string is written using uppercase hex
// characters. Otherwise, the output is lowercased.
constexpr size_t kMaxBuildIdStringLength = kSHA1Length * 2;
using ElfBuildIdBuffer = char[kMaxBuildIdStringLength + 1];
size_t BASE_EXPORT ReadElfBuildId(const void* elf_base,
size_t BASE_EXPORT ReadElfBuildId(const void* elf_mapped_base,
bool uppercase,
ElfBuildIdBuffer build_id);
// Returns the library name from the ELF file mapped at |elf_base|.
// Returns the library name from the ELF file mapped at |elf_mapped_base|.
// Returns an empty result if the name could not be read.
Optional<StringPiece> BASE_EXPORT ReadElfLibraryName(const void* elf_base);
Optional<StringPiece> BASE_EXPORT
ReadElfLibraryName(const void* elf_mapped_base);
// Returns a span of ELF program headers for the ELF file mapped at
// |elf_base|, or an empty span if the header couldn't be read.
span<const Phdr> BASE_EXPORT GetElfProgramHeaders(const void* elf_base);
// |elf_mapped_base|, or an empty span if the header couldn't be read.
span<const Phdr> BASE_EXPORT GetElfProgramHeaders(const void* elf_mapped_base);
} // namespace debug
} // namespace base
......
......@@ -34,6 +34,9 @@ std::string ParamInfoToString(
case TestElfImageBuilder::RELOCATABLE_WITH_BIAS:
return "RelocatableWithBias";
case TestElfImageBuilder::NON_RELOCATABLE:
return "NonRelocatable";
}
}
} // namespace
......@@ -142,7 +145,8 @@ INSTANTIATE_TEST_SUITE_P(
MappingTypes,
ElfReaderTest,
::testing::Values(TestElfImageBuilder::RELOCATABLE,
TestElfImageBuilder::RELOCATABLE_WITH_BIAS),
TestElfImageBuilder::RELOCATABLE_WITH_BIAS,
TestElfImageBuilder::NON_RELOCATABLE),
&ParamInfoToString);
TEST(ElfReaderTestWithCurrentElfImage, ReadElfBuildId) {
......
......@@ -105,13 +105,18 @@ struct TestElfImageBuilder::ImageMeasures {
size_t total_size;
};
Addr TestElfImageBuilder::GetVirtualAddressForOffset(Off offset) const {
Addr TestElfImageBuilder::GetVirtualAddressForOffset(
Off offset,
const uint8_t* elf_start) const {
switch (mapping_type_) {
case RELOCATABLE:
return static_cast<Addr>(offset);
case RELOCATABLE_WITH_BIAS:
return static_cast<Addr>(offset + kLoadBias);
case NON_RELOCATABLE:
return reinterpret_cast<Addr>(elf_start + offset);
}
}
......@@ -191,10 +196,11 @@ TestElfImage TestElfImageBuilder::Build() {
// Add the program header table.
loc = bits::Align(loc, kPhdrAlign);
loc = AppendHdr(CreatePhdr(PT_PHDR, PF_R, kPhdrAlign,
GetVirtualAddressForOffset(loc - elf_start),
sizeof(Phdr) * measures.phdrs_required),
loc);
loc = AppendHdr(
CreatePhdr(PT_PHDR, PF_R, kPhdrAlign, loc - elf_start,
GetVirtualAddressForOffset(loc - elf_start, 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;
......@@ -202,24 +208,27 @@ TestElfImage TestElfImageBuilder::Build() {
// encompass all the preceding headers.
if (i == 0)
size += loc - elf_start;
loc = AppendHdr(
CreatePhdr(PT_LOAD, load_segment.flags, kLoadAlign,
GetVirtualAddressForOffset(measures.load_segment_start[i]),
size),
loc);
loc = AppendHdr(CreatePhdr(PT_LOAD, load_segment.flags, kLoadAlign,
measures.load_segment_start[i],
GetVirtualAddressForOffset(
measures.load_segment_start[i], elf_start),
size),
loc);
}
if (measures.note_size != 0) {
loc = AppendHdr(CreatePhdr(PT_NOTE, PF_R, kNoteAlign,
GetVirtualAddressForOffset(measures.note_start),
measures.note_size),
loc);
loc = AppendHdr(
CreatePhdr(PT_NOTE, PF_R, kNoteAlign, measures.note_start,
GetVirtualAddressForOffset(measures.note_start, elf_start),
measures.note_size),
loc);
}
if (soname_) {
loc =
AppendHdr(CreatePhdr(PT_DYNAMIC, PF_R | PF_W, kDynamicAlign,
GetVirtualAddressForOffset(measures.dynamic_start),
sizeof(Dyn) * 2),
loc);
loc = AppendHdr(
CreatePhdr(
PT_DYNAMIC, PF_R | PF_W, kDynamicAlign, measures.dynamic_start,
GetVirtualAddressForOffset(measures.dynamic_start, elf_start),
sizeof(Dyn) * 2),
loc);
}
// Add the notes.
......@@ -253,7 +262,8 @@ TestElfImage TestElfImageBuilder::Build() {
#if defined(OS_FUCHSIA) || defined(OS_ANDROID)
// 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);
strtab_dyn->d_un.d_ptr =
GetVirtualAddressForOffset(measures.strtab_start, elf_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.
......@@ -319,13 +329,14 @@ Phdr TestElfImageBuilder::CreatePhdr(Word type,
Word flags,
size_t align,
Off offset,
Addr vaddr,
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_vaddr = vaddr;
phdr.p_paddr = 0;
phdr.p_memsz = phdr.p_filesz;
phdr.p_align = align;
......
......@@ -57,8 +57,9 @@ class TestElfImageBuilder {
public:
// 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.
RELOCATABLE, // Virtual address == file offset.
RELOCATABLE_WITH_BIAS, // Virtual address == file offset + load bias.
NON_RELOCATABLE, // Virtual address == mapped address.
};
explicit TestElfImageBuilder(MappingType mapping_type);
......@@ -94,7 +95,7 @@ class TestElfImageBuilder {
// 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;
Addr GetVirtualAddressForOffset(Off offset, const uint8_t* elf_start) const;
// Measures sizes/start offset of segments in the image.
ImageMeasures MeasureSizesAndOffsets() const;
......@@ -105,7 +106,12 @@ class TestElfImageBuilder {
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);
Phdr CreatePhdr(Word type,
Word flags,
size_t align,
Off offset,
Addr vaddr,
size_t size);
// The load bias to use for RELOCATABLE_WITH_BIAS. 0xc000 is a commonly used
// load bias for Android system ELF images.
......
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