blob: c8d8445564e06f3b5ca96893f231ad913c800750 [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_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;
// Whether the client requested a "weak" attach. If this is set, we ignore the loader breakpoint
// and never proactively send module updates. The client will explicitly request modules when it
// is ready to do more work. This is useful when the client is orchestrating with other tools, and
// may be hidden for some time. "Weak" attaches allow the client to defer loading modules (and not
// blocking the console) until something interesting happened.
bool weak = false;
};
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);
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_; }
private:
// ProcessHandleObserver implementation.
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;
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_