// 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 #include #include #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>* 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(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::Modules() { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (!initialized_modules_) { InitializeModules(); } return modules_; } const std::vector& 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): 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 "" and Crashpad needs to // replace it for symbol resolution to work properly). if (dsoname == "") { 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[] = " reader(new ElfImageReader()); std::unique_ptr 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 thread_koids = GetChildKoids(*process_, ZX_INFO_PROCESS_THREADS); std::vector 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