blob: bd8fcd58c678835fd5ea62d95e20024c364b01aa [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/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 initalize the parts that
// can report errors.
explicit ProcessBreakpoint(Breakpoint* breakpoint,
ProcessMemoryAccessor* memory_accessor,
zx_koid_t process_koid, 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_; }
uint64_t address() const { return address_; }
// 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).
void 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.
//
// 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(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,
uint32_t exception_type);
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 Install();
void Uninstall();
ProcessMemoryAccessor* memory_accessor_; // Non-owning.
zx_koid_t process_koid_;
uint64_t address_;
// Set to true when the instruction has been replaced.
bool installed_ = false;
// Previous memory contents before being replaced with the break instruction.
arch::BreakInstructionType previous_data_ = 0;
// 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_