blob: 332be6c6db4ab525b9aa23fd427479cb7b3df2b6 [file] [log] [blame]
// Copyright 2016 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 "elf_reader.h"
#include <lib/syslog/cpp/macros.h>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "src/lib/fxl/strings/string_number_conversions.h"
#include "src/lib/fxl/strings/string_printf.h"
#include "util.h"
namespace debugger_utils {
const char* ElfErrorName(ElfError err) {
switch (err) {
case ElfError::OK:
return "OK";
case ElfError::IO:
return "IO";
case ElfError::BADELF:
return "BADELF";
case ElfError::NOMEM:
return "NOMEM";
default:
return "UNKNOWN";
}
}
ElfError ElfReader::Create(const std::string& file_name, std::shared_ptr<ByteBlock> byte_block,
uint32_t options, uint64_t base, std::unique_ptr<ElfReader>* out) {
FX_DCHECK(options == 0);
FX_VLOGS(1) << "Creating ELF reader for: " << file_name << ": base 0x" << std::hex << base;
ElfReader* er = new ElfReader(file_name, byte_block, base);
if (!ReadHeader(*byte_block, base, &er->header_)) {
delete er;
return ElfError::IO;
}
if (!VerifyHeader(&er->header_)) {
delete er;
return ElfError::BADELF;
}
*out = std::unique_ptr<ElfReader>(er);
return ElfError::OK;
}
ElfReader::ElfReader(const std::string& file_name, std::shared_ptr<ByteBlock> byte_block,
uint64_t base)
: file_name_(file_name), byte_block_(byte_block), base_(base) {}
ElfReader::~ElfReader() {
FreeSegmentHeaders();
FreeSectionHeaders();
}
// static
bool ElfReader::ReadHeader(const ByteBlock& m, uint64_t base, ElfHeader* hdr) {
return m.Read(base, hdr, sizeof(*hdr));
}
// static
bool ElfReader::VerifyHeader(const ElfHeader* hdr) {
if (memcmp(hdr->e_ident, ELFMAG, SELFMAG)) {
FX_VLOGS(2) << fxl::StringPrintf("Bad ELF magic: %02x %02x %02x %02x", hdr->e_ident[0],
hdr->e_ident[1], hdr->e_ident[2], hdr->e_ident[3]);
return false;
}
// TODO(dje): Support larger entries.
if (hdr->e_ehsize != sizeof(ElfHeader)) {
FX_VLOGS(2) << "Bad ELF header size";
return false;
}
// It's possible the entry size is zero if there are no entries.
if (hdr->e_phnum > 0 && hdr->e_phentsize != sizeof(ElfSegmentHeader)) {
FX_VLOGS(2) << "Bad ELF segment size";
return false;
}
if (hdr->e_shnum > 0 && hdr->e_shentsize != sizeof(ElfSectionHeader)) {
FX_VLOGS(2) << "Bad ELF section size";
return false;
}
// TODO(dje): Could add more checks.
return true;
}
ElfError ElfReader::ReadSegmentHeaders() {
if (segment_headers_)
return ElfError::OK;
size_t num_segments = GetNumSegments();
auto seg_hdrs = new ElfSegmentHeader[num_segments];
if (!byte_block_->Read(base_ + header_.e_phoff, seg_hdrs,
num_segments * sizeof(ElfSegmentHeader))) {
delete[] seg_hdrs;
return ElfError::IO;
}
segment_headers_ = seg_hdrs;
return ElfError::OK;
}
void ElfReader::FreeSegmentHeaders() {
if (segment_headers_)
delete[] segment_headers_;
segment_headers_ = nullptr;
}
const ElfSegmentHeader& ElfReader::GetSegmentHeader(size_t segment_number) {
FX_DCHECK(segment_headers_);
FX_DCHECK(segment_number < GetNumSegments());
return segment_headers_[segment_number];
}
ElfError ElfReader::ReadSectionHeaders() {
if (section_headers_)
return ElfError::OK;
size_t num_sections = GetNumSections();
auto scn_hdrs = new ElfSectionHeader[num_sections];
if (!byte_block_->Read(base_ + header_.e_shoff, scn_hdrs,
num_sections * sizeof(ElfSectionHeader))) {
delete[] scn_hdrs;
return ElfError::IO;
}
section_headers_ = scn_hdrs;
return ElfError::OK;
}
void ElfReader::FreeSectionHeaders() {
if (section_headers_)
delete[] section_headers_;
section_headers_ = nullptr;
}
const ElfSectionHeader& ElfReader::GetSectionHeader(size_t section_number) {
FX_DCHECK(section_headers_);
FX_DCHECK(section_number < GetNumSections());
return section_headers_[section_number];
}
const ElfSectionHeader* ElfReader::GetSectionHeaderByType(unsigned type) {
size_t num_sections = GetNumSections();
for (size_t i = 0; i < num_sections; ++i) {
const ElfSectionHeader& shdr = GetSectionHeader(i);
if (shdr.sh_type == type)
return &shdr;
}
return nullptr;
}
ElfError ElfReader::GetSectionContents(const ElfSectionHeader& sh,
std::unique_ptr<ElfSectionContents>* out_contents) {
void* buffer = malloc(sh.sh_size);
if (!buffer) {
FX_LOGS(ERROR) << "OOM getting space for section contents";
return ElfError::NOMEM;
}
if (!byte_block_->Read(base_ + sh.sh_offset, buffer, sh.sh_size)) {
FX_LOGS(ERROR) << "Error reading section contents";
return ElfError::IO;
}
// TODO(dje): Handle malloc failures for new.
auto contents = new ElfSectionContents(sh, buffer);
*out_contents = std::unique_ptr<ElfSectionContents>(contents);
return ElfError::OK;
}
ElfError ElfReader::ReadBuildId(char* buf, size_t buf_size) {
uint64_t vaddr = base_;
FX_DCHECK(buf_size >= kMaxBuildIdSize * 2 + 1);
ElfError rc = ReadSegmentHeaders();
if (rc != ElfError::OK)
return rc;
size_t num_segments = GetNumSegments();
for (size_t i = 0; i < num_segments; ++i) {
const auto& phdr = GetSegmentHeader(i);
if (phdr.p_type != PT_NOTE)
continue;
struct {
Elf32_Nhdr hdr;
char name[sizeof("GNU")];
} note;
uint64_t size = phdr.p_filesz;
uint64_t offset = phdr.p_offset;
while (size > sizeof(note)) {
if (!byte_block_->Read(vaddr + offset, &note, sizeof(note)))
return ElfError::IO;
size_t header_size = sizeof(Elf32_Nhdr) + ((note.hdr.n_namesz + 3) & -4);
size_t payload_size = (note.hdr.n_descsz + 3) & -4;
offset += header_size;
size -= header_size;
uint64_t payload_vaddr = vaddr + offset;
offset += payload_size;
size -= payload_size;
if (note.hdr.n_type != NT_GNU_BUILD_ID || note.hdr.n_namesz != sizeof("GNU") ||
memcmp(note.name, "GNU", sizeof("GNU")) != 0) {
continue;
}
if (note.hdr.n_descsz > kMaxBuildIdSize) {
// TODO(dje): Revisit.
snprintf(buf, buf_size, "build_id_too_large_%u", note.hdr.n_descsz);
} else {
uint8_t buildid[kMaxBuildIdSize];
if (!byte_block_->Read(payload_vaddr, buildid, note.hdr.n_descsz))
return ElfError::IO;
for (uint32_t i = 0; i < note.hdr.n_descsz; ++i) {
snprintf(&buf[i * 2], 3, "%02x", buildid[i]);
}
}
return ElfError::OK;
}
}
*buf = '\0';
return ElfError::OK;
}
ElfSectionContents::ElfSectionContents(const ElfSectionHeader& header, void* contents)
: header_(header), contents_(contents) {
FX_DCHECK(contents);
}
ElfSectionContents::~ElfSectionContents() { free(contents_); }
size_t ElfSectionContents::GetNumEntries() const {
switch (header_.sh_type) {
case SHT_SYMTAB:
case SHT_DYNSYM:
break;
default:
return 0;
}
FX_DCHECK(header_.sh_entsize != 0);
return header_.sh_size / header_.sh_entsize;
}
const ElfRawSymbol& ElfSectionContents::GetSymbolEntry(size_t entry_number) {
FX_DCHECK(header_.sh_type == SHT_SYMTAB || header_.sh_type == SHT_DYNSYM);
FX_DCHECK(entry_number < GetNumEntries());
auto buf = reinterpret_cast<const char*>(contents_);
auto sym = buf + entry_number * header_.sh_entsize;
return *reinterpret_cast<const ElfRawSymbol*>(sym);
}
} // namespace debugger_utils