| // 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_CONSOLE_CONSOLE_CONTEXT_H_ | |
| #define SRC_DEVELOPER_DEBUG_ZXDB_CONSOLE_CONSOLE_CONTEXT_H_ | |
| #include <condition_variable> | |
| #include <map> | |
| #include <memory> | |
| #include <mutex> | |
| #include <string> | |
| #include <thread> | |
| #include <unordered_set> | |
| #include "src/developer/debug/zxdb/client/breakpoint_observer.h" | |
| #include "src/developer/debug/zxdb/client/component_observer.h" | |
| #include "src/developer/debug/zxdb/client/download_observer.h" | |
| #include "src/developer/debug/zxdb/client/pretty_stack_manager.h" | |
| #include "src/developer/debug/zxdb/client/process_observer.h" | |
| #include "src/developer/debug/zxdb/client/session_observer.h" | |
| #include "src/developer/debug/zxdb/client/setting_store_observer.h" | |
| #include "src/developer/debug/zxdb/client/system_observer.h" | |
| #include "src/developer/debug/zxdb/client/target_observer.h" | |
| #include "src/developer/debug/zxdb/client/thread_observer.h" | |
| #include "src/developer/debug/zxdb/console/command.h" | |
| #include "src/developer/debug/zxdb/console/test_failure_stack_matcher.h" | |
| #include "src/developer/debug/zxdb/symbols/module_symbol_status.h" | |
| namespace zxdb { | |
| class Breakpoint; | |
| class Command; | |
| class Filter; | |
| class Frame; | |
| class OutputBuffer; | |
| class Session; | |
| // The context for console commands. In a model-view-controller UI, this would | |
| // represent the state associated with the view and controller (depending on | |
| // how one splits things up). It keeps track of the currently selected | |
| // objects and watches for changes. | |
| // | |
| // This class maintains the mapping between objects and IDs. | |
| class ConsoleContext : public ProcessObserver, | |
| public SessionObserver, | |
| public SettingStoreObserver, | |
| public SystemObserver, | |
| public TargetObserver, | |
| public ThreadObserver, | |
| public BreakpointObserver, | |
| public DownloadObserver, | |
| public ComponentObserver { | |
| public: | |
| explicit ConsoleContext(Session* session); | |
| ~ConsoleContext(); | |
| Session* session() { return session_; } | |
| const Session* session() const { return session_; } | |
| // Returns the ID for the object. Asserts and returns 0 if not found. | |
| int IdForTarget(const Target* target) const; | |
| int IdForThread(const Thread* thread) const; | |
| int IdForFrame(const Frame* frame) const; | |
| int IdForBreakpoint(const Breakpoint* breakpoint) const; | |
| int IdForFilter(const Filter* filter) const; | |
| int IdForSymbolServer(const SymbolServer* symbol_server) const; | |
| // The active target will always exist except during setup and teardown. | |
| void SetActiveTarget(const Target* target); | |
| int GetActiveTargetId() const; | |
| Target* GetActiveTarget() const; | |
| // The active symbol server may or may not exist. | |
| void SetActiveSymbolServer(const SymbolServer* target); | |
| int GetActiveSymbolServerId() const; | |
| SymbolServer* GetActiveSymbolServer() const; | |
| // The active thread for its target. The active target is not affected. The | |
| // active thread ID for a target not running will be 0. | |
| void SetActiveThreadForTarget(const Thread* thread); | |
| int GetActiveThreadIdForTarget(const Target* target); | |
| Thread* GetActiveThreadForTarget(const Target* target); | |
| // Frames are a little bit different than threads and targets since they | |
| // have an intrinsic numbering supplied by the Thread object (the index into | |
| // the backtrace). If there are no frames on the thread, the return value | |
| // will be 0 (so the return value can't be blindly indexed into the frames | |
| // list). | |
| void SetActiveFrameForThread(const Frame* frame); | |
| void SetActiveFrameIdForThread(const Thread* thread, int id); | |
| int GetActiveFrameIdForThread(const Thread* thread) const; | |
| // Sets the active breakpoint. Can be null/0 if there is no active breakpoint | |
| // (set to null to clear). | |
| void SetActiveBreakpoint(const Breakpoint* breakpoint); | |
| int GetActiveBreakpointId() const; | |
| Breakpoint* GetActiveBreakpoint() const; | |
| // Sets the active filter. Can be null/0 if there is no active filter (set to | |
| // null to clear). | |
| void SetActiveFilter(const Filter* filter); | |
| int GetActiveFilterId() const; | |
| Filter* GetActiveFilter() const; | |
| // Each thread maintains a source affinity which was the last command that | |
| // implies either source code or disassembly viewing. This is used to control | |
| // what gets displayed by default for the next stop of that thread. Defaults | |
| // to kSource for new and unknown threads. Setting SourceAffinity::kNone does | |
| // nothing so calling code can unconditionally call for all commands. | |
| SourceAffinity GetSourceAffinityForThread(const Thread* thread) const; | |
| void SetSourceAffinityForThread(const Thread* thread, SourceAffinity source_affinity); | |
| // Schedules evaluation and subsequent display of the "display" expressions. These are the things | |
| // printed out for every thread stop. | |
| void ScheduleDisplayExpressions(Thread* thread) const; | |
| // Fills the current effective process, thread, etc. into the given Command | |
| // structure based on what the command specifies and the current context. | |
| // Returns an error if any of the referenced IDs are invalid. | |
| Err FillOutCommand(Command* cmd) const; | |
| // Returns the PrettyStackManager for this session. | |
| // | |
| // This object is currently sitting on the ConsoleContext as a convenient place to hold the | |
| // singleton for the console frontend. Depending on how this evolve, it might be better to have | |
| // the client layer manage this object. | |
| const fxl::RefPtr<PrettyStackManager>& pretty_stack_manager() { return pretty_stack_manager_; } | |
| /// Returns the TestFailureStackMatcher for this session. | |
| const fxl::RefPtr<TestFailureStackMatcher>& test_failure_stack_matcher() { | |
| return test_failure_stack_matcher_; | |
| } | |
| void InitConsoleMode(); | |
| std::string GetConsoleMode(); | |
| std::string GetEmbeddedModeContextOrDefault(std::optional<debug_ipc::ExceptionType> type); | |
| void SetConsoleMode(std::string mode); | |
| // If the current console mode is EmbeddedInteractive, return to Embedded mode (i.e. disable input | |
| // and hide zxdb output). If the console mode is Shell, do nothing. | |
| // | |
| // |process| may be a process that should be filtered from all running processes the frontend | |
| // knows about. This is because the notification we receive for a process that has been | |
| // detached/exited/killed/etc comes _before_ the process object is actually destructed. If null, | |
| // then the presence of any running process will keep interactive mode. | |
| void MaybeReturnToEmbeddedMode(Process* process); | |
| // SessionObserver implementation: | |
| void HandleNotification(NotificationType, const std::string&) override; | |
| void HandlePreviousConnectedProcesses(const std::vector<debug_ipc::ProcessRecord>&) override; | |
| void HandleProcessesInLimbo(const std::vector<debug_ipc::ProcessRecord>&) override; | |
| // SystemObserver implementation: | |
| void DidCreateBreakpoint(Breakpoint* breakpoint) override; | |
| void WillDestroyBreakpoint(Breakpoint* breakpoint) override; | |
| void DidCreateFilter(Filter* filter) override; | |
| void WillDestroyFilter(Filter* filter) override; | |
| void DidCreateSymbolServer(SymbolServer* symbol_server) override; | |
| // TargetObserver implementation: | |
| void DidCreateTarget(Target* target) override; | |
| void WillDestroyTarget(Target* target) override; | |
| // ProcessObserver implementation: | |
| void DidCreateProcess(Process* process, uint64_t timestamp) override; | |
| void WillDestroyProcess(Process* process, DestroyReason reason, int exit_code, | |
| uint64_t timestamp) override; | |
| void WillLoadModuleSymbols(Process* process, int num_modules) override; | |
| void DidLoadAllModuleSymbols(Process* process) override; | |
| void OnSymbolLoadFailure(Process* process, const Err& err) override; | |
| void WillAutomaticallyContinue(debug_ipc::ResumeRequest::How how, const StopInfo& info) override; | |
| // ThreadObserver implementation: | |
| void DidCreateThread(Thread* thread) override; | |
| void WillDestroyThread(Thread* thread) override; | |
| void OnThreadStopped(Thread* thread, const StopInfo& info) override; | |
| void DidUpdateStackFrames(Thread* thread) override; | |
| // DownloadObserver implementation: | |
| void OnDownloadsStarted() override; | |
| void OnDownloadsStopped(size_t success, size_t fail) override; | |
| // BreakpointObserver implementation. | |
| void OnBreakpointMatched(Breakpoint* breakpoint, bool user_requested) override; | |
| void OnBreakpointUpdateFailure(Breakpoint* breakpoint, const Err& err) override; | |
| void OnBreakpointImplicitUpdate(Breakpoint* breakpoint, BreakpointObserver::What what) override; | |
| // ComponentObserver implementation. | |
| void OnComponentStarted(const std::string& moniker, const std::string& url) override; | |
| void OnComponentExited(const std::string& moniker, const std::string& url) override; | |
| // SettingStoreObserver implementation. | |
| void OnSettingChanged(const SettingStore&, const std::string& setting_name) override; | |
| private: | |
| struct ThreadRecord { | |
| Thread* thread = nullptr; | |
| // This isn't necessarily valid since the frames could have been changed | |
| // out from under us. Be sure to range check before use. | |
| int active_frame_id = 0; | |
| // Default to showing source code for thread stops. | |
| SourceAffinity source_affinity = SourceAffinity::kSource; | |
| }; | |
| struct TargetRecord { | |
| int target_id = 0; | |
| Target* target = nullptr; | |
| int next_thread_id = 1; | |
| // The active ID will be 0 when there is no active thread (the case when | |
| // the process is not running). | |
| int active_thread_id = 0; | |
| std::map<int, ThreadRecord> id_to_thread; | |
| std::map<const Thread*, int> thread_to_id; | |
| }; | |
| // Returns the record for the given target, or null (+ assertion) if not | |
| // found. These pointers are not stable across target list changes. | |
| TargetRecord* GetTargetRecord(int target_id); | |
| const TargetRecord* GetTargetRecord(int target_id) const; | |
| TargetRecord* GetTargetRecord(const Target* target); | |
| const TargetRecord* GetTargetRecord(const Target* target) const; | |
| ThreadRecord* GetThreadRecord(const Thread* thread); | |
| const ThreadRecord* GetThreadRecord(const Thread* thread) const; | |
| // Backends for parts of FillOutCommand. | |
| // | |
| // For the variants that take an input pointer, the pointer may be null if | |
| // there is nothing of that type. | |
| // | |
| // For the variants that take an output pointer, the pointer will be stored | |
| // if the corresponding item (target/thread) is found, otherwise it will be | |
| // unchanged. | |
| Err FillOutTarget(Command* cmd, TargetRecord const** out_target_record) const; | |
| Err FillOutThread(Command* cmd, const TargetRecord* target_record, | |
| ThreadRecord const** out_thread_record) const; | |
| Err FillOutFrame(Command* cmd, const ThreadRecord* thread_record) const; | |
| Err FillOutBreakpoint(Command* cmd) const; | |
| Err FillOutFilter(Command* cmd) const; | |
| Err FillOutSymbolServer(Command* cmd) const; | |
| // When a thread stops on a breakpoint, sets that breakpoint to be the default. | |
| void SetActiveBreakpointForStop(const StopInfo& info); | |
| Session* const session_; | |
| // The ID from a user perspective maps to a Target/Process pair. | |
| std::map<int, TargetRecord> id_to_target_; | |
| std::map<const Target*, int> target_to_id_; | |
| int next_target_id_ = 1; | |
| std::map<int, Breakpoint*> id_to_breakpoint_; | |
| std::map<const Breakpoint*, int> breakpoint_to_id_; | |
| int next_breakpoint_id_ = 1; | |
| std::map<int, Filter*> id_to_filter_; | |
| std::map<const Filter*, int> filter_to_id_; | |
| int next_filter_id_ = 1; | |
| std::map<int, SymbolServer*> id_to_symbol_server_; | |
| std::map<const SymbolServer*, int> symbol_server_to_id_; | |
| int next_symbol_server_id_ = 1; | |
| int active_target_id_ = 0; | |
| int active_breakpoint_id_ = 0; | |
| int active_filter_id_ = 0; | |
| int active_symbol_server_id_ = 0; | |
| // A separate thread that handles the console UI while symbols are being loaded and indexed. A new | |
| // thread is spawned that will take more than a few seconds to process. Once | |
| // |OnAllModuleSymbolsLoaded| is called from the Process object, the thread is cleaned up and | |
| // console is given back to the user. | |
| std::unique_ptr<std::thread> symbol_loading_printer_thread_; | |
| std::timed_mutex lock_timer_; | |
| fxl::RefPtr<PrettyStackManager> pretty_stack_manager_; | |
| fxl::RefPtr<TestFailureStackMatcher> test_failure_stack_matcher_; | |
| }; | |
| } // namespace zxdb | |
| #endif // SRC_DEVELOPER_DEBUG_ZXDB_CONSOLE_CONSOLE_CONTEXT_H_ |