| // 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 "process.h" |
| |
| #include <fcntl.h> |
| #include <lib/fdio/io.h> |
| #include <lib/fit/function.h> |
| #include <link.h> |
| #include <zircon/dlfcn.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/object.h> |
| #include <cinttypes> |
| |
| #include "garnet/lib/debugger_utils/jobs.h" |
| #include "garnet/lib/debugger_utils/util.h" |
| #include "lib/fxl/logging.h" |
| #include "lib/fxl/strings/string_printf.h" |
| |
| #include "server.h" |
| |
| namespace inferior_control { |
| namespace { |
| |
| constexpr zx_time_t kill_timeout = ZX_MSEC(10 * 1000); |
| |
| std::unique_ptr<process::ProcessBuilder> CreateProcessBuilder( |
| zx_handle_t job, const debugger_utils::Argv& argv, |
| std::shared_ptr<component::Services>& services) { |
| FXL_DCHECK(argv.size() > 0); |
| zx::job builder_job; |
| zx_status_t status = zx_handle_duplicate(job, ZX_RIGHT_SAME_RIGHTS, |
| builder_job.reset_and_get_address()); |
| if (status != ZX_OK) |
| return nullptr; |
| |
| auto builder = std::make_unique<process::ProcessBuilder>( |
| std::move(builder_job), services); |
| |
| builder->AddArgs(argv); |
| builder->CloneAll(); |
| |
| // Also clone the loader since we call LoadVMO rather than use the resolver |
| // path of the ProcessBuilder. |
| zx::handle ldsvc; |
| dl_clone_loader_service(ldsvc.reset_and_get_address()); |
| builder->AddHandle(PA_LDSVC_LOADER, std::move(ldsvc)); |
| |
| return builder; |
| } |
| |
| zx_status_t LoadPath(const char* path, zx_handle_t* vmo) { |
| int fd = open(path, O_RDONLY); |
| if (fd < 0) |
| return ZX_ERR_IO; |
| zx_status_t status = fdio_get_vmo_clone(fd, vmo); |
| close(fd); |
| |
| if (status == ZX_OK) { |
| if (strlen(path) >= ZX_MAX_NAME_LEN) { |
| const char* p = strrchr(path, '/'); |
| if (p != NULL) { |
| path = p + 1; |
| } |
| } |
| |
| zx_object_set_property(*vmo, ZX_PROP_NAME, path, strlen(path)); |
| } |
| |
| return status; |
| } |
| |
| bool LoadBinary(process::ProcessBuilder* builder, |
| const std::string& binary_path) { |
| FXL_DCHECK(builder); |
| |
| zx::vmo vmo; |
| zx_status_t status = |
| LoadPath(binary_path.c_str(), vmo.reset_and_get_address()); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Could not load binary: " |
| << debugger_utils::ZxErrorString(status); |
| return false; |
| } |
| |
| builder->LoadVMO(std::move(vmo)); |
| return true; |
| } |
| |
| zx_koid_t GetProcessId(zx_handle_t process) { |
| zx_info_handle_basic_t info; |
| zx_status_t status = zx_object_get_info(process, ZX_INFO_HANDLE_BASIC, &info, |
| sizeof(info), nullptr, nullptr); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "zx_object_get_info_failed: " |
| << debugger_utils::ZxErrorString(status); |
| return ZX_KOID_INVALID; |
| } |
| |
| FXL_DCHECK(info.type == ZX_OBJ_TYPE_PROCESS); |
| FXL_DCHECK(info.koid != ZX_KOID_INVALID); |
| |
| return info.koid; |
| } |
| |
| } // namespace |
| |
| // static |
| const char* Process::StateName(Process::State state) { |
| #define CASE_TO_STR(x) \ |
| case Process::State::x: \ |
| return #x |
| switch (state) { |
| CASE_TO_STR(kNew); |
| CASE_TO_STR(kStarting); |
| CASE_TO_STR(kRunning); |
| CASE_TO_STR(kGone); |
| default: |
| break; |
| } |
| #undef CASE_TO_STR |
| return "(unknown)"; |
| } |
| |
| Process::Process(Server* server, Delegate* delegate, |
| std::shared_ptr<component::Services> services) |
| : server_(server), |
| delegate_(delegate), |
| services_(services), |
| memory_( |
| std::shared_ptr<debugger_utils::ByteBlock>(new ProcessMemory(this))), |
| breakpoints_(this) { |
| FXL_DCHECK(server_); |
| FXL_DCHECK(delegate_); |
| } |
| |
| Process::~Process() { |
| // If we're still attached then either kill the process if we |
| // started it or detach if we attached to it after it was running. |
| if (attached_running_) { |
| RawDetach(); |
| } else { |
| if (!Kill()) { |
| // Paranoia: Still need to detach before we can call Clear(). |
| RawDetach(); |
| } |
| } |
| Clear(); |
| } |
| |
| std::string Process::GetName() const { |
| return fxl::StringPrintf("%" PRId64, id()); |
| } |
| |
| void Process::AddStartupHandle(fuchsia::process::HandleInfo handle) { |
| extra_handles_.push_back(std::move(handle)); |
| } |
| |
| bool Process::Initialize() { |
| if (IsAttached()) { |
| FXL_LOG(ERROR) << "Cannot initialize, already attached to a process"; |
| return false; |
| } |
| |
| if (argv_.size() == 0 || argv_[0].size() == 0) { |
| FXL_LOG(ERROR) << "No program specified"; |
| return false; |
| } |
| |
| zx_handle_t job = server_->job_for_launch(); |
| if (job == ZX_HANDLE_INVALID) { |
| FXL_LOG(ERROR) << "No job in which to launch process"; |
| return false; |
| } |
| |
| FXL_DCHECK(!builder_); |
| FXL_DCHECK(!handle_); |
| FXL_DCHECK(!eport_key_); |
| |
| zx_status_t status; |
| |
| // The Process object survives run-after-run. Switch Gone back to New. |
| switch (state_) { |
| case State::kNew: |
| break; |
| case State::kGone: |
| set_state(State::kNew); |
| break; |
| default: |
| // Shouldn't get here if process is currently live. |
| FXL_DCHECK(false); |
| } |
| |
| FXL_LOG(INFO) << "Initializing process"; |
| |
| attached_running_ = false; |
| // There is no thread map yet. |
| thread_map_stale_ = false; |
| |
| FXL_LOG(INFO) << "argv: " << debugger_utils::ArgvToString(argv_); |
| |
| std::string error_message; |
| builder_ = CreateProcessBuilder(job, argv_, services_); |
| |
| if (!builder_) { |
| return false; |
| } |
| |
| builder_->AddHandles(std::move(extra_handles_)); |
| |
| if (!LoadBinary(builder_.get(), argv_[0])) { |
| goto fail; |
| } |
| |
| FXL_VLOG(1) << "Binary loaded"; |
| |
| status = builder_->Prepare(&error_message); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to start inferior process: " |
| << debugger_utils::ZxErrorString(status) << ": " |
| << error_message; |
| goto fail; |
| } |
| |
| if (!AllocDebugHandle(builder_.get())) { |
| goto fail; |
| } |
| |
| if (!BindExceptionPort()) { |
| goto fail; |
| } |
| |
| FXL_LOG(INFO) << fxl::StringPrintf("Process created: pid %" PRIu64, id_); |
| |
| base_address_ = builder_->data().base; |
| entry_address_ = builder_->data().entry; |
| |
| FXL_DCHECK(IsAttached()); |
| |
| FXL_LOG(INFO) << fxl::StringPrintf("Process %" PRIu64 |
| ": base load address 0x%" PRIxPTR |
| ", entry address 0x%" PRIxPTR, |
| id_, base_address_, entry_address_); |
| |
| return true; |
| |
| fail: |
| if (handle_ != ZX_HANDLE_INVALID) { |
| CloseDebugHandle(); |
| } |
| if (eport_key_) { |
| UnbindExceptionPort(); |
| } |
| id_ = ZX_KOID_INVALID; |
| builder_.reset(); |
| return false; |
| } |
| |
| // TODO(dje): Merge common parts with Initialize() after things settle down. |
| |
| bool Process::Attach(zx_koid_t pid) { |
| if (IsAttached()) { |
| FXL_LOG(ERROR) << "Cannot attach, already attached to a process"; |
| return false; |
| } |
| if (server_->job_for_search() == ZX_HANDLE_INVALID) { |
| FXL_LOG(ERROR) << "Cannot attach, no job for searching processes"; |
| return false; |
| } |
| |
| FXL_DCHECK(!builder_); |
| FXL_DCHECK(!handle_); |
| FXL_DCHECK(!eport_key_); |
| |
| // The Process object survives run-after-run. Switch Gone back to New. |
| switch (state_) { |
| case State::kNew: |
| break; |
| case State::kGone: |
| set_state(State::kNew); |
| break; |
| default: |
| // Shouldn't get here if process is currently live. |
| FXL_DCHECK(false); |
| } |
| |
| FXL_LOG(INFO) << "Attaching to process " << pid; |
| |
| if (!AllocDebugHandle(pid)) |
| return false; |
| |
| if (!BindExceptionPort()) { |
| CloseDebugHandle(); |
| return false; |
| } |
| |
| attached_running_ = true; |
| set_state(State::kRunning); |
| thread_map_stale_ = true; |
| |
| FXL_DCHECK(IsAttached()); |
| |
| FXL_LOG(INFO) << fxl::StringPrintf("Attach complete, pid %" PRIu64, id_); |
| |
| return true; |
| } |
| |
| bool Process::AllocDebugHandle(process::ProcessBuilder* builder) { |
| FXL_DCHECK(builder); |
| |
| zx_handle_t process = builder->data().process.get(); |
| FXL_DCHECK(process); |
| |
| // |process| is owned by |builder|. |
| // We need our own copy, and ProcessBuilder will give us one, but we need |
| // it before we call ProcessBuilder::Start in order to attach to the debugging |
| // exception port. |
| zx_handle_t debug_process; |
| auto status = |
| zx_handle_duplicate(process, ZX_RIGHT_SAME_RIGHTS, &debug_process); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "zx_handle_duplicate failed: " |
| << debugger_utils::ZxErrorString(status); |
| return false; |
| } |
| |
| id_ = GetProcessId(debug_process); |
| handle_ = debug_process; |
| return true; |
| } |
| |
| bool Process::AllocDebugHandle(zx_koid_t pid) { |
| FXL_DCHECK(pid != ZX_KOID_INVALID); |
| zx_handle_t job = server_->job_for_search(); |
| FXL_DCHECK(job != ZX_HANDLE_INVALID); |
| auto process = debugger_utils::FindProcess(job, pid); |
| if (!process.is_valid()) { |
| FXL_LOG(ERROR) << "Cannot find process " << pid; |
| return false; |
| } |
| // TODO(dje): It might be useful to use zx::foo throughout. Baby steps. |
| auto handle = process.release(); |
| |
| // TODO(armansito): Check that |handle| has ZX_RIGHT_DEBUG (this seems |
| // not to be set by anything at the moment but eventually we should check)? |
| |
| // Syscalls shouldn't return ZX_HANDLE_INVALID in the case of ZX_OK. |
| FXL_DCHECK(handle != ZX_HANDLE_INVALID); |
| |
| FXL_VLOG(1) << "Handle " << handle << " obtained for process " << pid; |
| |
| handle_ = handle; |
| id_ = pid; |
| return true; |
| } |
| |
| void Process::CloseDebugHandle() { |
| FXL_DCHECK(handle_ != ZX_HANDLE_INVALID); |
| zx_handle_close(handle_); |
| handle_ = ZX_HANDLE_INVALID; |
| } |
| |
| bool Process::BindExceptionPort() { |
| ExceptionPort::Key key = server_->exception_port().Bind( |
| handle_, fit::bind_member(this, &Process::OnExceptionOrSignal)); |
| if (!key) |
| return false; |
| eport_key_ = key; |
| return true; |
| } |
| |
| void Process::UnbindExceptionPort() { |
| FXL_DCHECK(eport_key_); |
| if (!server_->exception_port().Unbind(eport_key_)) |
| FXL_LOG(WARNING) << "Failed to unbind exception port; ignoring"; |
| eport_key_ = 0; |
| } |
| |
| void Process::RawDetach() { |
| // A copy of the handle is kept in ExceptionPort.BindData. |
| // We can't close the process handle until we unbind the exception port, |
| // so verify it's still open. |
| FXL_DCHECK(handle_); |
| FXL_DCHECK(IsAttached()); |
| |
| FXL_LOG(INFO) << "Detaching from process " << id(); |
| |
| UnbindExceptionPort(); |
| CloseDebugHandle(); |
| } |
| |
| bool Process::Detach() { |
| if (!IsAttached()) { |
| FXL_LOG(ERROR) << "Not attached"; |
| return false; |
| } |
| RawDetach(); |
| Clear(); |
| return true; |
| } |
| |
| bool Process::Start() { |
| FXL_DCHECK(builder_); |
| FXL_DCHECK(handle_); |
| |
| if (state_ != State::kNew) { |
| FXL_LOG(ERROR) << "Process already started"; |
| return false; |
| } |
| |
| zx_status_t status = builder_->Start(nullptr); |
| builder_.reset(); |
| |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to start inferior process: " |
| << debugger_utils::ZxErrorString(status); |
| return false; |
| } |
| |
| set_state(State::kStarting); |
| return true; |
| } |
| |
| bool Process::Kill() { |
| // If the caller wants to flag an error if the process isn't running s/he |
| // can, but for our purposes here we're more forgiving. |
| switch (state_) { |
| case Process::State::kNew: |
| case Process::State::kGone: |
| FXL_VLOG(1) << "Process is not live"; |
| return true; |
| default: |
| break; |
| } |
| |
| FXL_LOG(INFO) << "Killing process " << id(); |
| |
| // There's a few issues with sequencing here that we need to consider. |
| // - OnProcessExit, called when we receive an exception indicating |
| // the process has exited, will send back a stop reply which we don't want |
| // - we don't want to unbind the exception port before killing the process |
| // because we don't want to accidentally cause the process to resume before |
| // we kill it |
| // - we need the debug handle to kill the process |
| |
| FXL_DCHECK(handle_ != ZX_HANDLE_INVALID); |
| auto status = zx_task_kill(handle_); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to kill process: " |
| << debugger_utils::ZxErrorString(status); |
| return false; |
| } |
| |
| UnbindExceptionPort(); |
| |
| zx_signals_t signals; |
| // If something goes wrong we don't want to wait forever. |
| status = zx_object_wait_one(handle_, ZX_TASK_TERMINATED, |
| zx_deadline_after(kill_timeout), &signals); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Error waiting for process to die, ignoring: " |
| << debugger_utils::ZxErrorString(status); |
| } else { |
| FXL_DCHECK(signals & ZX_TASK_TERMINATED); |
| } |
| |
| CloseDebugHandle(); |
| |
| Clear(); |
| return true; |
| } |
| |
| void Process::set_state(State new_state) { |
| switch (new_state) { |
| case State::kNew: |
| FXL_DCHECK(state_ == State::kGone); |
| break; |
| case State::kStarting: |
| FXL_DCHECK(state_ == State::kNew); |
| break; |
| case State::kRunning: |
| FXL_DCHECK(state_ == State::kNew || state_ == State::kStarting); |
| break; |
| case State::kGone: |
| break; |
| default: |
| FXL_DCHECK(false); |
| } |
| state_ = new_state; |
| } |
| |
| void Process::Clear() { |
| // The process must already be fully detached from. |
| FXL_DCHECK(!IsAttached()); |
| |
| threads_.clear(); |
| thread_map_stale_ = false; |
| |
| id_ = ZX_KOID_INVALID; |
| base_address_ = 0; |
| entry_address_ = 0; |
| attached_running_ = false; |
| |
| dso_free_list(dsos_); |
| dsos_ = nullptr; |
| dsos_build_failed_ = false; |
| |
| builder_.reset(); |
| |
| // The process may just exited or whatever. Force the state to kGone. |
| set_state(State::kGone); |
| } |
| |
| bool Process::IsLive() const { |
| return state_ != State::kNew && state_ != State::kGone; |
| } |
| |
| bool Process::IsAttached() const { |
| if (eport_key_) { |
| FXL_DCHECK(handle_ != ZX_HANDLE_INVALID); |
| return true; |
| } else { |
| FXL_DCHECK(handle_ == ZX_HANDLE_INVALID); |
| return false; |
| } |
| } |
| |
| void Process::EnsureThreadMapFresh() { |
| if (thread_map_stale_) { |
| RefreshAllThreads(); |
| } |
| } |
| |
| Thread* Process::FindThreadById(zx_koid_t thread_id) { |
| FXL_DCHECK(handle_); |
| if (thread_id == ZX_HANDLE_INVALID) { |
| FXL_LOG(ERROR) << "Invalid thread ID given: " << thread_id; |
| return nullptr; |
| } |
| |
| EnsureThreadMapFresh(); |
| |
| const auto iter = threads_.find(thread_id); |
| if (iter != threads_.end()) { |
| Thread* thread = iter->second.get(); |
| if (thread->state() == Thread::State::kGone) { |
| FXL_VLOG(1) << "FindThreadById: Thread " << thread->GetDebugName() |
| << " is gone"; |
| return nullptr; |
| } |
| return thread; |
| } |
| |
| // Try to get a debug capable handle to the child of the current process with |
| // a kernel object ID that matches |thread_id|. |
| zx_handle_t thread_handle; |
| zx_status_t status = zx_object_get_child( |
| handle_, thread_id, ZX_RIGHT_SAME_RIGHTS, &thread_handle); |
| if (status != ZX_OK) { |
| // If the process just exited then the thread will be gone. So this is |
| // just a debug message, not a warning or error. |
| FXL_VLOG(1) << "Could not obtain a debug handle to thread " << thread_id |
| << ": " << debugger_utils::ZxErrorString(status); |
| return nullptr; |
| } |
| |
| Thread* thread = new Thread(this, thread_handle, thread_id); |
| threads_[thread_id] = std::unique_ptr<Thread>(thread); |
| return thread; |
| } |
| |
| Thread* Process::PickOneThread() { |
| EnsureThreadMapFresh(); |
| |
| if (threads_.empty()) |
| return nullptr; |
| |
| return threads_.begin()->second.get(); |
| } |
| |
| bool Process::RefreshAllThreads() { |
| FXL_DCHECK(handle_); |
| |
| // First get the thread count so that we can allocate an appropriately sized |
| // buffer. This is racy but unless the caller stops all threads that's just |
| // the way things are. |
| size_t num_threads; |
| zx_status_t status = zx_object_get_info(handle_, ZX_INFO_PROCESS_THREADS, |
| nullptr, 0, nullptr, &num_threads); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to get process thread info (#threads): " |
| << debugger_utils::ZxErrorString(status); |
| return false; |
| } |
| |
| auto buffer_size = num_threads * sizeof(zx_koid_t); |
| auto koids = std::make_unique<zx_koid_t[]>(num_threads); |
| size_t records_read; |
| status = zx_object_get_info(handle_, ZX_INFO_PROCESS_THREADS, koids.get(), |
| buffer_size, &records_read, nullptr); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to get process thread info: " |
| << debugger_utils::ZxErrorString(status); |
| return false; |
| } |
| |
| FXL_DCHECK(records_read == num_threads); |
| |
| ThreadMap new_threads; |
| for (size_t i = 0; i < num_threads; ++i) { |
| zx_koid_t thread_id = koids[i]; |
| zx_handle_t thread_handle = ZX_HANDLE_INVALID; |
| status = zx_object_get_child(handle_, thread_id, ZX_RIGHT_SAME_RIGHTS, |
| &thread_handle); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Could not obtain a debug handle to thread: " |
| << debugger_utils::ZxErrorString(status); |
| continue; |
| } |
| new_threads[thread_id] = |
| std::make_unique<Thread>(this, thread_handle, thread_id); |
| } |
| |
| // Just clear the existing list and repopulate it. |
| threads_ = std::move(new_threads); |
| thread_map_stale_ = false; |
| |
| return true; |
| } |
| |
| void Process::ForEachThread(const ThreadCallback& callback) { |
| EnsureThreadMapFresh(); |
| |
| for (const auto& iter : threads_) |
| callback(iter.second.get()); |
| } |
| |
| void Process::ForEachLiveThread(const ThreadCallback& callback) { |
| EnsureThreadMapFresh(); |
| |
| for (const auto& iter : threads_) { |
| Thread* thread = iter.second.get(); |
| if (thread->state() != Thread::State::kGone) |
| callback(thread); |
| } |
| } |
| |
| bool Process::ReadMemory(uintptr_t address, void* out_buffer, size_t length) { |
| return memory_->Read(address, out_buffer, length); |
| } |
| |
| bool Process::WriteMemory(uintptr_t address, const void* data, size_t length) { |
| return memory_->Write(address, data, length); |
| } |
| |
| void Process::TryBuildLoadedDsosList(Thread* thread, bool check_ldso_bkpt) { |
| FXL_DCHECK(dsos_ == nullptr); |
| |
| FXL_VLOG(2) << "Building dso list"; |
| |
| uintptr_t debug_addr; |
| zx_handle_t process_handle = thread->process()->handle(); |
| zx_status_t status = |
| zx_object_get_property(process_handle, ZX_PROP_PROCESS_DEBUG_ADDR, |
| &debug_addr, sizeof(debug_addr)); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) |
| << "zx_object_get_property failed, unable to fetch dso list: " |
| << debugger_utils::ZxErrorString(status); |
| return; |
| } |
| |
| struct r_debug debug; |
| if (!ReadMemory(debug_addr, &debug, sizeof(debug))) { |
| FXL_VLOG(2) << "unable to read _dl_debug_addr"; |
| // Don't set dsos_build_failed_ here, it may be too early to try. |
| return; |
| } |
| |
| // Since we could, theoretically, stop in the dynamic linker before we get |
| // that far check to see if it has been filled in. |
| // TODO(dje): Document our test in dynlink.c. |
| if (debug.r_version == 0) { |
| FXL_VLOG(2) << "debug.r_version is 0"; |
| // Don't set dsos_build_failed_ here, it may be too early to try. |
| return; |
| } |
| |
| if (check_ldso_bkpt) { |
| FXL_DCHECK(thread); |
| bool success = thread->registers()->RefreshGeneralRegisters(); |
| FXL_DCHECK(success); |
| zx_vaddr_t pc = thread->registers()->GetPC(); |
| // TODO(dje): -1: adjust_pc_after_break |
| if (pc - 1 != debug.r_brk) { |
| FXL_VLOG(2) << "not stopped at dynamic linker debug breakpoint"; |
| return; |
| } |
| } else { |
| FXL_DCHECK(!thread); |
| } |
| |
| auto lmap_vaddr = reinterpret_cast<zx_vaddr_t>(debug.r_map); |
| dsos_ = dso_fetch_list(memory_, lmap_vaddr, "app"); |
| // We should have fetched at least one since this is not called until the |
| // dl_debug_state breakpoint is hit. |
| if (dsos_ == nullptr) { |
| // Don't keep trying. |
| FXL_VLOG(2) << "dso_fetch_list failed"; |
| dsos_build_failed_ = true; |
| } else { |
| dso_vlog_list(dsos_); |
| // This may already be false, but set it any for documentation purposes. |
| dsos_build_failed_ = false; |
| } |
| } |
| |
| void Process::OnExceptionOrSignal(const zx_port_packet_t& packet, |
| const zx_exception_context_t& context) { |
| // Process exit is sent as a regular signal. |
| if (packet.type == ZX_PKT_TYPE_SIGNAL_ONE) { |
| FXL_VLOG(1) << "Received ZX_PKT_TYPE_SIGNAL_ONE, trigger 0x" << std::hex |
| << packet.signal.trigger; |
| if (packet.signal.trigger & ZX_TASK_TERMINATED) { |
| set_state(Process::State::kGone); |
| delegate_->OnProcessExit(this); |
| if (!Detach()) { |
| // This is not a fatal error, just log it. |
| FXL_LOG(ERROR) << "Unexpected failure to detach (already detached)"; |
| Clear(); |
| } |
| } |
| return; |
| } |
| |
| zx_excp_type_t type = static_cast<zx_excp_type_t>(packet.type); |
| zx_koid_t tid = packet.exception.tid; |
| Thread* thread = nullptr; |
| if (tid != ZX_KOID_INVALID) { |
| thread = FindThreadById(tid); |
| // TODO(dje): handle process exit |
| } |
| |
| // Finding the load address of the main executable requires a few steps. |
| // It's not loaded until the first time we hit the _dl_debug_state |
| // breakpoint. For now gdb sets that breakpoint. What we do is watch for |
| // s/w breakpoint exceptions. |
| if (type == ZX_EXCP_SW_BREAKPOINT) { |
| FXL_DCHECK(thread); |
| if (!DsosLoaded() && !dsos_build_failed_) |
| TryBuildLoadedDsosList(thread, true); |
| } |
| |
| // |type| could either map to an architectural exception or Zircon-defined |
| // synthetic exceptions. |
| if (ZX_EXCP_IS_ARCH(type)) { |
| FXL_DCHECK(thread); |
| thread->OnException(type, context); |
| delegate_->OnArchitecturalException(this, thread, type, context); |
| return; |
| } |
| |
| switch (type) { |
| case ZX_EXCP_THREAD_STARTING: |
| FXL_DCHECK(thread); |
| FXL_DCHECK(thread->state() == Thread::State::kNew); |
| FXL_VLOG(1) << "Received ZX_EXCP_THREAD_STARTING exception for thread " |
| << thread->GetName(); |
| thread->OnException(type, context); |
| delegate_->OnThreadStarting(this, thread, context); |
| break; |
| case ZX_EXCP_THREAD_EXITING: |
| // If the process also exited, then the thread may be gone. |
| if (thread) { |
| FXL_VLOG(1) << "Received ZX_EXCP_THREAD_EXITING exception for thread " |
| << tid << ", " << thread->GetName(); |
| thread->OnException(type, context); |
| delegate_->OnThreadExiting(this, thread, context); |
| } else { |
| FXL_VLOG(1) << "Received ZX_EXCP_THREAD_EXITING exception for thread " |
| << tid; |
| } |
| break; |
| case ZX_EXCP_POLICY_ERROR: |
| FXL_DCHECK(thread); |
| FXL_VLOG(1) << "Received ZX_EXCP_POLICY_ERROR exception for thread " |
| << thread->GetName(); |
| thread->OnException(type, context); |
| delegate_->OnSyntheticException(this, thread, type, context); |
| break; |
| default: |
| FXL_LOG(ERROR) << "Ignoring unrecognized synthetic exception for thread " |
| << tid << ": " << type; |
| break; |
| } |
| } |
| |
| int Process::ExitCode() { |
| FXL_DCHECK(state_ == State::kGone); |
| zx_info_process_t info; |
| auto status = zx_object_get_info(handle(), ZX_INFO_PROCESS, &info, |
| sizeof(info), NULL, NULL); |
| if (status == ZX_OK) { |
| FXL_LOG(INFO) << "Process exited with code " << info.return_code; |
| return info.return_code; |
| } else { |
| FXL_LOG(ERROR) << "Error getting process exit code: " |
| << debugger_utils::ZxErrorString(status); |
| return -1; |
| } |
| } |
| |
| const debugger_utils::dsoinfo_t* Process::GetExecDso() { |
| return dso_get_main_exec(dsos_); |
| } |
| |
| debugger_utils::dsoinfo_t* Process::LookupDso(zx_vaddr_t pc) const { |
| return dso_lookup(dsos_, pc); |
| } |
| |
| } // namespace inferior_control |