// Copyright 2018 The Crashpad Authors. All rights reserved. // // 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 <link.h> #include <zircon/syscalls.h> #include "base/fuchsia/fuchsia_logging.h" #include "base/fuchsia/scoped_zx_handle.h" #include "base/logging.h" namespace crashpad { 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(zx_handle_t process) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); 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_; } 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. std::string app_name("app:"); { char name[ZX_MAX_NAME_LEN]; zx_status_t status = zx_object_get_property(process_, ZX_PROP_NAME, name, sizeof(name)); if (status != ZX_OK) { LOG(ERROR) << "zx_object_get_property ZX_PROP_NAME"; return; } app_name += name; } // 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 = zx_object_get_property(process_, 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"; } Module module; if (dsoname.empty()) { module.name = app_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? process_memory_range->Initialize(process_memory_.get(), true); process_memory_ranges_.push_back(std::move(process_memory_range)); 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; // Retrieve the thread koids. This is racy; better if the process is suspended // itself, but threads could still be externally created. As there's no // maximum, this needs to be retried in a loop until the actual threads // retrieved is equal to the available threads. std::vector<zx_koid_t> threads(100); size_t actual_num_threads, available_num_threads; for (;;) { zx_status_t status = zx_object_get_info(process_, ZX_INFO_PROCESS_THREADS, &threads[0], sizeof(threads[0]) * threads.size(), &actual_num_threads, &available_num_threads); // If the buffer is too small (even zero), the result is still ZX_OK, not // ZX_ERR_BUFFER_TOO_SMALL. if (status != ZX_OK) { ZX_LOG(ERROR, status) << "zx_object_get_info ZX_INFO_PROCESS_THREADS"; break; } if (actual_num_threads == available_num_threads) { threads.resize(actual_num_threads); break; } // Resize to the expected number next time with a bit extra to attempt to // handle the race between here and the next request. threads.resize(available_num_threads + 10); } for (const zx_koid_t thread_koid : threads) { zx_handle_t raw_handle; zx_status_t status = zx_object_get_child( process_, thread_koid, ZX_RIGHT_SAME_RIGHTS, &raw_handle); if (status != ZX_OK) { ZX_LOG(ERROR, status) << "zx_object_get_child"; // TODO(scottmg): Decide if it's worthwhile adding a mostly-empty Thread // here, consisting only of the koid, but no other information. The only // time this is expected to happen is when there's a race between getting // the koid above, and requesting the handle here. continue; } base::ScopedZxHandle thread_handle(raw_handle); Thread thread; thread.id = thread_koid; char name[ZX_MAX_NAME_LEN] = {0}; status = zx_object_get_property( thread_handle.get(), 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 = zx_object_get_info(thread_handle.get(), 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; } threads_.push_back(thread); } } } // namespace crashpad