blob: e18f0b19df108c01e0b8d23a3b68ee74552003d1 [file] [log] [blame]
// 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.
#ifndef GARNET_BIN_DEBUG_AGENT_PROCESS_BREAKPOINT_H_
#define GARNET_BIN_DEBUG_AGENT_PROCESS_BREAKPOINT_H_
#include <map>
#include "garnet/bin/debug_agent/arch.h"
#include "garnet/bin/debug_agent/debugged_process.h"
#include "garnet/bin/debug_agent/process_memory_accessor.h"
#include "garnet/lib/debug_ipc/records.h"
#include "lib/fxl/macros.h"
namespace debug_agent {
class Breakpoint;
// Represents one breakpoint address in a single process. One Breakpoint object
// can expand to many ProcessBreakpoints across multiple processes and within a
// single one (when a symbolic breakpoint expands to multiple addresses). Also,
// multiple Breakpoint objects can refer to the same ProcessBreakpoint when
// they refer to the same address.
class ProcessBreakpoint {
public:
// Given the initial Breakpoint object this corresponds to. Breakpoints
// can be added or removed later.
//
// Call Init() immediately after construction to initialize the parts that
// can report errors.
explicit ProcessBreakpoint(Breakpoint* breakpoint,
DebuggedProcess* debugged_process,
ProcessMemoryAccessor* memory_accessor,
uint64_t address);
~ProcessBreakpoint();
// Call immediately after construction. If it returns failure, the breakpoint
// will not work.
zx_status_t Init();
zx_koid_t process_koid() const { return process_->koid(); }
DebuggedProcess* process() const { return process_; }
uint64_t address() const { return address_; }
const std::vector<Breakpoint*> breakpoints() const { return breakpoints_; }
// Adds or removes breakpoints associated with this process/address.
// Unregister returns whether there are still any breakpoints referring to
// this address (false means this is unused and should be deleted).
zx_status_t RegisterBreakpoint(Breakpoint* breakpoint);
bool UnregisterBreakpoint(Breakpoint* breakpoint);
// Writing debug breakpoints changes memory contents. If an unmodified
// virtual picture of memory is needed, this function will replace the
// replacement from this breakpoint if it appears in the given block.
// Otherwise does nothing.
void FixupMemoryBlock(debug_ipc::MemoryBlock* block);
// Notification that this breakpoint was just hit. All affected Breakpoints
// will have their stats updated and placed in the *stats param. This makes
// a difference whether the exceptions was software or hardware (debug
// registers) triggered.
//
// IMPORTANT: The caller should check the stats and for any breakpoint
// with "should_delete" set, remove the breakpoints. This can't conveniently
// be done within this call because it will cause this ProcessBreakpoint
// object to be deleted from within itself.
void OnHit(debug_ipc::BreakpointType exception_type,
std::vector<debug_ipc::BreakpointStats>* hit_breakpoints);
// Call before single-stepping over a breakpoint. This will remove the
// breakpoint such that it will be put back when the exception is hit and
// BreakpointStepHasException() is called.
//
// The thread must be put into single-step mode by the caller when this
// function is called.
void BeginStepOver(zx_koid_t thread_koid);
// When a thread has a "current breakpoint" its handling, exceptions will be
// routed here first. A thread has a current breakpoint when it's either
// suspended (can not generate exceptions), or when stepping over the
// breakpoint.
//
// This function will return true if the exception was from successfully
// stepping over this breakpoint. Otherwise, the stepped-over instruction
// (the one with the breakpoint) caused an exception itself (say, an access
// violation). In either case, the breakpoint will clean up after itself from
// a single-step.
bool BreakpointStepHasException(
zx_koid_t thread_koid, debug_ipc::NotifyException::Type exception_type);
bool SoftwareBreakpointInstalled() const;
bool HardwareBreakpointInstalled() const;
private:
// A breakpoint could be removed in the middle of single-stepping it. We
// need to track this to handle the race between deleting it and the
// step actually happening.
enum class StepStatus {
kCurrent, // Single-step currently valid.
kObsolete // Breakpoint was removed while single-stepping over.
};
// Returns true if the breakpoint is temporarily disabled as one or more
// threads step over it.
bool CurrentlySteppingOver() const;
// Install or uninstall this breakpoint.
zx_status_t Update(); // Will add/remove breakpoints as needed/
void Uninstall();
DebuggedProcess* process_; // Not-owning.
ProcessMemoryAccessor* memory_accessor_; // Non-owning.
uint64_t address_;
// Low-level implementations of the breakpoints.
// A ProcessBreakpoint represents the actual "installation" of a Breakpoint
// in a particular location (address). A Breakpoint can have many locations:
//
// b Foo() -> If Foo() is inlined, you can get 2+ locations.
//
// In that base, that Breakpoint will have two locations, which means two
// "installations", or ProcessBreakpoint.
//
// A Breakpoint can be a software or a hardware one. That will define what
// kind of installation the ProcessBreakpoint implements. Now, if a software
// and a separate hardware breakpoint install to the same memory address, they
// will implement the same ProcessBreakpoint, which will have both
// |software_breakpoint_| and |hardware_breakpoint_| members instanced.
// Null means that that particular installation is not used.
class SoftwareBreakpoint;
class HardwareBreakpoint;
std::unique_ptr<SoftwareBreakpoint> software_breakpoint_;
std::unique_ptr<HardwareBreakpoint> hardware_breakpoint_;
// Breakpoints that refer to this ProcessBreakpoint. More than one Breakpoint
// can refer to the same memory address.
std::vector<Breakpoint*> breakpoints_;
// Tracks the threads currently single-stepping over this breakpoint.
// Normally this will be empty (nobody) or have one thread, but could be more
// than one in rare cases. Maps thread koid to status.
//
// A step is executed by putting back the original instruction, stepping the
// thread, and then re-inserting the breakpoint instruction. The breakpoint
// instruction can't be put back until there are no more "kCurrent" threads
// in this map.
//
// This could be a simple refcount, but is a set so we can more robustly
// check for mistakes. CurrentlySteppingOver() checks this list to see if
// the breakpoint is disabled due to stepping.
//
// TODO(brettw) disabling the breakpoint opens a window where another thread
// can execute and miss the breakpoint. To avoid this, we need to implement
// something similar to GDB's "displaced step" to execute the instruction
// without ever removing the breakpoint instruction.
std::map<zx_koid_t, StepStatus> thread_step_over_;
FXL_DISALLOW_COPY_AND_ASSIGN(ProcessBreakpoint);
};
} // namespace debug_agent
#endif // GARNET_BIN_DEBUG_AGENT_PROCESS_BREAKPOINT_H_