| // Copyright 2018 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 "src/developer/debug/debug_agent/debugged_process.h" |
| |
| #include <inttypes.h> |
| #include <lib/fit/defer.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <zircon/syscalls/exception.h> |
| |
| #include <utility> |
| |
| #include "src/developer/debug/debug_agent/debug_agent.h" |
| #include "src/developer/debug/debug_agent/debugged_thread.h" |
| #include "src/developer/debug/debug_agent/hardware_breakpoint.h" |
| #include "src/developer/debug/debug_agent/object_provider.h" |
| #include "src/developer/debug/debug_agent/process_breakpoint.h" |
| #include "src/developer/debug/debug_agent/software_breakpoint.h" |
| #include "src/developer/debug/debug_agent/watchpoint.h" |
| #include "src/developer/debug/debug_agent/zircon_thread_exception.h" |
| #include "src/developer/debug/debug_agent/zircon_thread_handle.h" |
| #include "src/developer/debug/ipc/agent_protocol.h" |
| #include "src/developer/debug/ipc/message_reader.h" |
| #include "src/developer/debug/ipc/message_writer.h" |
| #include "src/developer/debug/shared/logging/logging.h" |
| #include "src/developer/debug/shared/platform_message_loop.h" |
| #include "src/developer/debug/shared/zx_status.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace debug_agent { |
| |
| namespace { |
| |
| std::vector<char> ReadSocketInput(debug_ipc::BufferedZxSocket* socket) { |
| FX_DCHECK(socket->valid()); |
| |
| constexpr size_t kReadSize = 1024; // Read in 1K chunks. |
| |
| std::vector<char> data; |
| auto& stream = socket->stream(); |
| while (true) { |
| char buf[kReadSize]; |
| |
| size_t read_amount = stream.Read(buf, kReadSize); |
| data.insert(data.end(), buf, buf + read_amount); |
| |
| if (read_amount < kReadSize) |
| break; |
| } |
| return data; |
| } |
| |
| // Meant to be used in debug logging. |
| std::string LogPreamble(const DebuggedProcess* process) { |
| return fxl::StringPrintf("[P: %lu (%s)] ", process->koid(), process->name().c_str()); |
| } |
| |
| void LogRegisterBreakpoint(debug_ipc::FileLineFunction location, DebuggedProcess* process, |
| Breakpoint* bp, uint64_t address) { |
| if (!debug_ipc::IsDebugModeActive()) |
| return; |
| |
| std::stringstream ss; |
| ss << LogPreamble(process) << "Setting breakpoint " << bp->settings().id << " (" |
| << bp->settings().name << ") on 0x" << std::hex << address; |
| |
| if (bp->settings().one_shot) |
| ss << " (one shot)"; |
| |
| DEBUG_LOG_WITH_LOCATION(Process, location) << ss.str(); |
| } |
| |
| } // namespace |
| |
| // DebuggedProcessCreateInfo ----------------------------------------------------------------------- |
| |
| DebuggedProcessCreateInfo::DebuggedProcessCreateInfo() = default; |
| DebuggedProcessCreateInfo::DebuggedProcessCreateInfo(zx_koid_t process_koid, |
| std::string process_name, |
| std::unique_ptr<ProcessHandle> handle) |
| : koid(process_koid), handle(std::move(handle)), name(std::move(process_name)) {} |
| |
| DebuggedProcessCreateInfo::DebuggedProcessCreateInfo( |
| zx_koid_t process_koid, std::string process_name, std::unique_ptr<ProcessHandle> handle, |
| std::shared_ptr<arch::ArchProvider> arch_provider, |
| std::shared_ptr<ObjectProvider> object_provider) |
| : koid(process_koid), |
| handle(std::move(handle)), |
| arch_provider(std::move(arch_provider)), |
| object_provider(std::move(object_provider)), |
| name(std::move(process_name)) {} |
| |
| // DebuggedProcess --------------------------------------------------------------------------------- |
| |
| DebuggedProcess::DebuggedProcess(DebugAgent* debug_agent, DebuggedProcessCreateInfo&& create_info) |
| : arch_provider_(std::move(create_info.arch_provider)), |
| object_provider_(std::move(create_info.object_provider)), |
| debug_agent_(debug_agent), |
| koid_(create_info.koid), |
| process_handle_(std::move(create_info.handle)), |
| name_(std::move(create_info.name)), |
| from_limbo_(create_info.from_limbo) { |
| RegisterDebugState(); |
| |
| // If create_info out or err are not valid, calling Init on the |
| // BufferedZxSocket will fail and leave it in an invalid state. This is |
| // expected if the io sockets could be obtained from the inferior. |
| stdout_.Init(std::move(create_info.out)); |
| stderr_.Init(std::move(create_info.err)); |
| } |
| |
| DebuggedProcess::~DebuggedProcess() { DetachFromProcess(); } |
| |
| void DebuggedProcess::DetachFromProcess() { |
| // 1. Remove installed software breakpoints. |
| // We need to tell each thread that this will happen. |
| for (auto& [address, breakpoint] : software_breakpoints_) { |
| for (auto& [thread_koid, thread] : threads_) { |
| thread->WillDeleteProcessBreakpoint(breakpoint.get()); |
| } |
| } |
| |
| // Clear the resources. |
| software_breakpoints_.clear(); |
| hardware_breakpoints_.clear(); |
| watchpoints_.clear(); |
| |
| // 2. Resume threads. |
| // Technically a 0'ed request would work, but being explicit is future-proof. |
| debug_ipc::ResumeRequest resume_request = {}; |
| resume_request.how = debug_ipc::ResumeRequest::How::kContinue; |
| resume_request.process_koid = koid_; |
| OnResume(resume_request); |
| |
| // 3. Unbind from the exception port. |
| process_watch_handle_.StopWatching(); |
| } |
| |
| zx_status_t DebuggedProcess::Init() { |
| debug_ipc::MessageLoopTarget* loop = debug_ipc::MessageLoopTarget::Current(); |
| FX_DCHECK(loop); // Loop must be created on this thread first. |
| |
| // Register for debug exceptions. |
| debug_ipc::MessageLoopTarget::WatchProcessConfig config; |
| config.process_name = object_provider_->NameForObject(handle()); |
| config.process_handle = handle().get(); |
| config.process_koid = koid_; |
| config.watcher = this; |
| zx_status_t status = loop->WatchProcessExceptions(std::move(config), &process_watch_handle_); |
| if (status != ZX_OK) |
| return status; |
| |
| // Binding stdout/stderr. |
| // We bind |this| into the callbacks. This is OK because the DebuggedProcess |
| // owns both sockets, meaning that it's assured to outlive the sockets. |
| |
| if (stdout_.valid()) { |
| stdout_.set_data_available_callback([this]() { OnStdout(false); }); |
| stdout_.set_error_callback([this]() { OnStdout(true); }); |
| status = stdout_.Start(); |
| if (status != ZX_OK) { |
| FX_LOGS(WARNING) << "Could not listen on stdout for process " << name_ << ": " |
| << debug_ipc::ZxStatusToString(status); |
| stdout_.Reset(); |
| } |
| } |
| |
| if (stderr_.valid()) { |
| stderr_.set_data_available_callback([this]() { OnStderr(false); }); |
| stderr_.set_error_callback([this]() { OnStderr(true); }); |
| status = stderr_.Start(); |
| if (status != ZX_OK) { |
| FX_LOGS(WARNING) << "Could not listen on stderr for process " << name_ << ": " |
| << debug_ipc::ZxStatusToString(status); |
| stderr_.Reset(); |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| void DebuggedProcess::OnPause(const debug_ipc::PauseRequest& request, |
| debug_ipc::PauseReply* reply) { |
| // This function should do a best effort to ensure the thread(s) are actually |
| // stopped before the reply is sent. |
| if (request.thread_koid) { |
| DebuggedThread* thread = GetThread(request.thread_koid); |
| if (thread) { |
| thread->Suspend(true); |
| thread->set_client_state(DebuggedThread::ClientState::kPaused); |
| |
| // The Suspend call could have failed though most failures should be rare (perhaps we raced |
| // with the thread being destroyed). Either way, send our current knowledge of the thread's |
| // state. |
| reply->threads.push_back( |
| thread->GetThreadRecord(debug_ipc::ThreadRecord::StackAmount::kMinimal)); |
| } |
| // Could be not found if there is a race between the thread exiting and |
| // the client sending the request. |
| } else { |
| // 0 thread ID means pause all threads. |
| std::vector<zx_koid_t> suspended_koids; |
| SuspendAll(true, &suspended_koids); |
| |
| // Change the state of those threads. |
| for (zx_koid_t thread_koid : suspended_koids) { |
| DebuggedThread* thread = GetThread(thread_koid); |
| FX_DCHECK(thread); |
| thread->set_client_state(DebuggedThread::ClientState::kPaused); |
| } |
| |
| reply->threads = GetThreadRecords(); |
| } |
| } |
| |
| void DebuggedProcess::OnResume(const debug_ipc::ResumeRequest& request) { |
| if (request.thread_koids.empty()) { |
| // Empty thread ID list means resume all threads. |
| for (auto& [thread_koid, thread] : threads_) { |
| thread->Resume(request); |
| thread->set_client_state(DebuggedThread::ClientState::kRunning); |
| } |
| } else { |
| for (uint64_t thread_koid : request.thread_koids) { |
| DebuggedThread* thread = GetThread(thread_koid); |
| if (thread) { |
| thread->Resume(request); |
| thread->set_client_state(DebuggedThread::ClientState::kRunning); |
| } |
| // Could be not found if there is a race between the thread exiting and |
| // the client sending the request. |
| } |
| } |
| } |
| |
| void DebuggedProcess::OnReadMemory(const debug_ipc::ReadMemoryRequest& request, |
| debug_ipc::ReadMemoryReply* reply) { |
| reply->blocks = process_handle_->ReadMemoryBlocks(request.address, request.size); |
| |
| // Remove any breakpoint instructions we've inserted. |
| // |
| // If there are a lot of ProcessBreakpoints this will get slow. If we find we have 100's of |
| // breakpoints an auxiliary data structure could be added to find overlapping breakpoints faster. |
| for (const auto& [addr, bp] : software_breakpoints_) { |
| // Generally there will be only one block. If we start reading many megabytes that cross |
| // mapped memory boundaries, a top-level range check would be a good idea to avoid unnecessary |
| // iteration. |
| for (auto& block : reply->blocks) { |
| bp->FixupMemoryBlock(&block); |
| } |
| } |
| } |
| |
| void DebuggedProcess::OnKill(const debug_ipc::KillRequest& request, debug_ipc::KillReply* reply) { |
| // Remove the watch handle before killing the process to avoid getting |
| // exceptions after we stopped listening to them. |
| process_watch_handle_ = {}; |
| |
| // Since we're being killed, we treat this process as not having any more |
| // threads. This makes cleanup code more straightforward, as there are no |
| // threads to resume/handle. |
| threads_.clear(); |
| reply->status = object_provider_->Kill(handle()); |
| } |
| |
| DebuggedThread* DebuggedProcess::GetThread(zx_koid_t thread_koid) const { |
| auto found_thread = threads_.find(thread_koid); |
| if (found_thread == threads_.end()) |
| return nullptr; |
| return found_thread->second.get(); |
| } |
| |
| std::vector<DebuggedThread*> DebuggedProcess::GetThreads() const { |
| std::vector<DebuggedThread*> threads; |
| threads.reserve(threads_.size()); |
| for (auto& kv : threads_) |
| threads.emplace_back(kv.second.get()); |
| return threads; |
| } |
| |
| void DebuggedProcess::PopulateCurrentThreads() { |
| for (zx_koid_t thread_koid : |
| object_provider_->GetChildKoids(handle().get(), ZX_INFO_PROCESS_THREADS)) { |
| // We should never populate the same thread twice. |
| if (threads_.find(thread_koid) != threads_.end()) |
| continue; |
| |
| zx_handle_t handle; |
| if (object_provider_->GetChild(this->handle().get(), thread_koid, ZX_RIGHT_SAME_RIGHTS, |
| &handle) == ZX_OK) { |
| DebuggedThread::CreateInfo create_info = {}; |
| create_info.process = this; |
| create_info.koid = thread_koid; |
| create_info.handle = std::make_unique<ZirconThreadHandle>(arch_provider_, koid_, thread_koid, |
| zx::thread(handle)); |
| create_info.creation_option = ThreadCreationOption::kRunningKeepRunning; |
| create_info.arch_provider = arch_provider_; |
| create_info.object_provider = object_provider_; |
| |
| auto new_thread = std::make_unique<DebuggedThread>(debug_agent_, std::move(create_info)); |
| threads_.emplace(thread_koid, std::move(new_thread)); |
| } |
| } |
| } |
| |
| std::vector<debug_ipc::ThreadRecord> DebuggedProcess::GetThreadRecords() const { |
| std::vector<debug_ipc::ThreadRecord> result; |
| for (const auto& pair : threads_) |
| result.push_back(pair.second->GetThreadRecord(debug_ipc::ThreadRecord::StackAmount::kMinimal)); |
| return result; |
| } |
| |
| bool DebuggedProcess::RegisterDebugState() { |
| // HOW REGISTRATION WITH THE LOADER WORKS. |
| // |
| // Upon process initialization and before executing the normal program code, ld.so sets the |
| // ZX_PROP_PROCESS_DEBUG_ADDR property on its own process to the address of a known struct defined |
| // in <link.h> containing the state of the loader. Debuggers can come along later, get the address |
| // from this property, and inspect the state of the dynamic loader for this process (get the |
| // loaded libraries, set breakpoints for loads, etc.). |
| // |
| // When launching a process in a debugger, the debugger needs to know when this property has been |
| // set or there will be a race to know when it's valid. To resolve this, the debuggers sets a |
| // known magic value to the property before startup. The loader checks for this value when setting |
| // the property, and if it had the magic value, issues a hardcoded software breakpoint. The |
| // debugger catches this breakpoint exception, reads the now-valid address from the property, and |
| // continues initialization. |
| // |
| // It's also possible that the property has been properly set up prior to starting the process. |
| // In Posix this can happen with a fork() where the entire process is duplicated, including the |
| // loader state and all dynamically loaded libraries. In Zircon this can happen if the creator |
| // of the process maps a valid loader state when it creates the process (possibly it's trying |
| // to emulate fork, or it could be injecting libraries itself for some reason). So we also need |
| // to handle the rare case that the propery is set before startup. |
| if (dl_debug_addr_) |
| return true; // Previously set. |
| |
| uintptr_t debug_addr = 0; |
| if (handle().get_property(ZX_PROP_PROCESS_DEBUG_ADDR, &debug_addr, sizeof(debug_addr)) != ZX_OK || |
| debug_addr == 0) { |
| // Register for sets on the debug addr by setting the magic value. |
| const intptr_t kMagicValue = ZX_PROCESS_DEBUG_ADDR_BREAK_ON_SET; |
| handle().set_property(ZX_PROP_PROCESS_DEBUG_ADDR, &kMagicValue, sizeof(kMagicValue)); |
| return false; |
| } |
| if (debug_addr == ZX_PROCESS_DEBUG_ADDR_BREAK_ON_SET) |
| return false; // Still not set. |
| |
| dl_debug_addr_ = debug_addr; |
| |
| // TODO(brettw) register breakpoint for dynamic loads. This current code |
| // only notifies for the initial set of binaries loaded by the process. |
| return true; |
| } |
| |
| void DebuggedProcess::SuspendAndSendModulesIfKnown() { |
| if (dl_debug_addr_) { |
| // This process' modules can be known. Send them. |
| // |
| // Suspend all threads while the module list is being sent. The client will resume the threads |
| // once it's loaded symbols and processed breakpoints (this may take a while and we'd like to |
| // get any breakpoints as early as possible). |
| std::vector<uint64_t> paused_thread_koids; |
| SuspendAll(false, &paused_thread_koids); |
| SendModuleNotification(std::move(paused_thread_koids)); |
| } |
| } |
| |
| void DebuggedProcess::SendModuleNotification(std::vector<uint64_t> paused_thread_koids) { |
| // Notify the client of any libraries. |
| debug_ipc::NotifyModules notify; |
| notify.process_koid = koid_; |
| notify.modules = process_handle_->GetModules(dl_debug_addr_); |
| notify.stopped_thread_koids = std::move(paused_thread_koids); |
| |
| DEBUG_LOG(Process) << LogPreamble(this) << "Sending modules."; |
| |
| debug_ipc::MessageWriter writer; |
| debug_ipc::WriteNotifyModules(notify, &writer); |
| debug_agent_->stream()->Write(writer.MessageComplete()); |
| } |
| |
| SoftwareBreakpoint* DebuggedProcess::FindSoftwareBreakpoint(uint64_t address) const { |
| auto it = software_breakpoints_.find(address); |
| if (it == software_breakpoints_.end()) |
| return nullptr; |
| return it->second.get(); |
| } |
| |
| HardwareBreakpoint* DebuggedProcess::FindHardwareBreakpoint(uint64_t address) const { |
| auto it = hardware_breakpoints_.find(address); |
| if (it == hardware_breakpoints_.end()) |
| return nullptr; |
| return it->second.get(); |
| } |
| |
| Watchpoint* DebuggedProcess::FindWatchpoint(const debug_ipc::AddressRange& range) const { |
| auto it = watchpoints_.lower_bound(range); |
| if (it == watchpoints_.end()) |
| return nullptr; |
| |
| for (; it != watchpoints_.end(); it++) { |
| if (it->first.Contains(range)) |
| return it->second.get(); |
| } |
| |
| return nullptr; |
| } |
| |
| zx_status_t DebuggedProcess::RegisterBreakpoint(Breakpoint* bp, uint64_t address) { |
| LogRegisterBreakpoint(FROM_HERE, this, bp, address); |
| |
| switch (bp->settings().type) { |
| case debug_ipc::BreakpointType::kSoftware: |
| return RegisterSoftwareBreakpoint(bp, address); |
| case debug_ipc::BreakpointType::kHardware: |
| return RegisterHardwareBreakpoint(bp, address); |
| case debug_ipc::BreakpointType::kReadWrite: |
| case debug_ipc::BreakpointType::kWrite: |
| FX_NOTREACHED() << "Watchpoints are registered through RegisterWatchpoint."; |
| // TODO(donosoc): Reactivate once the transition is complete. |
| return ZX_ERR_INVALID_ARGS; |
| case debug_ipc::BreakpointType::kLast: |
| FX_NOTREACHED(); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| FX_NOTREACHED(); |
| } |
| |
| void DebuggedProcess::UnregisterBreakpoint(Breakpoint* bp, uint64_t address) { |
| DEBUG_LOG(Process) << LogPreamble(this) << "Unregistering breakpoint " << bp->settings().id |
| << " (" << bp->settings().name << ")."; |
| |
| switch (bp->settings().type) { |
| case debug_ipc::BreakpointType::kSoftware: |
| return UnregisterSoftwareBreakpoint(bp, address); |
| case debug_ipc::BreakpointType::kHardware: |
| return UnregisterHardwareBreakpoint(bp, address); |
| case debug_ipc::BreakpointType::kReadWrite: |
| case debug_ipc::BreakpointType::kWrite: |
| FX_NOTREACHED() << "Watchpoints are unregistered through UnregisterWatchpoint."; |
| return; |
| case debug_ipc::BreakpointType::kLast: |
| FX_NOTREACHED(); |
| return; |
| } |
| |
| FX_NOTREACHED(); |
| } |
| |
| zx_status_t DebuggedProcess::RegisterWatchpoint(Breakpoint* bp, |
| const debug_ipc::AddressRange& range) { |
| FX_DCHECK(debug_ipc::IsWatchpointType(bp->settings().type)) |
| << "Breakpoint type must be kWatchpoint, got: " |
| << debug_ipc::BreakpointTypeToString(bp->settings().type); |
| |
| // NOTE: Even though the watchpoint system can handle un-aligned ranges, there is no way for |
| // an exception to determine which byte access actually triggered the exception. This means |
| // that watchpoint installed and nominal ranges should be the same. |
| // |
| // We make that check here and fail early if the range is not correctly aligned. |
| auto aligned_range = arch::AlignRange(range); |
| if (!aligned_range.has_value() || aligned_range.value() != range) |
| return ZX_ERR_INVALID_ARGS; |
| |
| auto it = watchpoints_.find(range); |
| if (it == watchpoints_.end()) { |
| auto watchpoint = |
| std::make_unique<Watchpoint>(bp->settings().type, bp, this, arch_provider_, range); |
| if (zx_status_t status = watchpoint->Init(); status != ZX_OK) |
| return status; |
| |
| watchpoints_[range] = std::move(watchpoint); |
| return ZX_OK; |
| } else { |
| return it->second->RegisterBreakpoint(bp); |
| } |
| } |
| |
| void DebuggedProcess::UnregisterWatchpoint(Breakpoint* bp, const debug_ipc::AddressRange& range) { |
| FX_DCHECK(debug_ipc::IsWatchpointType(bp->settings().type)) |
| << "Breakpoint type must be kWatchpoint, got: " |
| << debug_ipc::BreakpointTypeToString(bp->settings().type); |
| |
| auto it = watchpoints_.find(range); |
| if (it == watchpoints_.end()) |
| return; |
| |
| Watchpoint* watchpoint = it->second.get(); |
| bool still_used = watchpoint->UnregisterBreakpoint(bp); |
| if (!still_used) { |
| for (auto& [thread_koid, thread] : threads_) { |
| thread->WillDeleteProcessBreakpoint(watchpoint); |
| } |
| } |
| |
| watchpoints_.erase(it); |
| } |
| |
| void DebuggedProcess::EnqueueStepOver(ProcessBreakpoint* process_breakpoint, |
| DebuggedThread* thread) { |
| PruneStepOverQueue(); |
| |
| StepOverTicket ticket = {}; |
| ticket.process_breakpoint = process_breakpoint->GetWeakPtr(); |
| ticket.thread = thread->GetWeakPtr(); |
| step_over_queue_.push_back(std::move(ticket)); |
| |
| DEBUG_LOG(Process) << LogPreamble(this) << "[PB: 0x" << std::hex << process_breakpoint->address() |
| << "] Enqueing thread " << std::dec << thread->koid() |
| << " for step over. Queue size: " << step_over_queue_.size(); |
| |
| // If the queue already had an element, we wait until that element is done. |
| if (step_over_queue_.size() > 1u) |
| return; |
| |
| // This is the first ticket in the queue. We start executing it immediatelly. |
| process_breakpoint->ExecuteStepOver(thread); |
| } |
| |
| void DebuggedProcess::OnBreakpointFinishedSteppingOver() { |
| { |
| // We always tell the current breakpoint to finish the stepping over after starting the new one. |
| // This will free the other suspended threads, letting the new stepping over thread continue. |
| // |
| // We need to do this *after* starting the following breakpoint, because otherwise we introduce |
| // a window where threads are unsuspeded between breakpoints. |
| auto prev_ticket = step_over_queue_.front(); |
| auto post_execute_breakpoint = fit::defer([prev_ticket = std::move(prev_ticket)]() { |
| if (!prev_ticket.is_valid()) |
| return; |
| |
| prev_ticket.process_breakpoint->StepOverCleanup(prev_ticket.thread.get()); |
| }); |
| |
| // Pop the previous ticket (the post-complete action is already set with the deferred action). |
| step_over_queue_.pop_front(); |
| |
| // If there are still elements in the queue, we execute the next one. |
| // Since the queue is pruned, |
| PruneStepOverQueue(); |
| if (!step_over_queue_.empty()) { |
| auto& ticket = step_over_queue_.front(); |
| ticket.process_breakpoint->ExecuteStepOver(ticket.thread.get()); |
| return; |
| } |
| } |
| } |
| |
| void DebuggedProcess::OnProcessTerminated(zx_koid_t process_koid) { |
| DEBUG_LOG(Process) << LogPreamble(this) << "Terminating."; |
| debug_ipc::NotifyProcessExiting notify; |
| notify.process_koid = process_koid; |
| |
| zx_info_process info = {}; |
| process_handle_->GetInfo(&info); |
| notify.return_code = info.return_code; |
| |
| debug_ipc::MessageWriter writer; |
| debug_ipc::WriteNotifyProcessExiting(notify, &writer); |
| debug_agent_->stream()->Write(writer.MessageComplete()); |
| |
| debug_agent_->RemoveDebuggedProcess(process_koid); |
| // "THIS" IS NOW DELETED. |
| } |
| |
| void DebuggedProcess::OnThreadStarting(zx::exception exception, |
| zx_exception_info_t exception_info) { |
| FX_DCHECK(exception_info.pid == koid()); |
| FX_DCHECK(threads_.find(exception_info.tid) == threads_.end()); |
| |
| DebuggedThread::CreateInfo create_info = {}; |
| create_info.process = this; |
| create_info.koid = exception_info.tid; |
| create_info.handle = std::make_unique<ZirconThreadHandle>( |
| arch_provider_, koid_, exception_info.tid, |
| object_provider_->GetThreadFromException(exception.get())); |
| create_info.exception = std::make_unique<ZirconThreadException>(std::move(exception)); |
| create_info.creation_option = ThreadCreationOption::kSuspendedKeepSuspended; |
| create_info.arch_provider = arch_provider_; |
| create_info.object_provider = object_provider_; |
| |
| auto new_thread = std::make_unique<DebuggedThread>(debug_agent_, std::move(create_info)); |
| auto added = threads_.emplace(exception_info.tid, std::move(new_thread)); |
| |
| // Notify the client. |
| added.first->second->SendThreadNotification(); |
| } |
| |
| void DebuggedProcess::OnThreadExiting(zx::exception exception, zx_exception_info_t exception_info) { |
| FX_DCHECK(exception_info.pid == koid()); |
| |
| // Clean up our DebuggedThread object. |
| auto found_thread = threads_.find(exception_info.tid); |
| if (found_thread == threads_.end()) { |
| FX_NOTREACHED(); |
| return; |
| } |
| |
| // The thread will currently be in a "Dying" state. For it to complete its |
| // lifecycle it must be resumed. |
| exception.reset(); |
| |
| threads_.erase(exception_info.tid); |
| |
| // Notify the client. Can't call GetThreadRecord since the thread doesn't exist any more. |
| debug_ipc::NotifyThread notify; |
| notify.record.process_koid = exception_info.pid; |
| notify.record.thread_koid = exception_info.tid; |
| notify.record.state = debug_ipc::ThreadRecord::State::kDead; |
| |
| debug_ipc::MessageWriter writer; |
| debug_ipc::WriteNotifyThread(debug_ipc::MsgHeader::Type::kNotifyThreadExiting, notify, &writer); |
| debug_agent_->stream()->Write(writer.MessageComplete()); |
| } |
| |
| void DebuggedProcess::OnException(zx::exception exception_token, |
| zx_exception_info_t exception_info) { |
| FX_DCHECK(exception_info.pid == koid()); |
| |
| DebuggedThread* thread = GetThread(exception_info.tid); |
| if (!thread) { |
| FX_LOGS(ERROR) << "Exception on thread " << exception_info.tid << " which we don't know about."; |
| return; |
| } |
| |
| thread->OnException(std::make_unique<ZirconThreadException>(std::move(exception_token)), |
| exception_info); |
| } |
| |
| void DebuggedProcess::OnAddressSpace(const debug_ipc::AddressSpaceRequest& request, |
| debug_ipc::AddressSpaceReply* reply) { |
| reply->map = process_handle_->GetAddressSpace(request.address); |
| } |
| |
| void DebuggedProcess::OnModules(debug_ipc::ModulesReply* reply) { |
| // Modules can only be read after the debug state is set. |
| if (dl_debug_addr_) |
| reply->modules = process_handle_->GetModules(dl_debug_addr_); |
| } |
| |
| void DebuggedProcess::OnWriteMemory(const debug_ipc::WriteMemoryRequest& request, |
| debug_ipc::WriteMemoryReply* reply) { |
| size_t actual = 0; |
| reply->status = |
| process_handle_->WriteMemory(request.address, &request.data[0], request.data.size(), &actual); |
| if (reply->status == ZX_OK && actual != request.data.size()) |
| reply->status = ZX_ERR_IO; // Convert partial writes to errors. |
| } |
| |
| void DebuggedProcess::OnLoadInfoHandleTable(const debug_ipc::LoadInfoHandleTableRequest& request, |
| debug_ipc::LoadInfoHandleTableReply* reply) { |
| size_t actual = 0; |
| size_t avail = 0; |
| reply->status = handle().get_info(ZX_INFO_HANDLE_TABLE, nullptr, 0, &actual, &avail); |
| if (reply->status != ZX_OK) { |
| return; |
| } |
| std::vector<zx_info_handle_extended_t> handles(avail); |
| reply->status = handle().get_info(ZX_INFO_HANDLE_TABLE, handles.data(), |
| avail * sizeof(zx_info_handle_extended_t), &actual, &avail); |
| reply->handles.resize(actual); |
| for (size_t i = 0; i < actual; ++i) { |
| reply->handles[i].type = handles[i].type; |
| reply->handles[i].handle_value = handles[i].handle_value; |
| reply->handles[i].rights = handles[i].rights; |
| reply->handles[i].props = handles[i].props; |
| reply->handles[i].koid = handles[i].koid; |
| reply->handles[i].related_koid = handles[i].related_koid; |
| reply->handles[i].peer_owner_koid = handles[i].peer_owner_koid; |
| } |
| } |
| |
| void DebuggedProcess::SuspendAll(bool synchronous, std::vector<zx_koid_t>* suspended_koids) { |
| // Issue the suspension order for all the threads. |
| for (auto& [thread_koid, thread] : threads_) { |
| bool was_suspended = thread->Suspend(synchronous); |
| if (was_suspended && suspended_koids) |
| suspended_koids->push_back(thread_koid); |
| } |
| |
| if (!synchronous) |
| return; |
| |
| // If we want to block, we need to wait on the notification for each thread. |
| zx::time deadline = DebuggedThread::DefaultSuspendDeadline(); |
| for (auto& [thread_koid, thread] : threads_) { |
| thread->WaitForSuspension(deadline); |
| } |
| } |
| |
| void DebuggedProcess::OnStdout(bool close) { |
| FX_DCHECK(stdout_.valid()); |
| if (close) { |
| DEBUG_LOG(Process) << LogPreamble(this) << "stdout closed."; |
| stdout_.Reset(); |
| return; |
| } |
| |
| auto data = ReadSocketInput(&stdout_); |
| FX_DCHECK(!data.empty()); |
| DEBUG_LOG(Process) << LogPreamble(this) |
| << "Got stdout: " << std::string(data.data(), data.size()); |
| SendIO(debug_ipc::NotifyIO::Type::kStdout, std::move(data)); |
| } |
| |
| void DebuggedProcess::OnStderr(bool close) { |
| FX_DCHECK(stderr_.valid()); |
| if (close) { |
| DEBUG_LOG(Process) << LogPreamble(this) << "stderr closed."; |
| stderr_.Reset(); |
| return; |
| } |
| |
| auto data = ReadSocketInput(&stderr_); |
| FX_DCHECK(!data.empty()); |
| DEBUG_LOG(Process) << LogPreamble(this) |
| << "Got stderr: " << std::string(data.data(), data.size()); |
| SendIO(debug_ipc::NotifyIO::Type::kStderr, std::move(data)); |
| } |
| |
| void DebuggedProcess::SendIO(debug_ipc::NotifyIO::Type type, const std::vector<char>& data) { |
| // We send the IO message in chunks. |
| auto it = data.begin(); |
| size_t size = data.size(); |
| while (size > 0) { |
| size_t chunk_size = size; |
| if (chunk_size >= debug_ipc::NotifyIO::kMaxDataSize) |
| chunk_size = debug_ipc::NotifyIO::kMaxDataSize; |
| |
| auto end = it + chunk_size; |
| std::string msg(it, end); |
| |
| it = end; |
| size -= chunk_size; |
| |
| debug_ipc::NotifyIO notify; |
| notify.process_koid = koid_; |
| notify.type = type; |
| // We tell whether this is a piece of a bigger message. |
| notify.more_data_available = size > 0; |
| notify.data = std::move(msg); |
| |
| debug_ipc::MessageWriter writer; |
| debug_ipc::WriteNotifyIO(notify, &writer); |
| debug_agent_->stream()->Write(writer.MessageComplete()); |
| } |
| } |
| |
| void DebuggedProcess::PruneStepOverQueue() { |
| std::deque<StepOverTicket> pruned_tickets; |
| for (auto& ticket : step_over_queue_) { |
| if (!ticket.is_valid()) |
| continue; |
| pruned_tickets.push_back(std::move(ticket)); |
| } |
| |
| step_over_queue_ = std::move(pruned_tickets); |
| } |
| |
| zx_status_t DebuggedProcess::RegisterSoftwareBreakpoint(Breakpoint* bp, uint64_t address) { |
| auto found = software_breakpoints_.find(address); |
| if (found == software_breakpoints_.end()) { |
| auto breakpoint = std::make_unique<SoftwareBreakpoint>(bp, this, address); |
| if (zx_status_t status = breakpoint->Init(); status != ZX_OK) |
| return status; |
| |
| software_breakpoints_[address] = std::move(breakpoint); |
| return ZX_OK; |
| } else { |
| return found->second->RegisterBreakpoint(bp); |
| } |
| } |
| |
| void DebuggedProcess::UnregisterSoftwareBreakpoint(Breakpoint* bp, uint64_t address) { |
| auto found = software_breakpoints_.find(address); |
| if (found == software_breakpoints_.end()) { |
| return; |
| } |
| |
| bool still_used = found->second->UnregisterBreakpoint(bp); |
| if (!still_used) { |
| for (auto& pair : threads_) |
| pair.second->WillDeleteProcessBreakpoint(found->second.get()); |
| software_breakpoints_.erase(found); |
| } |
| } |
| |
| zx_status_t DebuggedProcess::RegisterHardwareBreakpoint(Breakpoint* bp, uint64_t address) { |
| auto found = hardware_breakpoints_.find(address); |
| if (found == hardware_breakpoints_.end()) { |
| auto breakpoint = std::make_unique<HardwareBreakpoint>(bp, this, address, arch_provider_); |
| if (zx_status_t status = breakpoint->Init(); status != ZX_OK) |
| return status; |
| |
| hardware_breakpoints_[address] = std::move(breakpoint); |
| return ZX_OK; |
| } else { |
| return found->second->RegisterBreakpoint(bp); |
| } |
| } |
| |
| void DebuggedProcess::UnregisterHardwareBreakpoint(Breakpoint* bp, uint64_t address) { |
| auto found = hardware_breakpoints_.find(address); |
| if (found == hardware_breakpoints_.end()) { |
| return; |
| } |
| |
| bool still_used = found->second->UnregisterBreakpoint(bp); |
| if (!still_used) { |
| for (auto& pair : threads_) |
| pair.second->WillDeleteProcessBreakpoint(found->second.get()); |
| hardware_breakpoints_.erase(found); |
| } |
| } |
| |
| } // namespace debug_agent |