| // 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_thread.h" |
| |
| #include <inttypes.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls/debug.h> |
| #include <zircon/syscalls/exception.h> |
| |
| #include <memory> |
| |
| #include "src/developer/debug/debug_agent/arch.h" |
| #include "src/developer/debug/debug_agent/debug_agent.h" |
| #include "src/developer/debug/debug_agent/debugged_process.h" |
| #include "src/developer/debug/debug_agent/object_util.h" |
| #include "src/developer/debug/debug_agent/process_breakpoint.h" |
| #include "src/developer/debug/debug_agent/process_info.h" |
| #include "src/developer/debug/debug_agent/process_watchpoint.h" |
| #include "src/developer/debug/debug_agent/unwind.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/message_loop_target.h" |
| #include "src/developer/debug/shared/stream_buffer.h" |
| #include "src/developer/debug/shared/zx_status.h" |
| #include "src/lib/fxl/logging.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace debug_agent { |
| |
| namespace { |
| |
| bool IsBlockedOnException(const zx::thread& thread) { |
| zx_info_thread info; |
| return thread.get_info(ZX_INFO_THREAD, &info, sizeof(info), nullptr, |
| nullptr) == ZX_OK && |
| info.state == ZX_THREAD_STATE_BLOCKED_EXCEPTION; |
| } |
| |
| // Used to have better context upon reading the debug logs. |
| std::string ThreadPreamble(const DebuggedThread* thread) { |
| return fxl::StringPrintf("[Pr: %lu (%s), T: %lu] ", thread->process()->koid(), |
| thread->process()->name().c_str(), thread->koid()); |
| } |
| |
| // TODO(donosoc): Move this to a more generic place (probably shared) where it |
| // can be used by other code. |
| const char* ExceptionTypeToString(uint32_t type) { |
| switch (type) { |
| case ZX_EXCP_GENERAL: |
| return "ZX_EXCP_GENERAL"; |
| case ZX_EXCP_FATAL_PAGE_FAULT: |
| return "ZX_EXCP_FATAL_PAGE_FAULT"; |
| case ZX_EXCP_UNDEFINED_INSTRUCTION: |
| return "ZX_EXCP_UNDEFINED_INSTRUCTION"; |
| case ZX_EXCP_SW_BREAKPOINT: |
| return "ZX_EXCP_SW_BREAKPOINT"; |
| case ZX_EXCP_HW_BREAKPOINT: |
| return "ZX_EXCP_HW_BREAKPOINT"; |
| case ZX_EXCP_UNALIGNED_ACCESS: |
| return "ZX_EXCP_UNALIGNED_ACCESS"; |
| default: |
| break; |
| } |
| |
| return "<unknown>"; |
| } |
| |
| } // namespace |
| |
| DebuggedThread::DebuggedThread(DebuggedProcess* process, zx::thread thread, |
| zx_koid_t koid, zx::exception exception, |
| ThreadCreationOption option) |
| : debug_agent_(process->debug_agent()), |
| process_(process), |
| thread_(std::move(thread)), |
| koid_(koid), |
| exception_token_(std::move(exception)) { |
| switch (option) { |
| case ThreadCreationOption::kRunningKeepRunning: |
| // do nothing |
| break; |
| case ThreadCreationOption::kSuspendedKeepSuspended: |
| break; |
| case ThreadCreationOption::kSuspendedShouldRun: |
| ResumeException(); |
| break; |
| } |
| } |
| |
| DebuggedThread::~DebuggedThread() = default; |
| |
| void DebuggedThread::OnException(zx::exception exception_token, |
| zx_exception_info_t exception_info) { |
| exception_token_ = std::move(exception_token); |
| |
| debug_ipc::NotifyException exception; |
| exception.type = |
| arch::ArchProvider::Get().DecodeExceptionType(*this, exception_info.type); |
| |
| DEBUG_LOG(Thread) << ThreadPreamble(this) << "Exception: " |
| << ExceptionTypeToString(exception_info.type) << " -> " |
| << debug_ipc::NotifyException::TypeToString(exception.type); |
| |
| zx_thread_state_general_regs regs; |
| thread_.read_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs)); |
| |
| switch (exception.type) { |
| case debug_ipc::NotifyException::Type::kSingleStep: |
| return HandleSingleStep(&exception, ®s); |
| case debug_ipc::NotifyException::Type::kSoftware: |
| return HandleSoftwareBreakpoint(&exception, ®s); |
| case debug_ipc::NotifyException::Type::kHardware: |
| return HandleHardwareBreakpoint(&exception, ®s); |
| case debug_ipc::NotifyException::Type::kGeneral: |
| // TODO(donosoc): Should synthetic be general or invalid? |
| case debug_ipc::NotifyException::Type::kSynthetic: |
| return HandleGeneralException(&exception, ®s); |
| case debug_ipc::NotifyException::Type::kWatchpoint: |
| return HandleWatchpoint(&exception, ®s); |
| case debug_ipc::NotifyException::Type::kNone: |
| case debug_ipc::NotifyException::Type::kLast: |
| break; |
| } |
| |
| FXL_NOTREACHED() << "Invalid exception notification type: " |
| << static_cast<uint32_t>(exception.type); |
| |
| // The exception was unhandled, so we close it so that the system can run its |
| // course. The destructor would've done it anyway, but being explicit helps |
| // readability. |
| exception_token_.reset(); |
| } |
| |
| void DebuggedThread::HandleSingleStep(debug_ipc::NotifyException* exception, |
| zx_thread_state_general_regs* regs) { |
| if (current_breakpoint_) { |
| // The current breakpoint is set only when stopped at a breakpoint or when |
| // single-stepping over one. We're not going to get an exception for a |
| // thread when stopped, so hitting this exception means the breakpoint is |
| // done being stepped over. The breakpoint will tell us if the exception |
| // was from a normal completion of the breakpoint step, or whether |
| // something else went wrong while stepping. |
| bool completes_bp_step = |
| current_breakpoint_->EndStepOver(koid_, exception->type); |
| current_breakpoint_ = nullptr; |
| if (completes_bp_step && |
| run_mode_ == debug_ipc::ResumeRequest::How::kContinue) { |
| // This step was an internal thing to step over the breakpoint in |
| // service of continuing from a breakpoint. Transparently resume the |
| // thread since the client didn't request the step. The step |
| // (non-continue) cases will be handled below in the normal flow since |
| // we just finished a step. |
| ResumeForRunMode(); |
| return; |
| } |
| // Something else went wrong while stepping (the instruction with the |
| // breakpoint could have crashed). Fall through to dispatching the |
| // exception to the client. |
| current_breakpoint_ = nullptr; |
| } |
| |
| if (run_mode_ == debug_ipc::ResumeRequest::How::kContinue) { |
| // This could be due to a race where the user was previously single |
| // stepping and then requested a continue before the single stepping |
| // completed. It could also be a breakpoint that was deleted while |
| // in the process of single-stepping over it. In both cases, the |
| // least confusing thing is to resume automatically. |
| ResumeForRunMode(); |
| return; |
| } |
| |
| // When stepping in a range, automatically continue as long as we're |
| // still in range. |
| if (run_mode_ == debug_ipc::ResumeRequest::How::kStepInRange && |
| *arch::ArchProvider::Get().IPInRegs(regs) >= step_in_range_begin_ && |
| *arch::ArchProvider::Get().IPInRegs(regs) < step_in_range_end_) { |
| ResumeForRunMode(); |
| return; |
| } |
| |
| SendExceptionNotification(exception, regs); |
| } |
| |
| void DebuggedThread::HandleGeneralException( |
| debug_ipc::NotifyException* exception, zx_thread_state_general_regs* regs) { |
| SendExceptionNotification(exception, regs); |
| } |
| |
| void DebuggedThread::HandleSoftwareBreakpoint( |
| debug_ipc::NotifyException* exception, zx_thread_state_general_regs* regs) { |
| DEBUG_LOG(Thread) << ThreadPreamble(this) << "Hit SW breakpoint"; |
| |
| auto on_stop = UpdateForSoftwareBreakpoint(regs, &exception->hit_breakpoints); |
| switch (on_stop) { |
| case OnStop::kIgnore: |
| return; |
| case OnStop::kNotify: |
| SendExceptionNotification(exception, regs); |
| return; |
| case OnStop::kResume: { |
| // We mark the thread as within an exception |
| ResumeForRunMode(); |
| return; |
| } |
| } |
| |
| FXL_NOTREACHED() << "Invalid OnStop."; |
| } |
| |
| void DebuggedThread::HandleHardwareBreakpoint( |
| debug_ipc::NotifyException* exception, zx_thread_state_general_regs* regs) { |
| if (UpdateForHardwareBreakpoint(regs, &exception->hit_breakpoints) == |
| OnStop::kIgnore) |
| return; |
| |
| SendExceptionNotification(exception, regs); |
| } |
| |
| void DebuggedThread::HandleWatchpoint(debug_ipc::NotifyException* exception, |
| zx_thread_state_general_regs* regs) { |
| if (UpdateForWatchpoint(regs, &exception->hit_breakpoints) == OnStop::kIgnore) |
| return; |
| |
| SendExceptionNotification(exception, regs); |
| } |
| |
| void DebuggedThread::SendExceptionNotification( |
| debug_ipc::NotifyException* exception, zx_thread_state_general_regs* regs) { |
| FillThreadRecord(debug_ipc::ThreadRecord::StackAmount::kMinimal, regs, |
| &exception->thread); |
| |
| // Keep the thread suspended for the client. |
| |
| // TODO(brettw) suspend other threads in the process and other debugged |
| // processes as desired. |
| |
| // Send notification. |
| debug_ipc::MessageWriter writer; |
| debug_ipc::WriteNotifyException(*exception, &writer); |
| debug_agent_->stream()->Write(writer.MessageComplete()); |
| } |
| |
| void DebuggedThread::Resume(const debug_ipc::ResumeRequest& request) { |
| DEBUG_LOG(Thread) << ThreadPreamble(this) << "Resuming."; |
| |
| run_mode_ = request.how; |
| step_in_range_begin_ = request.range_begin; |
| step_in_range_end_ = request.range_end; |
| |
| ResumeForRunMode(); |
| } |
| |
| void DebuggedThread::ResumeException() { |
| // We need to mark that this token is correctly handled before closing it. |
| if (exception_token_.is_valid()) { |
| DEBUG_LOG(Thread) << "Resuming from exception."; |
| uint32_t state = ZX_EXCEPTION_STATE_HANDLED; |
| zx_status_t status = exception_token_.set_property(ZX_PROP_EXCEPTION_STATE, |
| &state, sizeof(state)); |
| FXL_DCHECK(status == ZX_OK) << "Got: " << zx_status_get_string(status); |
| } |
| exception_token_.reset(); |
| } |
| |
| void DebuggedThread::ResumeSuspension() { |
| suspend_token_.reset(); |
| } |
| |
| bool DebuggedThread::Suspend(bool synchronous) { |
| // Subsequent suspend calls should return immediatelly. Note that this does |
| // not mean that the thread is in that state, but rather that that operation |
| // was sent to the kernel. |
| if (suspended() || in_exception()) |
| return false; |
| |
| DEBUG_LOG(Thread) << ThreadPreamble(this) << "Suspending thread."; |
| |
| zx_status_t status; |
| status = thread_.suspend(&suspend_token_); |
| if (status != ZX_OK) { |
| FXL_LOG(WARNING) << ThreadPreamble(this) |
| << "Could not suspend: " << zx_status_get_string(status); |
| return false; |
| } |
| |
| if (synchronous) |
| return WaitForSuspension(DefaultSuspendDeadline()); |
| return true; |
| } |
| |
| zx::time DebuggedThread::DefaultSuspendDeadline() { |
| // Various events and environments can cause suspensions to take a long time, |
| // so this needs to be a relatively long time. We don't generally expect |
| // error cases that take infinitely long so there isn't much downside of a |
| // long timeout. |
| return zx::deadline_after(zx::sec(1)); |
| } |
| |
| bool DebuggedThread::WaitForSuspension(zx::time deadline) { |
| // This function is complex because a thread in an exception state can't be |
| // suspended (ZX-3772). Delivery of exceptions are queued on the |
| // exception port so our cached state may be stale, and exceptions can also |
| // race with our suspend call. |
| // |
| // To manually stress-test this code, write a one-line infinite loop: |
| // volatile bool done = false; |
| // while (!done) {} |
| // and step over it with "next". This will cause an infinite flood of |
| // single-step exceptions as fast as the debugger can process them. Pausing |
| // after doing the "next" will trigger a suspension and is more likely to |
| // race with an exception. |
| |
| // If an exception happens before the suspend does, we'll never get the |
| // suspend signal and will end up waiting for the entire timeout just to be |
| // able to tell the difference between suspended and exception. To avoid |
| // waiting for a long timeout to tell the difference, wait for short timeouts |
| // multiple times. |
| auto poll_time = zx::msec(10); |
| zx_status_t status = ZX_OK; |
| do { |
| // Always check the thread state from the kernel because of queue desribed |
| // above. |
| if (IsBlockedOnException(thread_)) |
| return true; |
| |
| zx_signals_t observed; |
| status = thread_.wait_one(ZX_THREAD_SUSPENDED, |
| zx::deadline_after(poll_time), &observed); |
| if (status == ZX_OK && (observed & ZX_THREAD_SUSPENDED)) |
| return true; |
| |
| } while (status == ZX_ERR_TIMED_OUT && zx::clock::get_monotonic() < deadline); |
| return false; |
| } |
| |
| void DebuggedThread::FillThreadRecord( |
| debug_ipc::ThreadRecord::StackAmount stack_amount, |
| const zx_thread_state_general_regs* optional_regs, |
| debug_ipc::ThreadRecord* record) const { |
| record->process_koid = process_->koid(); |
| record->thread_koid = koid(); |
| record->name = NameForObject(thread_); |
| |
| // State (running, blocked, etc.). |
| zx_info_thread info; |
| if (thread_.get_info(ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr) == |
| ZX_OK) { |
| record->state = ThreadStateToEnums(info.state, &record->blocked_reason); |
| } else { |
| FXL_NOTREACHED(); |
| record->state = debug_ipc::ThreadRecord::State::kDead; |
| } |
| |
| // The registers are available when suspended or blocked in an exception. |
| if ((info.state == ZX_THREAD_STATE_SUSPENDED || |
| info.state == ZX_THREAD_STATE_BLOCKED_EXCEPTION) && |
| stack_amount != debug_ipc::ThreadRecord::StackAmount::kNone) { |
| // Only record this when we actually attempt to query the stack. |
| record->stack_amount = stack_amount; |
| |
| // The registers are required, fetch them if the caller didn't provide. |
| zx_thread_state_general_regs queried_regs; // Storage for fetched regs. |
| zx_thread_state_general_regs* regs = nullptr; |
| if (!optional_regs) { |
| if (thread_.read_state(ZX_THREAD_STATE_GENERAL_REGS, &queried_regs, |
| sizeof(queried_regs)) == ZX_OK) |
| regs = &queried_regs; |
| } else { |
| // We don't change the values here but *InRegs below returns mutable |
| // references so we need a mutable pointer. |
| regs = const_cast<zx_thread_state_general_regs*>(optional_regs); |
| } |
| |
| if (regs) { |
| // Minimal stacks are 2 (current frame and calling one). Full stacks max |
| // out at 256 to prevent edge cases, especially around corrupted stacks. |
| uint32_t max_stack_depth = |
| stack_amount == debug_ipc::ThreadRecord::StackAmount::kMinimal ? 2 |
| : 256; |
| |
| UnwindStack(process_->process(), process_->dl_debug_addr(), thread_, |
| *regs, max_stack_depth, &record->frames); |
| } |
| } else { |
| // Didn't bother querying the stack. |
| record->stack_amount = debug_ipc::ThreadRecord::StackAmount::kNone; |
| record->frames.clear(); |
| } |
| } |
| |
| void DebuggedThread::ReadRegisters( |
| const std::vector<debug_ipc::RegisterCategory::Type>& cats_to_get, |
| std::vector<debug_ipc::RegisterCategory>* out) const { |
| out->clear(); |
| for (const auto& cat_type : cats_to_get) { |
| auto& cat = out->emplace_back(); |
| cat.type = cat_type; |
| zx_status_t status = arch::ArchProvider::Get().ReadRegisters( |
| cat_type, thread_, &cat.registers); |
| if (status != ZX_OK) { |
| out->pop_back(); |
| FXL_LOG(ERROR) << "Could not get register state for category: " |
| << debug_ipc::RegisterCategory::TypeToString(cat_type); |
| } |
| } |
| } |
| |
| zx_status_t DebuggedThread::WriteRegisters( |
| const std::vector<debug_ipc::Register>& regs) { |
| // We use a map to keep track of which categories will change. |
| std::map<debug_ipc::RegisterCategory::Type, debug_ipc::RegisterCategory> |
| categories; |
| |
| bool rip_change = false; |
| debug_ipc::RegisterID rip_id = GetSpecialRegisterID( |
| arch::ArchProvider::Get().GetArch(), debug_ipc::SpecialRegisterType::kIP); |
| |
| // We append each register to the correct category to be changed. |
| for (const debug_ipc::Register& reg : regs) { |
| auto cat_type = debug_ipc::RegisterCategory::RegisterIDToCategory(reg.id); |
| if (cat_type == debug_ipc::RegisterCategory::Type::kNone) { |
| FXL_LOG(WARNING) << "Attempting to change register without category: " |
| << RegisterIDToString(reg.id); |
| continue; |
| } |
| |
| // We are changing the RIP, meaning that we're not going to jump over a |
| // breakpoint. |
| if (reg.id == rip_id) |
| rip_change = true; |
| |
| auto& category = categories[cat_type]; |
| category.type = cat_type; |
| category.registers.push_back(reg); |
| } |
| |
| for (const auto& [cat_type, cat] : categories) { |
| FXL_DCHECK(cat_type != debug_ipc::RegisterCategory::Type::kNone); |
| zx_status_t res = arch::ArchProvider::Get().WriteRegisters(cat, &thread_); |
| if (res != ZX_OK) { |
| FXL_LOG(WARNING) << "Could not write category " |
| << debug_ipc::RegisterCategory::TypeToString(cat_type) |
| << ": " << debug_ipc::ZxStatusToString(res); |
| } |
| } |
| // If the debug agent wrote to the thread IP directly, then current state is |
| // no longer valid. Specifically, if we're currently on a breakpoint, we have |
| // to now know the fact that we're no longer in a breakpoint. |
| // |
| // This is necessary to avoid the single-stepping logic that the thread does |
| // when resuming from a breakpoint. |
| current_breakpoint_ = nullptr; |
| return ZX_OK; |
| } |
| |
| void DebuggedThread::SendThreadNotification() const { |
| debug_ipc::NotifyThread notify; |
| FillThreadRecord(debug_ipc::ThreadRecord::StackAmount::kMinimal, nullptr, |
| ¬ify.record); |
| |
| debug_ipc::MessageWriter writer; |
| debug_ipc::WriteNotifyThread( |
| debug_ipc::MsgHeader::Type::kNotifyThreadStarting, notify, &writer); |
| debug_agent_->stream()->Write(writer.MessageComplete()); |
| } |
| |
| void DebuggedThread::WillDeleteProcessBreakpoint(ProcessBreakpoint* bp) { |
| if (current_breakpoint_ == bp) |
| current_breakpoint_ = nullptr; |
| } |
| |
| DebuggedThread::OnStop DebuggedThread::UpdateForSoftwareBreakpoint( |
| zx_thread_state_general_regs* regs, |
| std::vector<debug_ipc::BreakpointStats>* hit_breakpoints) { |
| // Get the correct address where the CPU is after hitting a breakpoint |
| // (this is architecture specific). |
| uint64_t breakpoint_address = |
| arch::ArchProvider::Get() |
| .BreakpointInstructionForSoftwareExceptionAddress( |
| *arch::ArchProvider::Get().IPInRegs(regs)); |
| |
| ProcessBreakpoint* found_bp = |
| process_->FindProcessBreakpointForAddr(breakpoint_address); |
| if (found_bp) { |
| FixSoftwareBreakpointAddress(found_bp, regs); |
| |
| // When hitting a breakpoint, we need to check if indeed this exception |
| // should apply to this thread or not. |
| if (!found_bp->ShouldHitThread(koid())) { |
| DEBUG_LOG(Thread) << ThreadPreamble(this) |
| << "SW Breakpoint not for me. Ignoring."; |
| // The way to go over is to step over the breakpoint as one would over |
| // a resume. |
| current_breakpoint_ = found_bp; |
| return OnStop::kResume; |
| } |
| |
| UpdateForHitProcessBreakpoint(debug_ipc::BreakpointType::kSoftware, |
| found_bp, regs, hit_breakpoints); |
| |
| // The found_bp could have been deleted if it was a one-shot, so must |
| // not be dereferenced below this. |
| found_bp = nullptr; |
| } else { |
| // Hit a software breakpoint that doesn't correspond to any current |
| // breakpoint. |
| if (arch::ArchProvider::Get().IsBreakpointInstruction(process_->process(), |
| breakpoint_address)) { |
| // The breakpoint is a hardcoded instruction in the program code. In |
| // this case we want to continue from the following instruction since |
| // the breakpoint instruction will never go away. |
| *arch::ArchProvider::Get().IPInRegs(regs) = |
| arch::ArchProvider::Get().NextInstructionForSoftwareExceptionAddress( |
| *arch::ArchProvider::Get().IPInRegs(regs)); |
| zx_status_t status = |
| thread_.write_state(ZX_THREAD_STATE_GENERAL_REGS, regs, |
| sizeof(zx_thread_state_general_regs)); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Warning: could not update IP on thread, error = %d.", |
| static_cast<int>(status)); |
| } |
| |
| if (!process_->dl_debug_addr() && process_->RegisterDebugState()) { |
| DEBUG_LOG(Thread) << ThreadPreamble(this) |
| << "Found ld.so breakpoint. Sending modules."; |
| // This breakpoint was the explicit breakpoint ld.so executes to |
| // notify us that the loader is ready. Send the current module list |
| // and silently keep this thread stopped. The client will explicitly |
| // resume this thread when it's ready to continue (it will need to |
| // load symbols for the modules and may need to set breakpoints based |
| // on them). |
| std::vector<uint64_t> paused_threads; |
| paused_threads.push_back(koid()); |
| process_->SendModuleNotification(std::move(paused_threads)); |
| return OnStop::kIgnore; |
| } |
| } else { |
| // Not a breakpoint instruction. Probably the breakpoint instruction |
| // used to be ours but its removal raced with the exception handler. |
| // Resume from the instruction that used to be the breakpoint. |
| *arch::ArchProvider::Get().IPInRegs(regs) = breakpoint_address; |
| |
| // Don't automatically continue execution here. A race for this should |
| // be unusual and maybe something weird happened that caused an |
| // exception we're not set up to handle. Err on the side of telling the |
| // user about the exception. |
| } |
| } |
| return OnStop::kNotify; |
| } |
| |
| DebuggedThread::OnStop DebuggedThread::UpdateForHardwareBreakpoint( |
| zx_thread_state_general_regs* regs, |
| std::vector<debug_ipc::BreakpointStats>* hit_breakpoints) { |
| uint64_t breakpoint_address = |
| arch::ArchProvider::Get() |
| .BreakpointInstructionForHardwareExceptionAddress( |
| *arch::ArchProvider::Get().IPInRegs(regs)); |
| ProcessBreakpoint* found_bp = |
| process_->FindProcessBreakpointForAddr(breakpoint_address); |
| if (!found_bp) { |
| // Hit a hw debug exception that doesn't belong to any ProcessBreakpoint. |
| // This is probably a race between the removal and the exception handler. |
| |
| // Send a notification. |
| *arch::ArchProvider::Get().IPInRegs(regs) = breakpoint_address; |
| return OnStop::kNotify; |
| } |
| |
| FixSoftwareBreakpointAddress(found_bp, regs); |
| UpdateForHitProcessBreakpoint(debug_ipc::BreakpointType::kHardware, found_bp, |
| regs, hit_breakpoints); |
| |
| // The ProcessBreakpoint could've been deleted if it was a one-shot, so must |
| // not be derefereced below this. |
| found_bp = nullptr; |
| return OnStop::kNotify; |
| } |
| |
| DebuggedThread::OnStop DebuggedThread::UpdateForWatchpoint( |
| zx_thread_state_general_regs* regs, |
| std::vector<debug_ipc::BreakpointStats>* hit_breakpoints) { |
| auto& arch = arch::ArchProvider::Get(); |
| uint64_t address = arch.InstructionForWatchpointHit(*this); |
| |
| ProcessWatchpoint* wp = process_->FindWatchpointByAddress(address); |
| if (!wp) { |
| // Hit a hw debug exception that doesn't belong to any ProcessBreakpoint. |
| // This is probably a race between the removal and the exception handler. |
| |
| // Send a notification. |
| *arch::ArchProvider::Get().IPInRegs(regs) = address; |
| return OnStop::kNotify; |
| } |
| |
| FixAddressForWatchpointHit(wp, regs); |
| UpdateForWatchpointHit(wp, regs, hit_breakpoints); |
| |
| // If the watchpoint was one-shot, it would've been deleted, so we should not |
| // rely on it being there. |
| wp = nullptr; |
| return OnStop::kNotify; |
| } |
| |
| void DebuggedThread::FixSoftwareBreakpointAddress( |
| ProcessBreakpoint* process_breakpoint, zx_thread_state_general_regs* regs) { |
| // When the program hits one of our breakpoints, set the IP back to |
| // the exact address that triggered the breakpoint. When the thread |
| // resumes, this is the address that it will resume from (after |
| // putting back the original instruction), and will be what the client |
| // wants to display to the user. |
| *arch::ArchProvider::Get().IPInRegs(regs) = process_breakpoint->address(); |
| zx_status_t status = thread_.write_state( |
| ZX_THREAD_STATE_GENERAL_REGS, regs, sizeof(zx_thread_state_general_regs)); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Warning: could not update IP on thread, error = %d.", |
| static_cast<int>(status)); |
| } |
| } |
| |
| void DebuggedThread::FixAddressForWatchpointHit( |
| ProcessWatchpoint* watchpoint, zx_thread_state_general_regs* regs) { |
| auto& arch_provider = arch::ArchProvider::Get(); |
| *arch_provider.IPInRegs(regs) = arch_provider.NextInstructionForWatchpointHit( |
| *arch_provider.IPInRegs(regs)); |
| } |
| |
| void DebuggedThread::UpdateForHitProcessBreakpoint( |
| debug_ipc::BreakpointType exception_type, |
| ProcessBreakpoint* process_breakpoint, zx_thread_state_general_regs* regs, |
| std::vector<debug_ipc::BreakpointStats>* hit_breakpoints) { |
| current_breakpoint_ = process_breakpoint; |
| |
| process_breakpoint->OnHit(exception_type, hit_breakpoints); |
| |
| // Delete any one-shot breakpoints. Since there can be multiple Breakpoints |
| // (some one-shot, some not) referring to the current ProcessBreakpoint, |
| // this operation could delete the ProcessBreakpoint or it could not. If it |
| // does, our observer will be told and current_breakpoint_ will be cleared. |
| for (const auto& stats : *hit_breakpoints) { |
| if (stats.should_delete) |
| process_->debug_agent()->RemoveBreakpoint(stats.id); |
| } |
| } |
| |
| void DebuggedThread::UpdateForWatchpointHit( |
| ProcessWatchpoint* watchpoint, zx_thread_state_general_regs* regs, |
| std::vector<debug_ipc::BreakpointStats>* hit_breakpoints) { |
| auto break_stat = watchpoint->OnHit(); |
| |
| // Delete any one-shot watchpoints. Since there can be multiple Watchpoints |
| // (some one-shot, some not) referring to the current ProcessBreakpoint, |
| // this operation could delete the ProcessBreakpoint or it could not. If it |
| // does, our observer will be told and current_breakpoint_ will be cleared. |
| if (break_stat.should_delete) |
| process_->debug_agent()->RemoveWatchpoint(break_stat.id); |
| |
| *hit_breakpoints = {}; |
| hit_breakpoints->push_back(std::move(break_stat)); |
| } |
| |
| void DebuggedThread::ResumeForRunMode() { |
| // If we jumped, once we resume we reset the status. |
| if (in_exception()) { |
| // Note: we could have a valid suspend token here in addition to the |
| // exception if the suspension races with the delivery of the exception. |
| if (current_breakpoint_) { |
| // Going over a breakpoint always requires a single-step first. Then we |
| // continue according to run_mode_. |
| DEBUG_LOG(Thread) << ThreadPreamble(this) << "Stepping over thread."; |
| SetSingleStep(true); |
| current_breakpoint_->BeginStepOver(koid_); |
| } else { |
| // All non-continue resumptions require single stepping. |
| SetSingleStep(run_mode_ != debug_ipc::ResumeRequest::How::kContinue); |
| } |
| |
| ResumeException(); |
| } else if (suspended()) { |
| // A breakpoint should only be current when it was hit which will be |
| // caused by an exception. |
| FXL_DCHECK(!current_breakpoint_); |
| |
| // All non-continue resumptions require single stepping. |
| SetSingleStep(run_mode_ != debug_ipc::ResumeRequest::How::kContinue); |
| |
| // The suspend token is holding the thread suspended, releasing it will |
| // resume (if nobody else has the thread suspended). |
| ResumeSuspension(); |
| } |
| } |
| |
| void DebuggedThread::SetSingleStep(bool single_step) { |
| zx_thread_state_single_step_t value = single_step ? 1 : 0; |
| // This could fail for legitimate reasons, like the process could have just |
| // closed the thread. |
| thread_.write_state(ZX_THREAD_STATE_SINGLE_STEP, &value, sizeof(value)); |
| } |
| |
| const char* DebuggedThread::ClientStateToString(ClientState client_state) { |
| switch (client_state) { |
| case ClientState::kRunning: |
| return "Running"; |
| case ClientState::kPaused: |
| return "Paused"; |
| } |
| |
| FXL_NOTREACHED(); |
| return "<unknown>"; |
| } |
| |
| } // namespace debug_agent |