blob: d7dc649091f1931bc3b5ef53fbd786a0cbabdca1 [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 SRC_DEVELOPER_DEBUG_ZXDB_CLIENT_THREAD_CONTROLLER_H_
#define SRC_DEVELOPER_DEBUG_ZXDB_CLIENT_THREAD_CONTROLLER_H_
#include <vector>
#include "lib/fit/function.h"
#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;
// Abstract base class that provides the policy decisions for various types of thread stepping.
//
// Once installed, the thread will ask the topmost thread controller how (and whether) to continue.
// All thread controllers installed on a thread will get notified for each exception and indicate
// whether they want to handle the stop or continue. Each thread controller is queried for each
// stop since completions could happen in the in any order.
//
// The thread may also delete thread controllers. This can happen when the thread is terminated or
// when there is an internal error stepping. If a controller has a callback it executes on
// completion it should be prepared to issue the callback from its destructor in such a way to
// indicate that the step operation failed.
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 = false.
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 SetThread() 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
// or fails 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, fit::callback<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.
//
// At this call, the stop location will be thread().GetStack()[0]. Thread controllers will only
// be called when there is a valid location for the stop, so there is guaranteed to be at least
// one stack entry (in constrast to general thread exception observers).
//
// ARGUMENTS
// ---------
// The exception type may be "kNone" if the exception type shouldn't matter to this controller.
// Controllers should treak "kNone" as being relevant to themselves. When a controller is used as
// a component of another controller, the exception type may have been "consumed" and a nested
// controller merely needs to evaluate its opinion of the current location.
//
// The stop type and breakpoint information should be passed to the first thread controller that
// handles the stop (this might be a sub controller if a controller is delegating the current
// execution to another one). Other controllers that might handle the stop (say, if a second
// sub-controller is created when the first one is done) don't care and might get confused by stop
// information originally handled by another one. In this second case, "kNone" and an empty
// breakpoint list should be sent to OnThreadStop().
//
// RETURN VALUE
// ------------
// 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::ExceptionType stop_type,
const std::vector<fxl::WeakPtr<Breakpoint>>& hit_breakpoints) = 0;
// 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;
// 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);
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 SetThread(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();
// Returns true if this controller has debug logging enabled. This is only valid after the thread
// has been set.
bool enable_debug_logging() const { return enable_debug_logging_; }
private:
Thread* thread_ = nullptr;
// Initialized from the setting when the thread is known.
bool enable_debug_logging_ = false;
FXL_DISALLOW_COPY_AND_ASSIGN(ThreadController);
};
} // namespace zxdb
#endif // SRC_DEVELOPER_DEBUG_ZXDB_CLIENT_THREAD_CONTROLLER_H_