blob: 5be7aec41944b11248cdc060977f3dbd4c5d0ee2 [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.
#pragma once
#include <functional>
#include <vector>
#include "src/developer/debug/ipc/protocol.h"
#include "src/developer/debug/zxdb/client/frame_fingerprint.h"
#include "src/developer/debug/zxdb/common/address_range.h"
#include "src/lib/fxl/macros.h"
#include "src/lib/fxl/memory/weak_ptr.h"
namespace zxdb {
class Breakpoint;
class Err;
class Frame;
class Location;
class Thread;
// Uncomment to enable detailed thread controller logging.
//
// TODO(brettw) when we have a settings system, make this run-time enableable
// for easier debugging when people encounter problems in the field.
//
// #define DEBUG_THREAD_CONTROLLERS
// Abstract base class that provides the policy decisions for various types of
// thread stepping.
class ThreadController {
public:
enum StopOp {
// Resume the thread. A controller can indicate "continue" but if another
// indicates "stop", the "stop" will take precedence.
kContinue,
// Keeps the thread stopped and reports the stop to the user. The
// controller is marked done and should be deleted. This takes precedence
// over any "continue" votes.
kStopDone,
// Reports that the controller doesn't know what to do with this thread
// stop. This is effectively a neutral vote for what should happen in
// response to a thread stop. If all active controllers report
// "unexpected", the thread will stop.
kUnexpected
};
// How the thread should run when it is executing this controller.
struct ContinueOp {
// Factory helper functions.
static ContinueOp Continue() {
return ContinueOp(); // Defaults are good for this case.
}
static ContinueOp StepInstruction() {
ContinueOp result;
result.how = debug_ipc::ResumeRequest::How::kStepInstruction;
return result;
}
static ContinueOp StepInRange(AddressRange range) {
ContinueOp result;
result.how = debug_ipc::ResumeRequest::How::kStepInRange;
result.range = range;
return result;
}
// See synthetic_stop_ below.
static ContinueOp SyntheticStop() {
ContinueOp result;
result.synthetic_stop_ = true;
return result;
}
// A synthetic stop means that the thread remains stopped but a synthetic
// stop notification is broadcast to make it look like the thread did
// continued and stopped again. This will call back into the top
// controller's OnThreadStop().
//
// This is useful when modifying the stack for inline routines, where the
// code didn't execute but from a user perspective they stepped into an
// inline subroutine. In this case the thread controller will update the
// Stack to reflect the new state, and return ContinueOp::SyntheticStop().
//
// Why isn't this a StopOp instead? This only makes sense as the initial
// state of the ThreadController that decides it doesn't need to do
// anything but wants to pretend that it did. When a ThreadController is in
// OnThreadStop and about to return a StopOp, returning kStop is a real
// thread stop and nothing needs to be synthetic.
//
// See GetContinueOp() for more.
bool synthetic_stop_ = false;
// Valid when synthetic_stop = true.
debug_ipc::ResumeRequest::How how =
debug_ipc::ResumeRequest::How::kContinue;
// When how == kStepInRange, this defines the address range to step in. As
// long as the instruction pointer is inside, execution will continue.
AddressRange range;
};
ThreadController();
virtual ~ThreadController();
// Registers the thread with the controller. The controller will be owned
// by the thread (possibly indirectly) so the pointer will remain valid for
// the rest of the lifetime of the controller.
//
// The implementation should call set_thread() with the thread.
//
// When the implementation is ready, it will issue the given callback to
// run the thread. The callback can be issued reentrantly from inside this
// function if the controller is ready synchronously.
//
// If the callback does not specify an error, the thread will be resumed
// when it is called. If the callback has an error, it will be reported and
// the thread will remain stopped.
virtual void InitWithThread(Thread* thread,
std::function<void(const Err&)> cb) = 0;
// Returns how to continue the thread when running this controller. This
// will be called after InitWithThread and after every subsequent kContinue
// response from OnThreadStop to see how the controller wishes to run.
//
// A thread controller can return a "synthetic stop" from this function which
// will schedule an OnThreadStop() call in the future without running the
// thread. This can be used to adjust the ambiguous inline stack state (see
// Stack object) to implement step commands.
//
// GetContinueOp() should not change thread state and controllers should be
// prepared for only InitWithThread() followe by OnThreadStop() calls. When
// thread controllers embed other thread controllers, the embedding
// controller may create the nested one and want it to evaluate the current
// stop, and this happens without ever continuing.
virtual ContinueOp GetContinueOp() = 0;
// Notification that the thread has stopped. The return value indicates what
// the thread should do in response.
//
// If the ThreadController returns |kStop|, its assumed the controller has
// completed its job and it will be deleted. |kContinue| doesn't necessarily
// mean the thread will continue, as there could be multiple controllers
// active and any of them can report "stop". When a thread is being
// continued, the main controller will get GetContinueOp() called to see what
// type of continuation it wants.
virtual StopOp OnThreadStop(
debug_ipc::NotifyException::Type stop_type,
const std::vector<fxl::WeakPtr<Breakpoint>>& hit_breakpoints) = 0;
#if defined(DEBUG_THREAD_CONTROLLERS)
// Writes the log message prefixed with the thread controller type. Callers
// should pass constant strings through here so the Log function takes
// almost no time if it's disabled: in the future we may want to make this
// run-time enable-able
void Log(const char* format, ...) const;
// Logs the raw string (no controller name prefix).
static void LogRaw(const char* format, ...);
// Returns the given frame's function name or a placeholder string if
// unavailable. Does nothing if logging is disabled (computing this is
// non-trivial).
static std::string FrameFunctionNameForLog(const Frame* frame);
#else
void Log(const char* format, ...) const {}
static void LogRaw(const char* format, ...) {}
static std::string FrameFunctionNameForLog(const Frame* frame) {
return std::string();
}
#endif
protected:
// How the frame argument to SetInlineFrameIfAmbiguous() is interpreted.
enum class InlineFrameIs {
// Set the inline frame equal to the given one.
kEqual,
// Set the inline frame to the frame immediately before the given one. This
// exists so that calling code can reference the previuos frame without
// actually having to compute the fingerprint of the previous frame (it may
// not be available if previous stack frames haven't been synced).
kOneBefore
};
Thread* thread() { return thread_; }
void set_thread(Thread* thread) { thread_ = thread; }
// Returns the name of this thread controller. This will be visible in logs.
// This should be something simple and short like "Step" or "Step Over".
virtual const char* GetName() const = 0;
// The beginning of an inline function is ambiguous about whether you're at
// the beginning of the function or about to call it (see Stack object for
// more).
//
// Many stepping functions know what frame they think they should be in, and
// identify this based on the frame fingerprint. As a concrete example, if
// a "finish" command exits a stack frame, but the next instruction is the
// beginning of an inlined function, the "finish" controller would like to
// say you're in the stack it returned to, not the inlined function.
//
// This function checks if there is ambiguity of inline frames and whether
// one of those ambiguous frames matches the given fingerprint. In this case,
// it will set the top stack frame to be the requested one.
//
// If there is no ambiguity or one of the possibly ambiguous frames doesn't
// match the given fingerprint, the inline frame hide count will be
// unchanged.
void SetInlineFrameIfAmbiguous(InlineFrameIs comparison,
FrameFingerprint fingerprint);
// Tells the owner of this class that this ThreadController has completed
// its work. Normally returning kStop from OnThreadStop() will do this, but
// if the controller has another way to get events (like breakpoints), it
// may notice out-of-band that its work is done.
//
// This function will likely cause |this| to be deleted.
void NotifyControllerDone();
private:
Thread* thread_ = nullptr;
FXL_DISALLOW_COPY_AND_ASSIGN(ThreadController);
};
} // namespace zxdb