| // 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_DEBUG_AGENT_DEBUGGED_PROCESS_H_ | |
| #define SRC_DEVELOPER_DEBUG_DEBUG_AGENT_DEBUGGED_PROCESS_H_ | |
| #include <map> | |
| #include <memory> | |
| #include <vector> | |
| #include <fbl/unique_fd.h> | |
| #include "src/developer/debug/debug_agent/debugged_thread.h" | |
| #include "src/developer/debug/debug_agent/module_list.h" | |
| #include "src/developer/debug/debug_agent/process_handle.h" | |
| #include "src/developer/debug/debug_agent/process_handle_observer.h" | |
| #include "src/developer/debug/debug_agent/stdio_handles.h" | |
| #include "src/developer/debug/ipc/protocol.h" | |
| #include "src/developer/debug/shared/message_loop.h" | |
| #include "src/lib/fxl/macros.h" | |
| namespace debug_agent { | |
| class Breakpoint; | |
| class DebugAgent; | |
| class HardwareBreakpoint; | |
| class ProcessBreakpoint; | |
| class ProcessWatchpoint; | |
| class SoftwareBreakpoint; | |
| class Watchpoint; | |
| struct DebuggedProcessCreateInfo { | |
| explicit DebuggedProcessCreateInfo(std::unique_ptr<ProcessHandle> handle); | |
| // Required. | |
| std::unique_ptr<ProcessHandle> handle; | |
| // Optional. | |
| StdioHandles stdio; | |
| // Whether this process was obtained via a process limbo. | |
| // This is relevant when attempting to kill the process, as handles obtained via the limbo do not | |
| // have the ZX_RIGHT_DESTROY right. The way to "kill" them is to re send them to the limbo and | |
| // then release it from it. | |
| bool from_limbo = false; | |
| // See documentation in AttachConfig. | |
| debug_ipc::AttachConfig::Priority priority = debug_ipc::AttachConfig::Priority::kStrong; | |
| }; | |
| class DebuggedProcess : public ProcessHandleObserver { | |
| public: | |
| using WatchpointMap = | |
| std::map<debug::AddressRange, std::unique_ptr<Watchpoint>, debug::AddressRangeBeginCmp>; | |
| // Caller must call Init after construction. | |
| explicit DebuggedProcess(DebugAgent*); | |
| virtual ~DebuggedProcess(); | |
| zx_koid_t koid() const { return process_handle_->GetKoid(); } | |
| DebugAgent* debug_agent() const { return debug_agent_; } | |
| const ProcessHandle& process_handle() const { return *process_handle_; } | |
| ProcessHandle& process_handle() { return *process_handle_; } | |
| // TODO(brettw) remove this and have all callers use thread_handle(). | |
| NativeProcessHandle& handle() { return process_handle_->GetNativeHandle(); } | |
| const NativeProcessHandle& handle() const { return process_handle_->GetNativeHandle(); } | |
| const ModuleList& module_list() const { return module_list_; } | |
| // The object is not usable if |Init| is not called or fails. | |
| debug::Status Init(DebuggedProcessCreateInfo create_info); | |
| // After a process has been initialized, but hasn't bound this object to the process' exception | |
| // channel (i.e. initialized with |deferred_attach| set to true), use this to attach. | |
| debug::Status AttachNow(); | |
| bool IsAttached() const; | |
| void SetStdout(OwnedStdioHandle handle); | |
| void SetStderr(OwnedStdioHandle handle); | |
| // IPC handlers. | |
| void OnResume(const debug_ipc::ResumeRequest& request); | |
| void OnReadMemory(const debug_ipc::ReadMemoryRequest& request, debug_ipc::ReadMemoryReply* reply); | |
| void OnKill(const debug_ipc::KillRequest& request, debug_ipc::KillReply* reply); | |
| void OnAddressSpace(const debug_ipc::AddressSpaceRequest& request, | |
| debug_ipc::AddressSpaceReply* reply); | |
| void OnModules(debug_ipc::ModulesReply* reply); | |
| void OnWriteMemory(const debug_ipc::WriteMemoryRequest& request, | |
| debug_ipc::WriteMemoryReply* reply); | |
| void OnLoadInfoHandleTable(const debug_ipc::LoadInfoHandleTableRequest& request, | |
| debug_ipc::LoadInfoHandleTableReply* reply); | |
| void OnSaveMinidump(const debug_ipc::SaveMinidumpRequest& request, | |
| debug_ipc::SaveMinidumpReply* reply); | |
| void InjectThreadForTest(std::unique_ptr<DebuggedThread> thread); | |
| // Synchronously pauses all threads in the process from the perspective of the client. This issues | |
| // ClientSuspend() on all threads (see that for more on what "client" means). | |
| // | |
| // The except_thread can be passed which indicates a thread to ship when suspending. This is for | |
| // certain operations that want to do something to all other threads. | |
| // | |
| // The affected threads are returned. If a thread is already in a client suspend, it will not be | |
| // affected and it will not be returned in the result. | |
| std::vector<debug_ipc::ProcessThreadId> ClientSuspendAllThreads( | |
| zx_koid_t except_thread = ZX_KOID_INVALID); | |
| // Returns the thread or null if there is no known thread for this koid. | |
| DebuggedThread* GetThread(zx_koid_t thread_koid) const; | |
| std::vector<DebuggedThread*> GetThreads() const; | |
| // Populates the thread map with the current threads for this process. | |
| // This function does not notify the client of thread start, but rather updates the internal | |
| // thread state according to the underlying zircon truth. | |
| void PopulateCurrentThreads(); | |
| // Returns the information for all current threads. This gets minimal stacks. | |
| std::vector<debug_ipc::ThreadRecord> GetThreadRecords() const; | |
| // Checks if a breakpoint at the given address is the loader's internal one. | |
| // If it is, handles it and returns true. If it's not, does nothing and returns false. | |
| bool HandleLoaderBreakpoint(uint64_t address); | |
| // Suspend all thread and send the module list. This does not refresh the module list. | |
| // The client expects the threads to be suspended so it can resolve breakpoints and resume them. | |
| virtual void SuspendAndSendModules(); | |
| // Looks for breakpoints at the given address. Null if no breakpoints are at that address. | |
| virtual SoftwareBreakpoint* FindSoftwareBreakpoint(uint64_t address) const; | |
| virtual HardwareBreakpoint* FindHardwareBreakpoint(uint64_t address) const; | |
| virtual Watchpoint* FindWatchpoint(const debug::AddressRange&) const; | |
| debug::Status RegisterBreakpoint(Breakpoint* bp, uint64_t address); | |
| void UnregisterBreakpoint(Breakpoint* bp, uint64_t address); | |
| debug::Status RegisterWatchpoint(Breakpoint* bp, const debug::AddressRange& range); | |
| void UnregisterWatchpoint(Breakpoint* bp, const debug::AddressRange& range); | |
| // Each time a thread attempts to step over a breakpoint, the breakpoint will enqueue itself and | |
| // the thread into the step over queue. The step over queue is used so that there is only one | |
| // breakpoint being stepped over at a time. | |
| // | |
| // Enqueuing the breakpoint does not mean that the step over begins immediately, but rather the | |
| // process will call the |ExecuteStepOver| method on the breakpoint once its turn has come up in | |
| // the queue. | |
| // | |
| // In some error cases this might happen twice for the same thread. For example, if there is an | |
| // error clearing the breakpoint instruction, attempting to clear it and then single-step it will | |
| // just hit the same breakpoint again and the "step over" will never complete. | |
| // | |
| // If this happens the original "step over" request will be silently dropped. Otherwise, the | |
| // step queue will be recursively waiting for itself and can never continue. All other breakpoints | |
| // will still be waiting behind this failed step, but at least it could theoretically continue if | |
| // the breakpoint clearing works in a future try. | |
| void EnqueueStepOver(ProcessBreakpoint* process_breakpoint, DebuggedThread* thread); | |
| // Called by the currently stepping over breakpoint when it's done. It will execute the next | |
| // enqueued breakpoint. If there are no more breakpoints enqueued, this will let all the | |
| // breakpoints know so that it can resume the stepped over threads. | |
| void OnBreakpointFinishedSteppingOver(); | |
| // This function will gracefully detach from the underlying zircon process. | |
| // Detaching correctly requires several steps: | |
| // | |
| // 1. Remove the installed breakpoints. | |
| // | |
| // 2. Resume threads from the exception. Only threads that are stopped on an | |
| // exception should be resumed. This is because otherwise zircon will treat | |
| // this exception as unhandled and will bubble up the exception upwards, | |
| // probably resulting in a crash. | |
| // | |
| // 3. Unbind the exception port. | |
| void DetachFromProcess(); | |
| // Queue of breakpoints that are currently being stepped over. | |
| // | |
| // As stepping over requires suspending all the threads, doing multiple at a time has a fair | |
| // chance of introducing deadlocks. We use this queue to serialize the stepping over, so only | |
| // one process breakpoint is being stepped over at a time. | |
| struct StepOverTicket { | |
| fxl::WeakPtr<ProcessBreakpoint> process_breakpoint; | |
| fxl::WeakPtr<DebuggedThread> thread; | |
| bool is_valid() const { return !!process_breakpoint && !!thread; } | |
| }; | |
| const std::deque<StepOverTicket>& step_over_queue() const { return step_over_queue_; } | |
| const std::map<uint64_t, std::unique_ptr<SoftwareBreakpoint>>& software_breakpoints() const { | |
| return software_breakpoints_; | |
| } | |
| const std::map<uint64_t, std::unique_ptr<HardwareBreakpoint>>& hardware_breakpoints() const { | |
| return hardware_breakpoints_; | |
| } | |
| const WatchpointMap& watchpoints() const { return watchpoints_; } | |
| bool from_limbo() const { return from_limbo_; } | |
| protected: | |
| // ProcessHandleObserver implementation. | |
| // | |
| // These are protected so that they can be called from MockProcess. In normal operation these are | |
| // called by the ProcessHandle implementation, but we don't want to have to construct fake | |
| // instances of platform specific exception objects when we can just use our own abstractions | |
| // here. This provides a simpler interface for testing the higher level objects without exposing | |
| // the API outside of tests. | |
| void OnProcessTerminated() override; | |
| void OnThreadStarting(std::unique_ptr<ExceptionHandle> exception) override; | |
| void OnThreadExiting(std::unique_ptr<ExceptionHandle> exception) override; | |
| void OnException(std::unique_ptr<ExceptionHandle> exception) override; | |
| void OnProcessStarting(std::unique_ptr<ProcessHandle> new_process_handle) override; | |
| private: | |
| void OnStdout(bool close); | |
| void OnStderr(bool close); | |
| // Sends a IO notification over to the client. | |
| void SendIO(debug_ipc::NotifyIO::Type, const std::vector<char>& data); | |
| // Deletes any elements in the step over queue that are no longer valid. This happens when either | |
| // the thread or the breakpoint went away while the ticket was waiting within the queue. | |
| // | |
| // If the |thread| parameter is non-null, ALL requests from that thread will be deleted in | |
| // addition to the normal pruning behavior. | |
| void PruneStepOverQueue(DebuggedThread* optional_thread); | |
| debug::Status RegisterSoftwareBreakpoint(Breakpoint* bp, uint64_t address); | |
| void UnregisterSoftwareBreakpoint(Breakpoint* bp, uint64_t address); | |
| debug::Status RegisterHardwareBreakpoint(Breakpoint* bp, uint64_t address); | |
| void UnregisterHardwareBreakpoint(Breakpoint* bp, uint64_t address); | |
| DebugAgent* debug_agent_ = nullptr; // Non-owning. | |
| std::unique_ptr<ProcessHandle> process_handle_; | |
| // Current modules loaded in the process. | |
| ModuleList module_list_; | |
| std::map<zx_koid_t, std::unique_ptr<DebuggedThread>> threads_; | |
| // Indicates that new threads, if discovered, should be suspended. | |
| // New thread could be spawned even when all known threads are suspended. | |
| bool suspend_new_threads_ = false; | |
| // Maps addresses to the ProcessBreakpoint at a location. | |
| std::map<uint64_t, std::unique_ptr<SoftwareBreakpoint>> software_breakpoints_; | |
| std::map<uint64_t, std::unique_ptr<HardwareBreakpoint>> hardware_breakpoints_; | |
| WatchpointMap watchpoints_; | |
| std::deque<StepOverTicket> step_over_queue_; | |
| // Non-null only if the corresponding stream is hooked up. | |
| std::unique_ptr<BufferedStdioHandle> stdout_; | |
| std::unique_ptr<BufferedStdioHandle> stderr_; | |
| // Whether this process was obtained from limbo or not. The agent will check this information | |
| // when it tries to kill this process in order to determine whether the ZX_ERR_BAD_ACCESS is | |
| // expected (limbo handles do not have ZX_RIGHT_DESTROY right) or it is an actual error. | |
| bool from_limbo_ = false; | |
| // Whether or not we are currently "weakly" attached to this process. The process will become | |
| // "strongly" attached when there is a non-loader breakpoint or an exception. Note the client we | |
| // are talking to will never be able to install its own breakpoints until symbols are loaded, so | |
| // if no external event (i.e. an exception from the kernel or a manually installed breakpoint) | |
| // occurs then the process will exit normally. | |
| bool is_weakly_attached_ = false; | |
| #if defined(__linux__) | |
| // On Linux we need to manually set a breakpoint in the loader to observe module loads (Fuchsia | |
| // uses a process property to signal the loader to do it for us). | |
| uint64_t dl_debug_addr_ = 0; | |
| std::unique_ptr<Breakpoint> loader_breakpoint_; | |
| #endif | |
| FXL_DISALLOW_COPY_AND_ASSIGN(DebuggedProcess); | |
| }; | |
| } // namespace debug_agent | |
| #endif // SRC_DEVELOPER_DEBUG_DEBUG_AGENT_DEBUGGED_PROCESS_H_ |