| // 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/breakpoint.h" |
| |
| #include "src/developer/debug/debug_agent/process_breakpoint.h" |
| #include "src/developer/debug/shared/logging/logging.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace debug_agent { |
| |
| // Breakpoint::DoesExceptionApply ------------------------------------------------------------------ |
| |
| bool Breakpoint::DoesExceptionApply(debug_ipc::BreakpointType exception_type, |
| debug_ipc::BreakpointType bp_type) { |
| if (exception_type == debug_ipc::BreakpointType::kLast || |
| bp_type == debug_ipc::BreakpointType::kLast) { |
| FX_NOTREACHED() << "Wrong exception (" << static_cast<uint32_t>(exception_type) |
| << ") or bp_type (" << static_cast<uint32_t>(bp_type) << ")."; |
| return false; |
| } |
| |
| if (exception_type == debug_ipc::BreakpointType::kSoftware) |
| return bp_type == debug_ipc::BreakpointType::kSoftware; |
| |
| if (exception_type == debug_ipc::BreakpointType::kHardware) |
| return bp_type == debug_ipc::BreakpointType::kHardware; |
| |
| // Now only watchpoint types are left. |
| if (!IsWatchpointType(bp_type)) |
| return false; |
| |
| // If any the types is a read write, it targets this type. |
| if (exception_type == debug_ipc::BreakpointType::kReadWrite || |
| bp_type == debug_ipc::BreakpointType::kReadWrite) |
| return true; |
| |
| // R/W case are already covered. |
| return exception_type == bp_type; |
| } |
| |
| // Breakpoint::ProcessDelegate --------------------------------------------------------------------- |
| |
| debug::Status Breakpoint::ProcessDelegate::RegisterBreakpoint(Breakpoint* bp, |
| zx_koid_t process_koid, |
| uint64_t address) { |
| FX_NOTREACHED() << "Should override."; |
| return debug::Status("Expecting override."); |
| } |
| |
| // Called When the breakpoint no longer applies to this location. |
| void Breakpoint::ProcessDelegate::UnregisterBreakpoint(Breakpoint* bp, zx_koid_t process_koid, |
| uint64_t address) { |
| FX_NOTREACHED() << "Should override."; |
| } |
| |
| debug::Status Breakpoint::ProcessDelegate::RegisterWatchpoint(Breakpoint* bp, |
| zx_koid_t process_koid, |
| const debug::AddressRange& range) { |
| FX_NOTREACHED() << "Should override."; |
| return debug::Status("Expecting override."); |
| } |
| |
| void Breakpoint::ProcessDelegate::UnregisterWatchpoint(Breakpoint* bp, zx_koid_t process_koid, |
| const debug::AddressRange& range) { |
| FX_NOTREACHED() << "Should override."; |
| } |
| |
| // Breakpoint -------------------------------------------------------------------------------------- |
| |
| namespace { |
| |
| std::string Preamble(const Breakpoint* bp) { |
| return fxl::StringPrintf("[Breakpoint %u (%s)] ", bp->settings().id, bp->settings().name.c_str()); |
| } |
| |
| // Debug logging to see if a breakpoint applies to a thread. |
| void LogAppliesToThread(const Breakpoint* bp, zx_koid_t pid, zx_koid_t tid, bool applies) { |
| DEBUG_LOG(Breakpoint) << Preamble(bp) << "applies to [P: " << pid << ", T: " << tid << "]? " |
| << applies; |
| } |
| |
| void LogSetSettings(debug::FileLineFunction location, const Breakpoint* bp) { |
| std::stringstream ss; |
| |
| // Print a list of locations (process + thread + address) place of an actual breakpoint. |
| ss << "Updating locations: "; |
| for (auto& location : bp->settings().locations) { |
| // Log the process. |
| ss << std::dec << "[P: " << location.id.process; |
| |
| // |thread_koid| == 0 means that it applies to all the threads. |
| if (location.id.thread != 0) |
| ss << ", T: " << location.id.thread; |
| |
| // Print the actual location. |
| ss << "], addr: 0x" << std::hex << location.address |
| << ", range: " << location.address_range.ToString(); |
| } |
| |
| DEBUG_LOG_WITH_LOCATION(Breakpoint, location) << Preamble(bp) << ss.str(); |
| } |
| |
| } // namespace |
| |
| Breakpoint::Breakpoint(ProcessDelegate* process_delegate) : process_delegate_(process_delegate) {} |
| |
| Breakpoint::~Breakpoint() { |
| DEBUG_LOG(Breakpoint) << Preamble(this) << "Deleting."; |
| for (const auto& [process_koid, address] : locations_) { |
| DEBUG_LOG(Breakpoint) << "- Proc " << process_koid << " at address 0x " << std::hex << address; |
| process_delegate_->UnregisterBreakpoint(this, process_koid, address); |
| } |
| |
| for (const auto& [process_koid, address_range] : watchpoint_locations_) { |
| DEBUG_LOG(Breakpoint) << "- Proc " << process_koid << " at address " << std::hex |
| << address_range.ToString(); |
| process_delegate_->UnregisterWatchpoint(this, process_koid, address_range); |
| } |
| } |
| |
| debug::Status Breakpoint::SetSettings(const debug_ipc::BreakpointSettings& settings) { |
| FX_DCHECK(settings.type != debug_ipc::BreakpointType::kLast); |
| settings_ = settings; |
| LogSetSettings(FROM_HERE, this); |
| |
| // The stats needs to reference the current ID. We assume setting the |
| // settings doesn't update the stats (an option to do this may need to be |
| // added in the future). |
| stats_.id = settings_.id; |
| |
| switch (settings_.type) { |
| case debug_ipc::BreakpointType::kSoftware: |
| case debug_ipc::BreakpointType::kHardware: |
| return SetBreakpointLocations(settings); |
| case debug_ipc::BreakpointType::kReadWrite: |
| case debug_ipc::BreakpointType::kWrite: |
| return SetWatchpointLocations(settings); |
| case debug_ipc::BreakpointType::kLast: |
| break; |
| } |
| |
| FX_NOTREACHED() << "Invalid breakpoint type: " << static_cast<int>(settings_.type); |
| return debug::Status("Invalid breakpoint type"); |
| } |
| |
| debug::Status Breakpoint::SetSettings(std::string name, zx_koid_t process_koid, uint64_t address) { |
| debug_ipc::BreakpointSettings settings; |
| settings.id = debug_ipc::kDebugAgentInternalBreakpointId; |
| settings.name = std::move(name); |
| |
| debug_ipc::ProcessBreakpointSettings& location = settings.locations.emplace_back(); |
| location.id.process = process_koid; |
| location.address = address; |
| |
| return SetSettings(settings); |
| } |
| |
| debug::Status Breakpoint::SetBreakpointLocations(const debug_ipc::BreakpointSettings& settings) { |
| debug::Status result; |
| |
| // The set of new locations. |
| std::set<LocationPair> new_set; |
| for (const auto& cur : settings.locations) |
| new_set.emplace(cur.id.process, cur.address); |
| |
| // Removed locations. |
| for (const auto& loc : locations_) { |
| if (new_set.find(loc) == new_set.end()) |
| process_delegate_->UnregisterBreakpoint(this, loc.first, loc.second); |
| } |
| |
| // Added locations. |
| for (const auto& loc : new_set) { |
| if (locations_.find(loc) == locations_.end()) { |
| debug::Status process_status = |
| process_delegate_->RegisterBreakpoint(this, loc.first, loc.second); |
| if (process_status.has_error()) |
| result = process_status; |
| } |
| } |
| |
| locations_ = std::move(new_set); |
| return result; |
| } |
| |
| debug::Status Breakpoint::SetWatchpointLocations(const debug_ipc::BreakpointSettings& settings) { |
| debug::Status result; |
| |
| // The set of new locations. |
| std::set<WatchpointLocationPair, WatchpointLocationPairCompare> new_set; |
| for (const auto& cur : settings.locations) |
| new_set.emplace(cur.id.process, cur.address_range); |
| |
| // Removed locations. |
| for (const auto& loc : watchpoint_locations_) { |
| if (new_set.find(loc) == new_set.end()) |
| process_delegate_->UnregisterWatchpoint(this, loc.first, loc.second); |
| } |
| |
| // Added locations. |
| for (const auto& loc : new_set) { |
| if (watchpoint_locations_.find(loc) == watchpoint_locations_.end()) { |
| debug::Status process_status = |
| process_delegate_->RegisterWatchpoint(this, loc.first, loc.second); |
| if (process_status.has_error()) |
| result = process_status; |
| } |
| } |
| |
| watchpoint_locations_ = std::move(new_set); |
| return result; |
| } |
| |
| bool Breakpoint::AppliesToThread(zx_koid_t pid, zx_koid_t tid) const { |
| for (auto& location : settings_.locations) { |
| if (location.id.process == pid) { |
| if (location.id.thread == 0 || location.id.thread == tid) { |
| LogAppliesToThread(this, pid, tid, true); |
| return true; |
| } |
| } |
| } |
| |
| LogAppliesToThread(this, pid, tid, false); |
| return false; |
| } |
| |
| // In the future we will want to have breakpoints that trigger on a specific |
| // hit count or other conditions and will need a "kContinue" result. |
| Breakpoint::HitResult Breakpoint::OnHit() { |
| stats_.hit_count++; |
| if (settings_.one_shot) { |
| DEBUG_LOG(Breakpoint) << Preamble(this) << "One-shot breakpoint. Will be deleted."; |
| stats_.should_delete = true; |
| return HitResult::kOneShotHit; |
| } |
| return HitResult::kHit; |
| } |
| |
| // WatchpointLocationPairCompare ------------------------------------------------------------------- |
| |
| bool Breakpoint::WatchpointLocationPairCompare::operator()( |
| const WatchpointLocationPair& lhs, const WatchpointLocationPair& rhs) const { |
| if (lhs.first != rhs.first) |
| return lhs.first < rhs.first; |
| |
| if (lhs.second.begin() != rhs.second.begin()) |
| return lhs.second.begin() < rhs.second.begin(); |
| return lhs.second.end() < rhs.second.end(); |
| } |
| |
| } // namespace debug_agent |