crashpad/snapshot/fuchsia/process_reader_fuchsia.cc
Thomas Gales dcdccf56f2 [fuchsia][arm64] Don't query for fp registers
Fuchsia devices are failing to read floating point context for ARM
because floating point registers are in the vector context for ARM.

This CL prevents warning logs from being emitted in this situation.

Fixed: fuchsia:129171

Tested: `fx shell crasher` @ 659207de7293cb30
Change-Id: I1d8d928da122aeb1bc4ac66b789cb638969d0fdf
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/4617960
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
2023-06-15 21:20:13 +00:00

388 lines
13 KiB
C++

// Copyright 2018 The Crashpad Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "snapshot/fuchsia/process_reader_fuchsia.h"
#include <lib/zx/thread.h>
#include <link.h>
#include <zircon/syscalls.h>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/logging.h"
#include "util/fuchsia/koid_utilities.h"
namespace crashpad {
namespace {
// Based on the thread's SP and the process's memory map, attempts to figure out
// the stack regions for the thread. Fuchsia's C ABI specifies
// https://fuchsia.googlesource.com/zircon/+/master/docs/safestack.md so the
// callstack and locals-that-have-their-address-taken are in two different
// stacks.
void GetStackRegions(
const zx_thread_state_general_regs_t& regs,
const MemoryMapFuchsia& memory_map,
std::vector<CheckedRange<zx_vaddr_t, size_t>>* stack_regions) {
stack_regions->clear();
uint64_t sp;
#if defined(ARCH_CPU_X86_64)
sp = regs.rsp;
#elif defined(ARCH_CPU_ARM64) || defined(ARCH_CPU_RISCV64)
sp = regs.sp;
#else
#error Port
#endif
// TODO(fxbug.dev/74897): make this work for stack overflows, e.g., by looking
// up using the initial stack pointer (sp) when the thread was created. Right
// now, it gets the stack by getting the mapping that contains the current sp.
// But in the case of stack overflows, the current sp is by definition outside
// of the stack so the mapping returned is not the stack and fails the type
// check, at least on arm64.
zx_info_maps_t range_with_sp;
if (!memory_map.FindMappingForAddress(sp, &range_with_sp)) {
LOG(ERROR) << "stack pointer not found in mapping";
return;
}
if (range_with_sp.type != ZX_INFO_MAPS_TYPE_MAPPING) {
LOG(ERROR) << "stack range has unexpected type " << range_with_sp.type
<< ", stack overflow? Aborting";
return;
}
if (range_with_sp.u.mapping.mmu_flags & ZX_VM_PERM_EXECUTE) {
LOG(ERROR)
<< "stack range is unexpectedly marked executable, continuing anyway";
}
// The stack covers [range_with_sp.base, range_with_sp.base +
// range_with_sp.size). The stack pointer (sp) can be anywhere in that range.
// It starts at the end of the range (range_with_sp.base + range_with_sp.size)
// and goes downwards until range_with_sp.base. Capture the part of the stack
// that is currently used: [sp, range_with_sp.base + range_with_sp.size).
// Capture up to kExtraCaptureSize additional bytes of stack, but only if
// present in the region that was already found.
constexpr uint64_t kExtraCaptureSize = 128;
const uint64_t start_address =
std::max(sp >= kExtraCaptureSize ? sp - kExtraCaptureSize : sp,
range_with_sp.base);
const size_t region_size =
range_with_sp.size - (start_address - range_with_sp.base);
// Because most Fuchsia processes use safestack, it is very unlikely that a
// stack this large would be valid. Even if it were, avoid creating
// unreasonably large dumps by artificially limiting the captured amount.
constexpr uint64_t kMaxStackCapture = 1048576u;
LOG_IF(ERROR, region_size > kMaxStackCapture)
<< "clamping unexpectedly large stack capture of " << region_size;
const size_t clamped_region_size = std::min(region_size, kMaxStackCapture);
stack_regions->push_back(
CheckedRange<zx_vaddr_t, size_t>(start_address, clamped_region_size));
// TODO(scottmg): https://crashpad.chromium.org/bug/196, once the retrievable
// registers include FS and similar for ARM, retrieve the region for the
// unsafe part of the stack too.
}
} // namespace
ProcessReaderFuchsia::Module::Module() = default;
ProcessReaderFuchsia::Module::~Module() = default;
ProcessReaderFuchsia::Thread::Thread() = default;
ProcessReaderFuchsia::Thread::~Thread() = default;
ProcessReaderFuchsia::ProcessReaderFuchsia() = default;
ProcessReaderFuchsia::~ProcessReaderFuchsia() = default;
bool ProcessReaderFuchsia::Initialize(const zx::process& process) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
process_ = zx::unowned_process(process);
process_memory_.reset(new ProcessMemoryFuchsia());
process_memory_->Initialize(*process_);
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
}
const std::vector<ProcessReaderFuchsia::Module>&
ProcessReaderFuchsia::Modules() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
if (!initialized_modules_) {
InitializeModules();
}
return modules_;
}
const std::vector<ProcessReaderFuchsia::Thread>&
ProcessReaderFuchsia::Threads() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
if (!initialized_threads_) {
InitializeThreads();
}
return threads_;
}
const MemoryMapFuchsia* ProcessReaderFuchsia::MemoryMap() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
if (!initialized_memory_map_) {
InitializeMemoryMap();
}
return memory_map_.get();
}
void ProcessReaderFuchsia::InitializeModules() {
DCHECK(!initialized_modules_);
DCHECK(modules_.empty());
initialized_modules_ = true;
// TODO(scottmg): <inspector/inspector.h> does some of this, but doesn't
// expose any of the data that's necessary to fill out a Module after it
// retrieves (some of) the data into internal structures. It may be worth
// trying to refactor/upstream some of this into Fuchsia.
// Starting from the ld.so's _dl_debug_addr, read the link_map structure and
// walk the list to fill out modules_.
uintptr_t debug_address;
zx_status_t status = process_->get_property(
ZX_PROP_PROCESS_DEBUG_ADDR, &debug_address, sizeof(debug_address));
if (status != ZX_OK || debug_address == 0) {
LOG(ERROR) << "zx_object_get_property ZX_PROP_PROCESS_DEBUG_ADDR";
return;
}
constexpr auto k_r_debug_map_offset = offsetof(r_debug, r_map);
uintptr_t map;
if (!process_memory_->Read(
debug_address + k_r_debug_map_offset, sizeof(map), &map)) {
LOG(ERROR) << "read link_map";
return;
}
int i = 0;
constexpr int kMaxDso = 1000; // Stop after an unreasonably large number.
while (map != 0) {
if (++i >= kMaxDso) {
LOG(ERROR) << "possibly circular dso list, terminating";
return;
}
constexpr auto k_link_map_addr_offset = offsetof(link_map, l_addr);
zx_vaddr_t base;
if (!process_memory_->Read(
map + k_link_map_addr_offset, sizeof(base), &base)) {
LOG(ERROR) << "Read base";
// Could theoretically continue here, but realistically if any part of
// link_map fails to read, things are looking bad, so just abort.
break;
}
constexpr auto k_link_map_next_offset = offsetof(link_map, l_next);
zx_vaddr_t next;
if (!process_memory_->Read(
map + k_link_map_next_offset, sizeof(next), &next)) {
LOG(ERROR) << "Read next";
break;
}
constexpr auto k_link_map_name_offset = offsetof(link_map, l_name);
zx_vaddr_t name_address;
if (!process_memory_->Read(map + k_link_map_name_offset,
sizeof(name_address),
&name_address)) {
LOG(ERROR) << "Read name address";
break;
}
std::string dsoname;
if (!process_memory_->ReadCString(name_address, &dsoname)) {
// In this case, it could be reasonable to continue on to the next module
// as this data isn't strictly in the link_map.
LOG(ERROR) << "ReadCString name";
}
// Debug symbols are indexed by module name x build-id on the crash server.
// The module name in the indexed Breakpad files is set at build time. So
// Crashpad needs to use the same module name at run time for symbol
// resolution to work properly.
//
// TODO: https://fxbug.dev/6057 - once Crashpad switches to elf-search, the
// following overwrites won't be necessary as only shared libraries will
// have a soname at runtime, just like at build time.
//
// * For shared libraries, the soname is used as module name at build time,
// which is the dsoname here except for libzircon.so (because it is
// injected by the kernel, its load name is "<vDSO>" and Crashpad needs to
// replace it for symbol resolution to work properly).
if (dsoname == "<vDSO>") {
dsoname = "libzircon.so";
}
// * For executables and loadable modules, the dummy value "<_>" is used as
// module name at build time. This is because executable and loadable
// modules don't have a name on Fuchsia. So we need to use the same dummy
// value at build and run times.
// Most executables have an empty dsoname. Loadable modules (and some rare
// executables) have a non-empty dsoname starting with a specific prefix,
// which Crashpas can use to identify loadable modules and clear the
// dsoname for them.
static constexpr const char kLoadableModuleLoadNamePrefix[] = "<VMO#";
// Pre-C++ 20 std::basic_string::starts_with
if (dsoname.compare(0,
strlen(kLoadableModuleLoadNamePrefix),
kLoadableModuleLoadNamePrefix) == 0) {
dsoname = "";
}
Module module;
if (dsoname.empty()) {
// This value must be kept in sync with what is used at build time to
// index symbols for executables and loadable modules.
module.name = "<_>";
module.type = ModuleSnapshot::kModuleTypeExecutable;
} else {
module.name = dsoname;
// TODO(scottmg): Handle kModuleTypeDynamicLoader.
module.type = ModuleSnapshot::kModuleTypeSharedLibrary;
}
std::unique_ptr<ElfImageReader> reader(new ElfImageReader());
std::unique_ptr<ProcessMemoryRange> process_memory_range(
new ProcessMemoryRange());
// TODO(scottmg): Could this be limited range?
if (process_memory_range->Initialize(process_memory_.get(), true)) {
process_memory_ranges_.push_back(std::move(process_memory_range));
if (reader->Initialize(*process_memory_ranges_.back(), base)) {
module.reader = reader.get();
module_readers_.push_back(std::move(reader));
modules_.push_back(module);
}
}
map = next;
}
}
void ProcessReaderFuchsia::InitializeThreads() {
DCHECK(!initialized_threads_);
DCHECK(threads_.empty());
initialized_threads_ = true;
std::vector<zx_koid_t> thread_koids =
GetChildKoids(*process_, ZX_INFO_PROCESS_THREADS);
std::vector<zx::thread> thread_handles =
GetHandlesForThreadKoids(*process_, thread_koids);
DCHECK_EQ(thread_koids.size(), thread_handles.size());
for (size_t i = 0; i < thread_handles.size(); ++i) {
Thread thread;
thread.id = thread_koids[i];
if (thread_handles[i].is_valid()) {
char name[ZX_MAX_NAME_LEN] = {0};
zx_status_t status =
thread_handles[i].get_property(ZX_PROP_NAME, &name, sizeof(name));
if (status != ZX_OK) {
ZX_LOG(WARNING, status) << "zx_object_get_property ZX_PROP_NAME";
} else {
thread.name.assign(name);
}
zx_info_thread_t thread_info;
status = thread_handles[i].get_info(
ZX_INFO_THREAD, &thread_info, sizeof(thread_info), nullptr, nullptr);
if (status != ZX_OK) {
ZX_LOG(WARNING, status) << "zx_object_get_info ZX_INFO_THREAD";
} else {
thread.state = thread_info.state;
}
zx_thread_state_general_regs_t general_regs;
status = thread_handles[i].read_state(
ZX_THREAD_STATE_GENERAL_REGS, &general_regs, sizeof(general_regs));
if (status != ZX_OK) {
ZX_LOG(WARNING, status)
<< "zx_thread_read_state(ZX_THREAD_STATE_GENERAL_REGS)";
} else {
thread.general_registers = general_regs;
const MemoryMapFuchsia* memory_map = MemoryMap();
if (memory_map) {
// Attempt to retrive stack regions if a memory map was retrieved. In
// particular, this may be null when operating on the current process
// where the memory map will not be able to be retrieved.
GetStackRegions(general_regs, *memory_map, &thread.stack_regions);
}
}
// Floating point registers are in the vector context for ARM.
#if !defined(ARCH_CPU_ARM64)
zx_thread_state_fp_regs_t fp_regs;
status = thread_handles[i].read_state(
ZX_THREAD_STATE_FP_REGS, &fp_regs, sizeof(fp_regs));
if (status != ZX_OK) {
ZX_LOG(WARNING, status)
<< "zx_thread_read_state(ZX_THREAD_STATE_FP_REGS)";
} else {
thread.fp_registers = fp_regs;
}
#endif
zx_thread_state_vector_regs_t vector_regs;
status = thread_handles[i].read_state(
ZX_THREAD_STATE_VECTOR_REGS, &vector_regs, sizeof(vector_regs));
if (status != ZX_OK) {
ZX_LOG(WARNING, status)
<< "zx_thread_read_state(ZX_THREAD_STATE_VECTOR_REGS)";
} else {
thread.vector_registers = vector_regs;
}
}
threads_.push_back(thread);
}
}
void ProcessReaderFuchsia::InitializeMemoryMap() {
DCHECK(!initialized_memory_map_);
initialized_memory_map_ = true;
memory_map_.reset(new MemoryMapFuchsia);
if (!memory_map_->Initialize(*process_)) {
memory_map_.reset();
}
}
} // namespace crashpad