| // 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_RECORDS_H_ |
| #define SRC_DEVELOPER_DEBUG_IPC_RECORDS_H_ |
| |
| #include <stdint.h> |
| |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include "src/developer/debug/ipc/register_desc.h" |
| #include "src/developer/debug/shared/address_range.h" |
| |
| namespace debug_ipc { |
| |
| #pragma pack(push, 8) |
| |
| enum class ExceptionType : uint32_t { |
| // No current exception, used as placeholder or to indicate not set. |
| kNone = 0, |
| |
| // Zircon defines this as a sort of catch-all exception. |
| kGeneral, |
| |
| // The usual band of execution traps. |
| kPageFault, |
| kUndefinedInstruction, |
| kUnalignedAccess, |
| |
| // Indicates the process was killed due to misusing a syscall, e.g. passing a bad handle. |
| kPolicyError, |
| |
| // Synthetic exeptions used by zircon to communicated with the debugger. The debug agent generally |
| // shouldn't pass these on, but we should recognize them at least. |
| kThreadStarting, |
| kThreadExiting, |
| kProcessStarting, |
| |
| // Hardware breakpoints are issues by the CPU via debug registers. |
| kHardwareBreakpoint, |
| |
| // HW exceptions triggered on memory read/write. |
| kWatchpoint, |
| |
| // Single-step completion issued by the CPU. |
| kSingleStep, |
| |
| // Software breakpoint. This will be issued when a breakpoint is hit and when the debugged program |
| // manually issues a breakpoint instruction. |
| kSoftwareBreakpoint, |
| |
| // Indicates this exception is not a real CPU exception but was generated internally for the |
| // purposes of sending a stop notification. The frontend uses this value when the thread didn't |
| // actually do anything, but the should be updated as if it hit an exception. |
| kSynthetic, |
| |
| // For exception codes the debugger doesn't recognize. |
| kUnknown, |
| |
| kLast // Not an actual exception type, for range checking. |
| }; |
| const char* ExceptionTypeToString(ExceptionType); |
| bool IsDebug(ExceptionType); |
| |
| // Exception handling strategy. |
| enum class ExceptionStrategy : uint32_t { |
| // No current exception, used as placeholder or to indicate not set. |
| kNone = 0, |
| |
| // Indicates that the debugger only gets the first chance to handle the |
| // exception, before the thread and process-level handlers. |
| kFirstChance, |
| |
| // Indicates that the debugger also gets a second first chance to handle |
| // the exception after process-level handler. |
| kSecondChance, |
| |
| kLast, // Not an actual exception strategy, for range checking. |
| }; |
| |
| const char* ExceptionStrategyToString(ExceptionStrategy); |
| |
| std::optional<ExceptionStrategy> ToExceptionStrategy(uint32_t raw_value); |
| |
| std::optional<uint32_t> ToRawValue(ExceptionStrategy strategy); |
| |
| struct ExceptionRecord { |
| ExceptionRecord() { memset(&arch, 0, sizeof(Arch)); } |
| |
| // Race conditions or other errors can conspire to mean the exception records are not valid. In |
| // order to differentiate this case from "0" addresses, this flag indicates validity of the "arch" |
| // union. |
| bool valid = false; |
| |
| union Arch { |
| // Exception record for x64. |
| struct { |
| uint64_t vector; |
| uint64_t err_code; |
| uint64_t cr2; |
| } x64; |
| |
| // Exception record for ARM64. |
| struct { |
| uint32_t esr; |
| uint64_t far; |
| } arm64; |
| } arch; |
| |
| ExceptionStrategy strategy = ExceptionStrategy::kNone; |
| }; |
| |
| // Note: see "ps" source: |
| // https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/sys/bin/psutils/ps.c |
| struct ProcessTreeRecord { |
| enum class Type : uint32_t { kJob, kProcess }; |
| |
| Type type = Type::kJob; |
| uint64_t koid = 0; |
| std::string name; |
| |
| std::vector<ProcessTreeRecord> children; |
| }; |
| |
| // Value representing a particular register. |
| struct Register { |
| Register() = default; |
| Register(RegisterID rid, std::vector<uint8_t> d) : id(rid), data(std::move(d)) {} |
| |
| // Constructs from a size and a pointed-to data buffer in machine-endianness. |
| Register(RegisterID rid, size_t byte_size, const void* bytes) : id(rid) { |
| data.resize(byte_size); |
| memcpy(&data[0], bytes, byte_size); |
| } |
| |
| // Constructs a sized value for the current platform. |
| Register(RegisterID rid, uint64_t val) : id(rid) { |
| data.resize(sizeof(val)); |
| memcpy(&data[0], &val, sizeof(val)); |
| } |
| Register(RegisterID rid, uint32_t val) : id(rid) { |
| data.resize(sizeof(val)); |
| memcpy(&data[0], &val, sizeof(val)); |
| } |
| Register(RegisterID rid, uint16_t val) : id(rid) { |
| data.resize(sizeof(val)); |
| memcpy(&data[0], &val, sizeof(val)); |
| } |
| Register(RegisterID rid, uint8_t val) : id(rid) { |
| data.resize(sizeof(val)); |
| memcpy(&data[0], &val, sizeof(val)); |
| } |
| |
| // Retrieves the low up-to-128 bits of the register value as a number. |
| __int128 GetValue() const; |
| |
| // Comparisons (primarily for tests). |
| bool operator==(const Register& other) const { return id == other.id && data == other.data; } |
| bool operator!=(const Register& other) const { return !operator==(other); } |
| |
| RegisterID id = RegisterID::kUnknown; |
| |
| // This data is stored in the architecture's native endianness |
| // (eg. the result of running memcpy over the data). |
| std::vector<uint8_t> data; |
| }; |
| |
| struct StackFrame { |
| StackFrame() = default; |
| StackFrame(uint64_t ip, uint64_t sp, uint64_t cfa = 0, std::vector<Register> r = {}) |
| : ip(ip), sp(sp), cfa(cfa), regs(std::move(r)) {} |
| |
| // Comparisons (primarily for tests). |
| bool operator==(const StackFrame& other) const { |
| return ip == other.ip && sp == other.sp && cfa == other.cfa && regs == other.regs; |
| } |
| bool operator!=(const StackFrame& other) const { return !operator==(other); } |
| |
| // Instruction pointer. |
| uint64_t ip = 0; |
| |
| // Stack pointer. |
| uint64_t sp = 0; |
| |
| // Canonical frame address. This is the stack pointer of the previous |
| // frame at the time of the call. 0 if unknown. |
| uint64_t cfa = 0; |
| |
| // Known general registers for this stack frame. See IsGeneralRegister() for |
| // which registers are counted as "general". |
| // |
| // Every frame should contain the register for the IP and SP for the current |
| // architecture (duplicating the above two fields). |
| std::vector<Register> regs; |
| }; |
| |
| struct ThreadRecord { |
| enum class State : uint32_t { |
| kNew = 0, |
| kRunning, |
| kSuspended, |
| kBlocked, |
| kDying, |
| kDead, |
| kCoreDump, |
| |
| kLast // Not an actual thread state, for range checking. |
| }; |
| static const char* StateToString(State); |
| |
| enum class BlockedReason : uint32_t { |
| kNotBlocked = 0, // Used when State isn't kBlocked. |
| |
| kException, |
| kSleeping, |
| kFutex, |
| kPort, |
| kChannel, |
| kWaitOne, |
| kWaitMany, |
| kInterrupt, |
| kPager, |
| |
| kLast // Not an actual blocked reason, for range checking. |
| }; |
| static const char* BlockedReasonToString(BlockedReason); |
| |
| // Indicates how much of the stack was attempted to be retrieved in this |
| // call. This doesn't indicate how many stack frames were actually retrieved. |
| // For example, there could be no stack frames because they weren't |
| // requested, or there could be no stack frames due to an error. |
| enum class StackAmount : uint32_t { |
| // A backtrace was not attempted. This will always be the case if the |
| // thread is neither suspended nor blocked in an exception. |
| kNone = 0, |
| |
| // The frames vector contains a minimal stack only (if available) which |
| // is defined as the top two frames. This is used when the stack frames |
| // have not been specifically requested since retrieving the full stack |
| // can be slow. The frames can still be less than 2 if there was an error |
| // or if there is only one stack frame. |
| kMinimal, |
| |
| // The frames are the full stack trace (up to some maximum). |
| kFull, |
| |
| kLast // Not an actual state, for range checking. |
| }; |
| |
| uint64_t process_koid = 0; |
| uint64_t thread_koid = 0; |
| std::string name; |
| State state = State::kNew; |
| // Only valid when state is kBlocked. |
| BlockedReason blocked_reason = BlockedReason::kNotBlocked; |
| StackAmount stack_amount = StackAmount::kNone; |
| |
| // The frames of the top of the stack when the thread is in suspended or blocked in an exception |
| // (if possible). See stack_amnount for how to interpret this. |
| // |
| // This could still be empty in the "kMinimal" or "kFull" cases if retrieval failed, which can |
| // happen in some valid race conditions if the thread was killed out from under the debug agent. |
| std::vector<StackFrame> frames; |
| }; |
| |
| struct ProcessRecord { |
| uint64_t process_koid = 0; |
| std::string process_name; |
| |
| std::vector<ThreadRecord> threads; |
| }; |
| |
| struct MemoryBlock { |
| // Begin address of this memory. |
| uint64_t address = 0; |
| |
| // When true, indicates this is valid memory, with the data containing the |
| // memory. False means that this range is not mapped in the process and the |
| // data will be empty. |
| bool valid = false; |
| |
| // Length of this range. When valid == true, this will be the same as |
| // data.size(). When valid == false, this will be whatever the length of |
| // the invalid region is, and data will be empty. |
| uint32_t size = 0; |
| |
| // The actual memory. Filled in only if valid == true. |
| std::vector<uint8_t> data; |
| }; |
| |
| struct ProcessBreakpointSettings { |
| // Required to be nonzero. |
| uint64_t process_koid = 0; |
| |
| // Zero indicates this is a process-wide breakpoint. Otherwise, this is the thread to break. |
| uint64_t thread_koid = 0; |
| |
| // Address to break at. |
| uint64_t address = 0; |
| |
| // Range is used for watchpoints. |
| AddressRange address_range; |
| }; |
| |
| // What threads to stop when the breakpoint is hit. |
| enum class Stop : uint32_t { |
| kAll, // Stop all threads of all processes attached to the debugger. |
| kProcess, // Stop all threads of the process that hit the breakpoint. |
| kThread, // Stop only the thread that hit the breakpoint. |
| kNone // Don't stop anything but accumulate hit counts. |
| }; |
| |
| // NOTE: read-only could be added in the future as arm64 supports them. They're not added today as |
| // x64 does not support them and presenting a common platform is cleaner for now. |
| enum class BreakpointType : uint32_t { |
| kSoftware = 0, // Software code execution. |
| kHardware, // Hardware code execution. |
| kReadWrite, // Hardware read/write. |
| kWrite, // Hardware write. |
| |
| kLast, // Not a real type, end marker. |
| }; |
| const char* BreakpointTypeToString(BreakpointType); |
| |
| // Read, ReadWrite and Write are considered watchpoint types. |
| bool IsWatchpointType(BreakpointType); |
| |
| constexpr uint32_t kDebugAgentInternalBreakpointId = static_cast<uint32_t>(-1); |
| |
| struct BreakpointSettings { |
| // The ID if this breakpoint. This is assigned by the client. This is different than the ID in |
| // the console frontend which can be across mutliple processes or may match several addresses in |
| // a single process. |
| // |
| // The ID kDebugAgentInternalBreakpointId is reserved for internal use by the backend. |
| uint32_t id = 0; |
| |
| BreakpointType type = BreakpointType::kSoftware; |
| |
| // Name used to recognize a breakpoint. Useful for debugging purposes. Optional. |
| std::string name; |
| |
| // When set, the breakpoint will automatically be removed as soon as it is |
| // hit. |
| bool one_shot = false; |
| |
| // What should stop when the breakpoint is hit. |
| Stop stop = Stop::kAll; |
| |
| // Processes to which this breakpoint applies. |
| // |
| // If any process specifies a nonzero thread_koid, it must be the only process (a breakpoint can |
| // apply either to all threads in a set of processes, or exactly one thread globally). |
| std::vector<ProcessBreakpointSettings> locations; |
| }; |
| |
| struct BreakpointStats { |
| uint32_t id = 0; |
| uint32_t hit_count = 0; |
| |
| // On a "breakpoint hit" message from the debug agent, if this flag is set, |
| // the agent has deleted the breakpoint because it was a one-shot breakpoint. |
| // Whenever a client gets a breakpoint hit with this flag set, it should |
| // clear the local state associated with the breakpoint. |
| bool should_delete = false; |
| }; |
| |
| // Information on one loaded module. |
| struct Module { |
| std::string name; // The main executable binary will normally have an empty name. |
| uint64_t base = 0; // Load address of this file. |
| uint64_t debug_address = 0; // Link map address for this module. |
| std::string build_id; |
| }; |
| |
| struct AddressRegion { |
| std::string name; |
| uint64_t base; |
| uint64_t size; |
| uint64_t depth; |
| }; |
| |
| // ReadRegisters ----------------------------------------------------------------------------------- |
| |
| struct ConfigAction { |
| enum class Type : uint32_t { |
| // Quit whenever the connection shutdowns. |
| kQuitOnExit, // Values are "false" | "true" |
| |
| kLast, // Not valid. |
| }; |
| static const char* TypeToString(Type); |
| |
| Type type = Type::kLast; |
| |
| // Each action uses a different set of values. |
| std::string value; |
| }; |
| |
| // LoadInfoHandleTable ----------------------------------------------------------------------------- |
| |
| // VMO-specific handle information from zx_info_vmo that's not in the InfoHandle structure. |
| struct InfoHandleVmo { |
| char name[32]; // Needs to be POD for use in union below, and 32 is the max from the kernel. |
| uint64_t size_bytes; |
| uint64_t parent_koid; |
| uint64_t num_children; |
| uint64_t num_mappings; |
| uint64_t share_count; |
| uint32_t flags; |
| uint64_t committed_bytes; |
| uint32_t cache_policy; |
| uint64_t metadata_bytes; |
| uint64_t committed_change_events; |
| }; |
| |
| // This structure is assumed to be entirely POD. |
| struct InfoHandle { |
| // Provide 0-init that covers the union. |
| InfoHandle() { memset(this, 0, sizeof(InfoHandle)); } |
| |
| // Standard information from zx_info_handle_extended. |
| // |
| // There is a special case for a VMO. It is possible to have a VMO mapped without a handle to it. |
| // These will appear here but the handle_value will be 0. |
| uint32_t type; |
| uint32_t handle_value; |
| uint32_t rights; |
| uint32_t reserved; |
| uint64_t koid; |
| uint64_t related_koid; |
| uint64_t peer_owner_koid; |
| |
| // Type-specific handle information. |
| union { |
| InfoHandleVmo vmo; // Valid when type == ZX_OBJ_TYPE_VMO. |
| // Other types go here. |
| } ext; |
| }; |
| |
| #pragma pack(pop) |
| |
| } // namespace debug_ipc |
| |
| #endif // SRC_DEVELOPER_DEBUG_IPC_RECORDS_H_ |