| // 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::kResolveAndContinue; | |
| // 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_ |