blob: d147b758f28cc8555810ed3edea729b23cf4c358 [file] [log] [blame]
// Copyright 2022 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 "lib/zxdump/elf-search.h"
#include <optional>
#include "core.h"
namespace zxdump {
namespace {
template <class Phdr>
struct LayoutFor;
template <>
struct LayoutFor<elfldltl::Elf64<>::Phdr> {
using type = elfldltl::Elf64<>;
};
template <>
struct LayoutFor<elfldltl::Elf32<>::Phdr> {
using type = elfldltl::Elf32<>;
};
using DetectElfResult = fit::result<ElfSearchError, AnyElfPhdrsBuffer>;
using DetectIdentityResult = fit::result<ElfSearchError, ElfIdentity>;
DetectElfResult NoElf() { return fit::ok(AnyElfPhdrsBuffer{}); }
template <typename T, class View = std::span<const T>>
fit::result<ElfSearchError, Buffer<T, View>> ReadMemory( //
Process& process, zx_vaddr_t vaddr, size_t count) {
auto result = process.read_memory<T, View>(vaddr, count);
if (result.is_ok()) [[likely]] {
return fit::ok(*std::move(result));
}
return fit::error{ElfSearchError{result.error_value(), vaddr}};
}
} // namespace
bool IsLikelyElfMapping(const zx_info_maps_t& maps) {
const bool readonly =
(maps.u.mapping.mmu_flags & (ZX_VM_PERM_READ | ZX_VM_PERM_WRITE)) == ZX_VM_PERM_READ;
// It might be ELF if it...
return maps.type == ZX_INFO_MAPS_TYPE_MAPPING &&
maps.u.mapping.vmo_offset == 0 && // is the start of the file (VMO),
readonly && // maps it read-only,
maps.size > sizeof(Elf::Ehdr); // ... and has space for ELF,
}
// Try to detect an ELF image in this segment. If one is found and its phdrs
// are accessible, return a view into them from process.read_memory.
DetectElfResult DetectElf(Process& process, const zx_info_maps_t& segment) {
if (segment.type != ZX_INFO_MAPS_TYPE_MAPPING) {
return NoElf();
}
union Ehdr {
elfldltl::Elf64<>::Ehdr e64;
elfldltl::Elf32<>::Ehdr e32;
};
auto result = ReadMemory<Ehdr>(process, segment.base, 1);
if (result.is_error()) {
return result.take_error();
}
auto try_ehdr = [&process, &segment]<class Ehdr>( //
const Ehdr& ehdr, DetectElfResult& result) -> bool {
using Phdr = typename Ehdr::ElfLayout::Phdr;
if (ehdr.Valid() && ehdr.phentsize == sizeof(Phdr) && ehdr.phnum > 0 &&
ehdr.phnum != Ehdr::kPnXnum && (ehdr.phoff % alignof(Phdr)) == 0 &&
ehdr.phoff < segment.size && segment.size - ehdr.phoff >= ehdr.phnum * sizeof(Phdr)) {
auto read = ReadMemory<Phdr>(process, segment.base + ehdr.phoff, ehdr.phnum);
if (read.is_error() || !read->empty()) {
result = std::move(read);
return true;
}
}
return false;
};
auto try_formats = [try_ehdr](const auto&... ehdr) {
DetectElfResult result = NoElf();
(try_ehdr(ehdr, result) || ...);
return result;
};
return try_formats(result->front().e64, result->front().e32);
}
DetectIdentityResult DetectElfIdentity(Process& process, const zx_info_maps_t& segment,
const AnyElfPhdrs& phdrs) {
auto detect = [&process, &segment]<class Phdr>(
std::span<const Phdr> phdrs) -> fit::result<ElfSearchError, ElfIdentity> {
using Elf = LayoutFor<Phdr>::type;
using Dyn = Elf::Dyn;
using Nhdr = Elf::Nhdr;
// Find the first PT_LOAD segment.
std::optional<uint64_t> first_load;
for (const auto& phdr : phdrs) {
if (phdr.type == elfldltl::ElfPhdrType::kLoad) {
first_load = phdr.vaddr & -process.dump_page_size();
break;
}
}
if (!first_load) {
// This is not really a valid ELF image, probably.
return fit::ok(ElfIdentity{});
}
// Note the notes.
std::vector<SegmentDisposition::Note> notes;
std::optional<SegmentDisposition::Note> dynamic;
for (const auto& phdr : phdrs) {
if (phdr.type == elfldltl::ElfPhdrType::kNote && phdr.vaddr >= *first_load) {
// It's an allocated note segment that might be within this segment.
const size_t offset = phdr.vaddr - *first_load;
if (offset < segment.size && segment.size - offset >= phdr.filesz) {
// It actually fits inside the segment, so we can examine it.
notes.push_back({phdr.vaddr, phdr.filesz});
}
} else if (phdr.type == elfldltl::ElfPhdrType::kDynamic && phdr.vaddr >= *first_load) {
dynamic = {.vaddr = phdr.vaddr, .size = phdr.filesz};
}
}
// The phdrs view is no longer used now, so it's safe to use read_memory.
ElfIdentity id;
// Detect the build ID.
constexpr uint32_t kBuildIdNoteType{
static_cast<uint32_t>(elfldltl::ElfNoteType::kGnuBuildId),
};
constexpr std::string_view kBuildIdNoteName{
"GNU", sizeof("GNU"), // NUL terminator included!
};
for (auto note : notes) {
note.vaddr += segment.base - *first_load;
while (note.size >= sizeof(Nhdr)) {
Nhdr nhdr;
if (auto result = ReadMemory<Nhdr>(process, note.vaddr, 1); result.is_ok()) {
nhdr = result->front();
} else {
return result.take_error();
}
const size_t this_note_size =
sizeof(nhdr) + NoteAlign(nhdr.namesz) + NoteAlign(nhdr.descsz);
if (this_note_size > note.size) {
break;
}
if (nhdr.type == kBuildIdNoteType && nhdr.descsz > 0 &&
nhdr.namesz() == kBuildIdNoteName.size()) {
auto result = ReadMemory<char, std::string_view>(process, note.vaddr + sizeof(Nhdr),
kBuildIdNoteName.size());
if (result.is_error()) {
return result.take_error();
}
ZX_DEBUG_ASSERT(result->size() == kBuildIdNoteName.size());
if (**result == kBuildIdNoteName) {
// We have a winner!
note.size = this_note_size;
id.build_id = note;
break;
}
}
note.vaddr += this_note_size;
note.size -= this_note_size;
}
}
// Detect the SONAME. Ignore any memory errors since just returning
// a build ID and no SONAME is better than returning no information at all.
if (dynamic && dynamic->size > sizeof(Dyn)) {
dynamic->vaddr += segment.base - *first_load;
dynamic->size /= sizeof(Dyn);
auto read = process.read_memory<Dyn>(dynamic->vaddr, dynamic->size);
if (read.is_ok()) {
std::optional<uint64_t> soname, strtab;
for (const auto& dyn : **read) {
switch (dyn.tag) {
case elfldltl::ElfDynTag::kSoname:
soname = dyn.val;
break;
case elfldltl::ElfDynTag::kStrTab:
strtab = segment.base - *first_load + dyn.val;
break;
default:
break;
}
if (soname && strtab) {
uint64_t vaddr = *strtab + *soname;
auto string = process.read_memory_string(vaddr);
if (string.is_ok()) {
id.soname = {.vaddr = vaddr, .size = string->size()};
}
break;
}
}
}
}
return fit::ok(id);
};
return std::visit(detect, phdrs);
}
fit::result<ElfSearchError, MapsInfoSpan> ElfSearch( //
Process& process, MapsInfoSpan maps_info) {
for (auto first = maps_info.begin(); first != maps_info.end(); ++first) {
const auto& segment = *first;
if (!IsLikelyElfMapping(segment)) {
continue;
}
// If ELF headers of either ELFCLASS are detected, the phdrs will tell all.
auto on_phdrs = [&](const auto& phdrs_buffer) -> MapsInfoSpan {
if (phdrs_buffer->empty()) {
return {};
}
std::optional<uint64_t> vaddr_base, vaddr_limit;
const auto phdrs = *phdrs_buffer;
const uint64_t page_size = process.dump_page_size();
for (const auto& phdr : phdrs) {
if (phdr.type == elfldltl::ElfPhdrType::kLoad) {
if (!vaddr_base) {
vaddr_base = phdr.vaddr & -page_size;
}
if (phdr.vaddr < *vaddr_base) {
// It's invalid to have non-ascending segments.
// Consider this not to be a real ELF image at all.
vaddr_limit.reset();
break;
}
// Wrap-around arithmetic is fine in calculating the runtime vaddr.
const zx_vaddr_t abs_vaddr = phdr.vaddr - *vaddr_base + segment.base;
// Overflow in calculating the runtime range is invalid.
const uint64_t limit = //
(abs_vaddr + phdr.memsz + page_size - 1) & -page_size;
if (limit < segment.base) {
vaddr_limit.reset();
break;
}
if (vaddr_limit && limit < *vaddr_limit) {
// It's invalid to have overlapping segments.
vaddr_limit.reset();
break;
}
// The high-water mark for the vaddr_size now covers this segment.
// TODO(https://fxbug.dev/416040479): There may be gaps big enough
// that other mappings are allowed in between, and the caller will
// now skip over them.
vaddr_limit = limit;
}
}
if (!vaddr_limit) { // No useful phdrs found, so no image here.
return {};
}
// Make sure the described ELF image would actually fit into the range of
// segments. If it would go off the end, it's cannot really be a valid
// _loaded_ ELF image but might just be an ELF _file_ image--and we only
// notice the difference when it's too high in the address space. In
// that case, the _next_ segment might be a real loaded ELF image, so
// this segment won't be reported as an ELF image and following segments
// won't be skipped in the scan for one.
if (*vaddr_limit > maps_info.back().base + maps_info.back().size) {
return {};
}
// We have an image.
auto last = first;
do {
++last;
} while (last != maps_info.end() && last->base < *vaddr_limit);
return {first, last};
};
// It's approximately ELF. See if its segments can be gleaned.
auto detected = DetectElf(process, segment);
if (detected.is_error()) {
return detected.take_error();
}
// If valid-looking ELF segments were found, then the job is done.
// Otherwise iterate to check the next mapping.
if (MapsInfoSpan found = std::visit(on_phdrs, *detected); !found.empty()) {
return fit::ok(found);
}
}
return fit::ok(MapsInfoSpan{});
}
} // namespace zxdump