| // 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_IPC_PROTOCOL_H_ |
| #define SRC_DEVELOPER_DEBUG_IPC_PROTOCOL_H_ |
| |
| #include "src/developer/debug/ipc/records.h" |
| |
| namespace debug_ipc { |
| |
| // As defined in zircon/types.h |
| using zx_status_t = int32_t; |
| |
| constexpr uint32_t kProtocolVersion = 31; |
| |
| enum class Arch : uint32_t { kUnknown = 0, kX64, kArm64 }; |
| |
| #pragma pack(push, 8) |
| |
| // A message consists of a MsgHeader followed by a serialized version of |
| // whatever struct is associated with that message type. Use the MessageWriter |
| // class to build this up, which will reserve room for the header and allows |
| // the structs to be appended, possibly dynamically. |
| struct MsgHeader { |
| enum class Type : uint32_t { |
| kNone = 0, |
| kHello, |
| |
| kAddOrChangeBreakpoint, |
| kAddressSpace, |
| kConfigAgent, |
| kAttach, |
| kDetach, |
| kJobFilter, |
| kKill, |
| kLaunch, |
| kModules, |
| kPause, |
| kProcessStatus, |
| kProcessTree, |
| kQuitAgent, |
| kReadMemory, |
| kReadRegisters, |
| kWriteRegisters, |
| kRemoveBreakpoint, |
| kResume, |
| kStatus, |
| kSysInfo, |
| kThreadStatus, |
| kThreads, |
| kWriteMemory, |
| kLoadInfoHandleTable, |
| kUpdateGlobalSettings, |
| |
| // The "notify" messages are sent unrequested from the agent to the client. |
| kNotifyException, |
| kNotifyIO, |
| kNotifyModules, |
| kNotifyProcessExiting, |
| kNotifyProcessStarting, |
| kNotifyThreadExiting, |
| kNotifyThreadStarting, |
| |
| kNumMessages |
| }; |
| static const char* TypeToString(Type); |
| |
| MsgHeader() = default; |
| explicit MsgHeader(Type t) : type(t) {} |
| |
| uint32_t size = 0; // Size includes this header. |
| Type type = Type::kNone; |
| |
| // The transaction ID is assigned by the sender of a request, and is echoed |
| // in the reply so the transaction can be easily correlated. |
| // |
| // Notification messages (sent unsolicited from the agent to the client) have |
| // a 0 transaction ID. |
| uint32_t transaction_id = 0; |
| |
| static constexpr uint32_t kSerializedHeaderSize = sizeof(uint32_t) * 3; |
| }; |
| |
| struct HelloRequest {}; |
| struct HelloReply { |
| // Stream signature to make sure we're talking to the right service. |
| // This number is ASCII for "zxdbIPC>". |
| static constexpr uint64_t kStreamSignature = 0x7a7864624950433e; |
| |
| uint64_t signature = kStreamSignature; |
| uint32_t version = kProtocolVersion; |
| Arch arch = Arch::kUnknown; |
| }; |
| |
| enum class InferiorType : uint32_t { |
| kBinary, |
| kComponent, |
| kLast, |
| }; |
| const char* InferiorTypeToString(InferiorType); |
| |
| // Status ------------------------------------------------------------------------------------------ |
| // |
| // Asks for a present view of the system. |
| |
| struct StatusRequest {}; |
| struct StatusReply { |
| // All the processes that the debug agent is currently attached. |
| std::vector<ProcessRecord> processes; |
| |
| // List of processes waiting on limbo. Limbo are the processes that triggered an exception with |
| // no exception handler attached. If the system is configured to keep those around in order to |
| // wait for a debugger, it is said that those processes are in "limbo". |
| std::vector<ProcessRecord> limbo; |
| }; |
| |
| // Triggers the system to send the notifications (process starting, modules) for an already |
| // attached process. |
| struct ProcessStatusRequest { |
| uint64_t process_koid = 0; |
| }; |
| |
| struct ProcessStatusReply { |
| // Returns ZX_OK if the process exists and the agent was able to retrieve the data. |
| uint32_t status = 0; |
| }; |
| |
| struct LaunchRequest { |
| // TODO(fxbug.dev/5796): zxdb should be able to recognize when something is a binary |
| // or a component. Replying with the type is probably OK as it |
| // makes the client handling a bit easier. |
| InferiorType inferior_type = InferiorType::kLast; |
| |
| // argv[0] is the app to launch. |
| std::vector<std::string> argv; |
| }; |
| struct LaunchReply { |
| // The client needs to react differently depending on whether we started a |
| // process or a component. |
| InferiorType inferior_type; |
| |
| // zx_status_t value from launch, ZX_OK on success. |
| zx_status_t status = 0; |
| |
| // These fields are mutually exclusive. If InferiorType is process, then |
| // process_id != 0 and component_id == 0. If it's component, it's the other |
| // way around. |
| uint64_t process_id = 0; |
| uint64_t component_id = 0; |
| |
| std::string process_name; |
| }; |
| |
| struct KillRequest { |
| uint64_t process_koid = 0; |
| }; |
| struct KillReply { |
| zx_status_t status = 0; |
| }; |
| |
| enum class TaskType : uint32_t { kProcess = 0, kJob, kSystemRoot, kComponentRoot, kLast }; |
| const char* TaskTypeToString(TaskType); |
| |
| // The debug agent will follow a successful AttachReply with notifications for |
| // all threads currently existing in the attached process. |
| struct AttachRequest { |
| TaskType type = TaskType::kProcess; |
| uint64_t koid = 0; // Unused for ComponentRoot. |
| }; |
| |
| struct AttachReply { |
| uint64_t koid = 0; |
| zx_status_t status = 0; // zx_status_t value from attaching. ZX_OK on success. |
| std::string name; |
| }; |
| |
| struct DetachRequest { |
| TaskType type = TaskType::kProcess; |
| uint64_t koid = 0; |
| }; |
| struct DetachReply { |
| zx_status_t status = 0; |
| }; |
| |
| struct PauseRequest { |
| // If 0, all threads of all debugged processes will be paused. |
| uint64_t process_koid = 0; |
| |
| // If 0, all threads in the given process will be paused. |
| uint64_t thread_koid = 0; |
| }; |
| // The backend should make a best effort to ensure the requested threads are |
| // actually stopped before sending the reply. |
| struct PauseReply { |
| // The updated thead state for all affected threads. |
| std::vector<ThreadRecord> threads; |
| }; |
| |
| struct QuitAgentRequest {}; |
| struct QuitAgentReply {}; |
| |
| struct ResumeRequest { |
| enum class How : uint32_t { |
| kResolveAndContinue = 0, // Marks the exception as handled and continues executions. |
| kForwardAndContinue, // Marks the exception as second-chance and continues executions. |
| kStepInstruction, // Step one machine instruction. |
| kStepInRange, // Step until control exits an address range. |
| |
| kLast // Not a real state, used for validation. |
| }; |
| |
| // Whether a give resume mode steps. |
| static bool MakesStep(const How how) { |
| return (how == How::kStepInstruction || how == How::kStepInRange); |
| } |
| static const char* HowToString(How); |
| |
| // If 0, all threads of all debugged processes will be continued. |
| uint64_t process_koid = 0; |
| |
| // If empty, all threads in the given process will be continued. If nonempty, |
| // the threads with listed koids will be resumed. kStepInRange may only be |
| // used with a single thread. |
| std::vector<uint64_t> thread_koids; |
| |
| How how = How::kResolveAndContinue; |
| |
| // When how == kStepInRange, these variables define the address range to |
| // step in. As long as the instruction pointer is inside |
| // [range_begin, range_end), execution will continue. |
| uint64_t range_begin = 0; |
| uint64_t range_end = 0; |
| }; |
| struct ResumeReply {}; |
| |
| struct ProcessTreeRequest {}; |
| struct ProcessTreeReply { |
| ProcessTreeRecord root; |
| }; |
| |
| struct ThreadsRequest { |
| uint64_t process_koid = 0; |
| }; |
| struct ThreadsReply { |
| // If there is no such process, the threads array will be empty. |
| std::vector<ThreadRecord> threads; |
| }; |
| |
| struct ReadMemoryRequest { |
| uint64_t process_koid = 0; |
| uint64_t address = 0; |
| uint32_t size = 0; |
| }; |
| struct ReadMemoryReply { |
| std::vector<MemoryBlock> blocks; |
| }; |
| |
| struct AddOrChangeBreakpointRequest { |
| BreakpointSettings breakpoint; |
| }; |
| struct AddOrChangeBreakpointReply { |
| // A variety of race conditions could cause a breakpoint modification or |
| // set to fail. For example, updating or setting a breakpoint could race |
| // with the library containing that code unloading. |
| // |
| // The update or set will always apply the breakpoint to any contexts that |
| // it can apply to (if there are multiple locations, we don't want to |
| // remove them all just because one failed). Therefore, you can't |
| // definitively say the breakpoint is invalid just because it has a failure |
| // code here. If necessary, we can add more information in the failure. |
| zx_status_t status = 0; |
| }; |
| |
| struct RemoveBreakpointRequest { |
| uint32_t breakpoint_id = 0; |
| }; |
| struct RemoveBreakpointReply {}; |
| |
| struct SysInfoRequest {}; |
| struct SysInfoReply { |
| std::string version; |
| uint32_t num_cpus; |
| uint32_t memory_mb; |
| uint32_t hw_breakpoint_count; |
| uint32_t hw_watchpoint_count; |
| }; |
| |
| // The thread state request asks for the current thread status with a full |
| // backtrace if it is suspended. If the thread with the given KOID doesn't |
| // exist, the ThreadRecord will report a "kDead" status. |
| struct ThreadStatusRequest { |
| uint64_t process_koid = 0; |
| uint32_t thread_koid = 0; |
| }; |
| struct ThreadStatusReply { |
| ThreadRecord record; |
| }; |
| |
| struct AddressSpaceRequest { |
| uint64_t process_koid = 0; |
| // if non-zero |address| indicates to return only the regions |
| // that contain it. |
| uint64_t address = 0; |
| }; |
| |
| struct AddressSpaceReply { |
| std::vector<AddressRegion> map; |
| }; |
| |
| struct ModulesRequest { |
| uint64_t process_koid = 0; |
| }; |
| struct ModulesReply { |
| std::vector<Module> modules; |
| }; |
| |
| // Request to set filter. |
| struct JobFilterRequest { |
| uint64_t job_koid = 0; |
| |
| // Empty strings will match all processes. |
| std::vector<std::string> filters; |
| }; |
| |
| struct JobFilterReply { |
| zx_status_t status = 0; // zx_status for filter request |
| |
| // List of koids for currently running processes that match any of the filters. |
| // Guaranteed that each koid is unique. |
| std::vector<uint64_t> matched_processes; |
| }; |
| |
| struct WriteMemoryRequest { |
| uint64_t process_koid = 0; |
| uint64_t address = 0; |
| std::vector<uint8_t> data; |
| }; |
| |
| struct WriteMemoryReply { |
| zx_status_t status = 0; |
| }; |
| |
| struct LoadInfoHandleTableRequest { |
| uint64_t process_koid = 0; |
| }; |
| struct LoadInfoHandleTableReply { |
| zx_status_t status = 0; |
| std::vector<InfoHandle> handles; |
| }; |
| |
| struct UpdateGlobalSettingsRequest { |
| // Updates how the default strategy for handling a particular exception type. |
| struct UpdateExceptionStrategy { |
| ExceptionType type = ExceptionType::kNone; |
| ExceptionStrategy value = ExceptionStrategy::kNone; |
| }; |
| |
| std::vector<UpdateExceptionStrategy> exception_strategies; |
| }; |
| |
| struct UpdateGlobalSettingsReply { |
| zx_status_t status = 0; |
| }; |
| |
| // ReadRegisters --------------------------------------------------------------- |
| |
| struct ReadRegistersRequest { |
| uint64_t process_koid = 0; |
| uint64_t thread_koid = 0; |
| // What categories do we want to receive data from. |
| std::vector<RegisterCategory> categories; |
| }; |
| |
| struct ReadRegistersReply { |
| std::vector<Register> registers; |
| }; |
| |
| // WriteRegisters -------------------------------------------------------------- |
| |
| struct WriteRegistersRequest { |
| uint64_t process_koid = 0; |
| uint64_t thread_koid = 0; |
| std::vector<Register> registers; |
| }; |
| |
| struct WriteRegistersReply { |
| zx_status_t status = 0; |
| |
| // The latest registers from all affected categories after the write. |
| // |
| // This allows clients to validate that the change actually took effect. All known registers |
| // from all categories changed by the write request will be sent. |
| std::vector<Register> registers; |
| }; |
| |
| // Agent Config ---------------------------------------------------------------- |
| // |
| // The client sends a list of configurations and will receive a status result |
| // for each of them in order. |
| |
| struct ConfigAgentRequest { |
| std::vector<ConfigAction> actions; |
| }; |
| |
| struct ConfigAgentReply { |
| std::vector<zx_status_t> results; |
| }; |
| |
| // Notifications --------------------------------------------------------------- |
| |
| // Notify that a new process was created in debugged job. |
| |
| struct NotifyProcessStarting { |
| enum class Type : uint32_t { |
| kNormal, // Normal process startup. |
| kLimbo, // Process entered the limbo. See debug_agent/limbo_provider.h. |
| |
| kLast, // Not valid, for verification purposes. |
| }; |
| static const char* TypeToString(Type); |
| |
| Type type = Type::kNormal; |
| |
| uint64_t koid = 0; |
| // When components are launched from the debugger, they look like normal |
| // processes starting. The debug agent sets an id to them so the debugger can |
| // detect them and start them as they would normal processes. |
| // |
| // 0 means non set. |
| uint32_t component_id = 0; |
| std::string name = ""; |
| }; |
| |
| // Data for process destroyed messages (process created messages are in |
| // response to launch commands so is just the reply to that message). |
| struct NotifyProcessExiting { |
| uint64_t process_koid = 0; |
| int64_t return_code = 0; |
| }; |
| |
| // Data for thread created and destroyed messages. |
| struct NotifyThread { |
| ThreadRecord record; |
| }; |
| |
| // Data passed for exceptions. |
| struct NotifyException { |
| // Holds the state and a minimal stack (up to 2 frames) of the thread at the |
| // moment of notification. |
| ThreadRecord thread; |
| |
| ExceptionType type = ExceptionType::kNone; |
| |
| ExceptionRecord exception; |
| |
| // When the stop was caused by hitting a breakpoint, this vector will contain |
| // the post-hit stats of every hit breakpoint (since there can be more than |
| // one breakpoint at any given address). |
| // |
| // Be sure to check should_delete on each of these and update local state as |
| // necessary. |
| std::vector<BreakpointStats> hit_breakpoints; |
| |
| // Lists all other threads affected by this exception. Breakpoints can indicate that other threads |
| // in the same process or all attached processes should be stopped when the breakpoint is hit. |
| // This vector will not contain the thread that was stopped (the "thread" member above), and it |
| // will not contain threads that were already stopped at the time of the exception. |
| std::vector<ThreadRecord> other_affected_threads; |
| }; |
| |
| // Indicates the loaded modules may have changed. The entire list of current |
| // modules is sent every time. |
| struct NotifyModules { |
| uint64_t process_koid = 0; |
| std::vector<Module> modules; |
| |
| // The list of threads in the process stopped automatically as a result of |
| // the module load. The client will want to resume these threads once it has |
| // processed the load. |
| std::vector<uint64_t> stopped_thread_koids; |
| }; |
| |
| struct NotifyIO { |
| static constexpr size_t kMaxDataSize = 64 * 1024; // 64k. |
| |
| enum class Type : uint32_t { |
| kStderr, |
| kStdout, |
| kLast, // Not a real type. |
| }; |
| static const char* TypeToString(Type); |
| |
| uint64_t process_koid = 0; |
| Type type = Type::kLast; |
| |
| // Data will be up most kMaxDataSize bytes. |
| std::string data; |
| |
| // Whether this is a piece of bigger message. |
| // This information can be used by the consumer to change how it will handle |
| // this data. |
| bool more_data_available = false; |
| }; |
| |
| #pragma pack(pop) |
| |
| } // namespace debug_ipc |
| |
| #endif // SRC_DEVELOPER_DEBUG_IPC_PROTOCOL_H_ |