blob: 57cfac15c44a12db8cabd319a9128fdaada546db [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_IPC_PROTOCOL_H_
#define SRC_DEVELOPER_DEBUG_IPC_PROTOCOL_H_
#include <set>
#include "lib/syslog/cpp/macros.h"
#include "src/developer/debug/ipc/records.h"
#include "src/developer/debug/shared/arch.h"
#include "src/developer/debug/shared/platform.h"
#include "src/developer/debug/shared/register_info.h"
#include "src/developer/debug/shared/serialization.h"
#include "src/developer/debug/shared/status.h"
namespace debug_ipc {
// ABI Compatibility Guide
// -----------------------
//
// Goal: zxdb and debug_agent with overlapping [kMinimumProtocolVersion, kCurrentProtocolVersion]
// should be able to talk with each other.
//
// - If you want to rename something, don't bump the version number because ABI doesn't change.
// - If you want to add/remove a field to/from a message, bump the version number, and use e.g.
// `if (ver > ...) { ser | some_field; }` in the `Serialize` function.
// - If you want to add a new request/notification type, pick a new message id, bump the version,
// and define a static variable `uint32_t kSupportedSinceVersion` in the definition of the
// request/notification type. This will make the `Serialize` function return empty bytes when
// serializing so the message won't be sent.
// - You don't want to remove a message type. Just mark it as deprecated but still handle it
// when receiving it.
// - More complex logic could be implemented by checking the protocol version before sending.
//
// NOTE: Before you want to bump the kCurrentProtocolVersion, please make sure that
// CURRENT_SUPPORTED_API_LEVEL is equal to FUCHSIA_API_LEVEL specified in version_history.json.
// If not, continue reading the comments below.
constexpr uint32_t kCurrentProtocolVersion = 63;
// How to decide kMinimumProtocolVersion
// -------------------------------------
//
// We want to maintain a compatibility window of 2 major releases, so that zxdb built with a given
// FUCHSIA_API_LEVEL could support debug_agent built between FUCHSIA_API_LEVEL-2 (inclusive) and
// FUCHSIA_API_LEVEL+2 (inclusive). This exceeds the minimum compatibility required by RFC-0169,
// which only requires forward compatibility (older zxdb with newer debug_agent).
//
// To achieve this, kMinimumProtocolVersion must be set to the initial protocol version used in
// FUCHSIA_API_LEVEL-2. The following macros are used to ensure that kMinimumProtocolVersion is
// set based on FUCHSIA_API_LEVEL.
//
// To avoid blocking FUCHSIA_API_LEVEL bumps, we allow CURRENT_SUPPORTED_API_LEVEL to be out of
// sync with FUCHSIA_API_LEVEL, as long as the kCurrentProtocolVersion stays the same.
// When FUCHSIA_API_LEVEL changes, we need to update those macros as the following:
//
// - INITIAL_VERSION_FOR_API_LEVEL_MINUS_2 = INITIAL_VERSION_FOR_API_LEVEL_MINUS_1
// - INITIAL_VERSION_FOR_API_LEVEL_MINUS_1 = INITIAL_VERSION_FOR_API_LEVEL_CURRENT
// - INITIAL_VERSION_FOR_API_LEVEL_CURRENT = kCurrentProtocolVersion
// - CURRENT_SUPPORTED_API_LEVEL = FUCHSIA_API_LEVEL
#define INITIAL_VERSION_FOR_API_LEVEL_MINUS_2 56
#define INITIAL_VERSION_FOR_API_LEVEL_MINUS_1 58
#define INITIAL_VERSION_FOR_API_LEVEL_CURRENT 60
#define CURRENT_SUPPORTED_API_LEVEL 19
#if defined(__Fuchsia__) == defined(FUCHSIA_API_LEVEL)
#error FUCHSIA_API_LEVEL should be defind only and always for non-Fuchsia platforms.
#endif
#if !defined(FUCHSIA_API_LEVEL)
// This is a workaround when using this library in the @internal_sdk, as the SDK
// metadata surface has no way to indicate that a library requires a specific
// compilation define.
// When building for host using the @internal_sdk, if __Fuchsia_API_level__ is
// not defined, the user of this library will need to explicitly define
// FUCHSIA_API_LEVEL.
#if !defined(__Fuchsia_API_level__)
#error FUCHSIA_API_LEVEL or __Fuchsia_API_level__ must be defined
#endif
#define FUCHSIA_API_LEVEL __Fuchsia_API_level__
#endif
#if FUCHSIA_API_LEVEL == CURRENT_SUPPORTED_API_LEVEL
constexpr uint32_t kMinimumProtocolVersion = INITIAL_VERSION_FOR_API_LEVEL_MINUS_2;
#else
// If this branch is chosen, please update as above.
constexpr uint32_t kMinimumProtocolVersion = INITIAL_VERSION_FOR_API_LEVEL_MINUS_1;
#endif
// This is so that it's obvious if the timestamp wasn't properly set (that number should be at
// least 30,000 years) but it's not the max so that if things add to it then time keeps moving
// forward.
const uint64_t kTimestampDefault = 0x0fefffffffffffff;
// The arch values are encoded in the protocol, if these change the protocol version above needs to
// be updated.
static_assert(static_cast<int>(debug::Arch::kX64) == 1);
static_assert(static_cast<int>(debug::Arch::kArm64) == 2);
#pragma pack(push, 8)
// Enumerate over the name of all possible request/reply types. The message id will be
// MsgHeader::Type::k##name, the request type is name##Request, and the reply type is name##Reply.
#define FOR_EACH_REQUEST_TYPE(FN) \
FN(Hello) \
FN(AddOrChangeBreakpoint) \
FN(AddressSpace) \
FN(Attach) \
FN(Detach) \
FN(UpdateFilter) \
FN(Kill) \
FN(RunBinary) \
FN(Modules) \
FN(Pause) \
FN(ProcessTree) \
FN(ReadMemory) \
FN(ReadRegisters) \
FN(WriteRegisters) \
FN(RemoveBreakpoint) \
FN(Resume) \
FN(Status) \
FN(SysInfo) \
FN(ThreadStatus) \
FN(Threads) \
FN(WriteMemory) \
FN(LoadInfoHandleTable) \
FN(UpdateGlobalSettings) \
FN(SaveMinidump) \
FN(RunComponent) \
FN(RunTest)
// The "notify" messages are sent unrequested from the agent to the client.
//
// Enumerate over the name of all possible notification types. The message id is
// MsgHeader::Type::k##name, and the type is type.
#define FOR_EACH_NOTIFICATION_TYPE(FN) \
FN(NotifyException) \
FN(NotifyIO) \
FN(NotifyModules) \
FN(NotifyProcessExiting) \
FN(NotifyProcessStarting) \
FN(NotifyThreadExiting) \
FN(NotifyThreadStarting) \
FN(NotifyLog) \
FN(NotifyComponentExiting) \
FN(NotifyComponentStarting) \
FN(NotifyComponentDiscovered) \
FN(NotifyTestExited)
// 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 = 1,
kAddOrChangeBreakpoint = 2,
kAddressSpace = 3,
kAttach = 4,
kDetach = 5,
kUpdateFilter = 6,
kKill = 7,
kRunBinary = 8,
kModules = 9,
kPause = 10,
kProcessTree = 11,
kReadMemory = 12,
kReadRegisters = 13,
kWriteRegisters = 14,
kRemoveBreakpoint = 15,
kResume = 16,
kStatus = 17,
kSysInfo = 18,
kThreadStatus = 19,
kThreads = 20,
kWriteMemory = 21,
kLoadInfoHandleTable = 22,
kUpdateGlobalSettings = 23,
kSaveMinidump = 24,
kRunComponent = 25,
kRunTest = 26,
kNotifyException = 101,
kNotifyIO = 102,
kNotifyModules = 103,
kNotifyProcessExiting = 104,
kNotifyProcessStarting = 105,
kNotifyThreadExiting = 106,
kNotifyThreadStarting = 107,
kNotifyLog = 108,
kNotifyComponentExiting = 109,
kNotifyComponentStarting = 110,
kNotifyTestExited = 111,
kNotifyComponentDiscovered = 112,
};
static const char* TypeToString(Type);
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;
void Serialize(Serializer& ser, uint32_t ver) { ser | size | type | transaction_id; }
};
struct HelloRequest {
uint32_t version = 0;
void Serialize(Serializer& ser, uint32_t ver) { ser | version; }
};
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 = 0;
debug::Arch arch = debug::Arch::kUnknown;
uint64_t page_size = 0;
debug::Platform platform = debug::Platform::kUnknown;
// Danger: The HelloReply is special because it is used to set up the rest of the IPC
// communication.
//
// It will get deserialized with |ver| = 0 to extract the |signature| and |version| member, and
// then it will get deserialized again with the correct version to get everything else.
void Serialize(Serializer& ser, uint32_t ver) {
ser | signature | version | arch | page_size;
if (ver >= 58) {
ser | platform;
}
}
};
// Status ------------------------------------------------------------------------------------------
//
// Asks for a present view of the system.
struct StatusRequest {
void Serialize(Serializer& ser, uint32_t ver) {}
};
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;
// All the breakpoints (pending or active, hardware or software) registered with the Agent.
std::vector<BreakpointSettings> breakpoints;
// All the installed filters.
std::vector<Filter> filters;
void Serialize(Serializer& ser, uint32_t ver) {
ser | processes | limbo;
if (ver >= 59) {
ser | breakpoints | filters;
}
}
};
struct KillRequest {
uint64_t process_koid = 0;
void Serialize(Serializer& ser, uint32_t ver) { ser | process_koid; }
};
struct KillReply {
uint64_t timestamp = kTimestampDefault;
debug::Status status;
void Serialize(Serializer& ser, uint32_t ver) { ser | timestamp | status; }
};
// The debug agent will follow a successful AttachReply with notifications for
// all threads currently existing in the attached process.
struct AttachRequest {
uint64_t koid = 0;
// Attaching weakly means that the backend will only be listening for exceptions on the process,
// the frontend must explicitly request and load the modules to do anything with symbols.
bool weak = false;
void Serialize(Serializer& ser, uint32_t ver) {
ser | koid;
if (ver >= 61) {
ser | weak;
}
}
};
struct AttachReply {
uint64_t timestamp = kTimestampDefault;
uint64_t koid = 0;
debug::Status status; // Result of attaching.
std::string name;
// The component information if the process is running in a component. There could be many
// components for a single process. An empty vector means there was no component associated with
// the process. Order of components is not guaranteed.
std::vector<ComponentInfo> components;
void Serialize(Serializer& ser, uint32_t ver) {
ser | timestamp | koid | status | name;
if (ver < 57) {
// The component information if the task is a process and the process is running in a
// component.
std::optional<ComponentInfo> component = std::nullopt;
if (!components.empty()) {
component = components[0];
}
components.clear();
ser | component;
if (component) {
components = {*component};
}
} else {
ser | components;
}
}
};
struct DetachRequest {
uint64_t koid = 0;
void Serialize(Serializer& ser, uint32_t ver) { ser | koid; }
};
struct DetachReply {
uint64_t timestamp = kTimestampDefault;
debug::Status status;
void Serialize(Serializer& ser, uint32_t ver) { ser | timestamp | status; }
};
struct PauseRequest {
// When empty, pauses all threads in all processes. An entry with a process koid and a 0 thread
// koid will resume all threads of the given process.
std::vector<ProcessThreadId> ids;
void Serialize(Serializer& ser, uint32_t ver) { ser | ids; }
};
// 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;
void Serialize(Serializer& ser, uint32_t ver) { ser | threads; }
};
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 |count| machine instructions.
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(How how) {
return (how == How::kStepInstruction || how == How::kStepInRange);
}
static const char* HowToString(How);
// If empty, all threads of all debugged processes will be continued. An entry with a process
// koid and a 0 thread koid will resume all threads of the given process.
//
// kStepInRange may only be used with a unique thread.
std::vector<ProcessThreadId> ids;
How how = How::kResolveAndContinue;
// When how == kStepInstruction, how many instructions to step.
uint64_t count = 1;
// 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;
void Serialize(Serializer& ser, uint32_t ver) {
ser | ids | how | count | range_begin | range_end;
}
};
struct ResumeReply {
void Serialize(Serializer& ser, uint32_t ver) {}
};
struct ProcessTreeRequest {
void Serialize(Serializer& ser, uint32_t ver) {}
};
struct ProcessTreeReply {
ProcessTreeRecord root;
void Serialize(Serializer& ser, uint32_t ver) { ser | root; }
};
struct ThreadsRequest {
uint64_t process_koid = 0;
void Serialize(Serializer& ser, uint32_t ver) { ser | process_koid; }
};
struct ThreadsReply {
// If there is no such process, the threads array will be empty.
std::vector<ThreadRecord> threads;
void Serialize(Serializer& ser, uint32_t ver) { ser | threads; }
};
struct ReadMemoryRequest {
uint64_t process_koid = 0;
uint64_t address = 0;
uint32_t size = 0;
void Serialize(Serializer& ser, uint32_t ver) { ser | process_koid | address | size; }
};
struct ReadMemoryReply {
std::vector<MemoryBlock> blocks;
void Serialize(Serializer& ser, uint32_t ver) { ser | blocks; }
};
struct AddOrChangeBreakpointRequest {
BreakpointSettings breakpoint;
void Serialize(Serializer& ser, uint32_t ver) { ser | 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.
debug::Status status;
void Serialize(Serializer& ser, uint32_t ver) { ser | status; }
};
struct RemoveBreakpointRequest {
uint32_t breakpoint_id = 0;
void Serialize(Serializer& ser, uint32_t ver) { ser | breakpoint_id; }
};
struct RemoveBreakpointReply {
void Serialize(Serializer& ser, uint32_t ver) {}
};
struct SysInfoRequest {
void Serialize(Serializer& ser, uint32_t ver) {}
};
struct SysInfoReply {
std::string version;
uint32_t num_cpus;
uint32_t memory_mb;
uint32_t hw_breakpoint_count;
uint32_t hw_watchpoint_count;
void Serialize(Serializer& ser, uint32_t ver) {
ser | version | num_cpus | memory_mb | hw_breakpoint_count | 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 {
ProcessThreadId id;
void Serialize(Serializer& ser, uint32_t ver) { ser | id; }
};
struct ThreadStatusReply {
ThreadRecord record;
void Serialize(Serializer& ser, uint32_t ver) { ser | 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;
void Serialize(Serializer& ser, uint32_t ver) { ser | process_koid | address; }
};
struct AddressSpaceReply {
std::vector<AddressRegion> map;
void Serialize(Serializer& ser, uint32_t ver) { ser | map; }
};
struct ModulesRequest {
uint64_t process_koid = 0;
void Serialize(Serializer& ser, uint32_t ver) { ser | process_koid; }
};
struct ModulesReply {
std::vector<Module> modules;
void Serialize(Serializer& ser, uint32_t ver) { ser | modules; }
};
// Request to set filter.
struct UpdateFilterRequest {
std::vector<Filter> filters;
void Serialize(Serializer& ser, uint32_t ver) { ser | filters; }
};
struct UpdateFilterReply {
// Each matched process will be paired with the frontend id of the filter that matched it.
// Several filters could have matched the same pids, which can be observed in the respective
// filter's list of matches.
std::vector<FilterMatch> matched_processes_for_filter;
void Serialize(Serializer& ser, uint32_t ver) {
if (ver < 61) {
// 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;
// This set will filter out duplicate koids.
std::set<uint64_t> pids;
for (auto& match : matched_processes_for_filter) {
pids.insert(match.matched_pids.begin(), match.matched_pids.end());
}
matched_processes.reserve(pids.size());
matched_processes.insert(matched_processes.end(), pids.begin(), pids.end());
pids.clear();
matched_processes_for_filter.clear();
ser | matched_processes;
// There's no way for us to reconstruct the filter id to pid mapping, so we map all of the
// matches to the reserved invalid filter id.
matched_processes_for_filter.emplace_back(kInvalidFilterId, std::move(matched_processes));
} else {
ser | matched_processes_for_filter;
}
}
};
struct WriteMemoryRequest {
uint64_t process_koid = 0;
uint64_t address = 0;
std::vector<uint8_t> data;
void Serialize(Serializer& ser, uint32_t ver) { ser | process_koid | address | data; }
};
struct WriteMemoryReply {
debug::Status status;
void Serialize(Serializer& ser, uint32_t ver) { ser | status; }
};
struct LoadInfoHandleTableRequest {
uint64_t process_koid = 0;
void Serialize(Serializer& ser, uint32_t ver) { ser | process_koid; }
};
struct LoadInfoHandleTableReply {
debug::Status status;
std::vector<InfoHandle> handles;
void Serialize(Serializer& ser, uint32_t ver) { ser | status | handles; }
};
struct UpdateGlobalSettingsRequest {
// Updates how the default strategy for handling a particular exception type.
struct UpdateExceptionStrategy {
ExceptionType type = ExceptionType::kNone;
ExceptionStrategy value = ExceptionStrategy::kNone;
void Serialize(Serializer& ser, uint32_t ver) { ser | type | value; }
};
std::vector<UpdateExceptionStrategy> exception_strategies;
void Serialize(Serializer& ser, uint32_t ver) { ser | exception_strategies; }
};
struct UpdateGlobalSettingsReply {
debug::Status status;
void Serialize(Serializer& ser, uint32_t ver) { ser | status; }
};
struct SaveMinidumpRequest {
uint64_t process_koid = 0;
void Serialize(Serializer& ser, uint32_t ver) { ser | process_koid; }
};
struct SaveMinidumpReply {
debug::Status status;
std::vector<uint8_t> core_data;
void Serialize(Serializer& ser, uint32_t ver) { ser | status | core_data; }
};
// ReadRegisters ---------------------------------------------------------------
struct ReadRegistersRequest {
ProcessThreadId id;
// What categories do we want to receive data from.
std::vector<debug::RegisterCategory> categories;
void Serialize(Serializer& ser, uint32_t ver) { ser | id | categories; }
};
struct ReadRegistersReply {
std::vector<debug::RegisterValue> registers;
void Serialize(Serializer& ser, uint32_t ver) { ser | registers; }
};
// WriteRegisters --------------------------------------------------------------
struct WriteRegistersRequest {
ProcessThreadId id;
std::vector<debug::RegisterValue> registers;
void Serialize(Serializer& ser, uint32_t ver) { ser | id | registers; }
};
struct WriteRegistersReply {
debug::Status status;
// 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<debug::RegisterValue> registers;
void Serialize(Serializer& ser, uint32_t ver) { ser | status | registers; }
};
// Run -------------------------------------------------------------------------
struct RunBinaryRequest {
// argv[0] is the app to launch.
std::vector<std::string> argv;
void Serialize(Serializer& ser, uint32_t ver) { ser | argv; }
};
struct RunBinaryReply {
uint64_t timestamp = kTimestampDefault;
// Result of launch.
debug::Status status;
// process_id and process_name are only valid when inferior_type is kBinary.
uint64_t process_id = 0;
std::string process_name;
void Serialize(Serializer& ser, uint32_t ver) {
ser | timestamp | status | process_id | process_name;
}
};
struct RunComponentRequest {
std::string url;
void Serialize(Serializer& ser, uint32_t ver) { ser | url; }
};
struct RunComponentReply {
debug::Status status;
void Serialize(Serializer& ser, uint32_t ver) { ser | status; }
};
struct RunTestRequest {
std::string url;
std::optional<std::string> realm;
std::vector<std::string> case_filters;
void Serialize(Serializer& ser, uint32_t ver) { ser | url | realm | case_filters; }
};
struct RunTestReply {
debug::Status status;
void Serialize(Serializer& ser, uint32_t ver) { ser | status; }
};
// Notifications ---------------------------------------------------------------
// Notify that a new process of interest was created and attached.
struct NotifyProcessStarting {
uint64_t timestamp = kTimestampDefault;
enum class Type : uint32_t {
kAttach, // The process was attached from a filter.
kLaunch, // The process was attached from a component launching.
kLimbo, // The process entered the limbo and is NOT attached.
kLast, // Not valid, for verification purposes.
};
static const char* TypeToString(Type);
Type type = Type::kAttach;
uint64_t koid = 0;
std::string name;
// The component information if the process is running in a component. There could be many
// components for a single process. An empty vector means there was no component associated with
// the process. Order of components is not guaranteed.
std::vector<ComponentInfo> components;
// The client filter id that matched this process.
uint32_t filter_id = kInvalidFilterId;
void Serialize(Serializer& ser, uint32_t ver) {
ser | timestamp | type | koid | name;
if (ver < 57) {
// The component information if the process is running in a component.
std::optional<ComponentInfo> component = std::nullopt;
if (!components.empty()) {
component = components[0];
}
components.clear();
ser | component;
if (component) {
components = {*component};
}
} else {
ser | components;
}
if (ver >= 61) {
ser | filter_id;
}
}
};
// 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 timestamp = kTimestampDefault;
uint64_t process_koid = 0;
int64_t return_code = 0;
void Serialize(Serializer& ser, uint32_t ver) { ser | timestamp | process_koid | return_code; }
};
// Data for thread created.
struct NotifyThreadStarting {
uint64_t timestamp = kTimestampDefault;
ThreadRecord record;
void Serialize(Serializer& ser, uint32_t ver) { ser | timestamp | record; }
};
// Data for thread destroyed.
struct NotifyThreadExiting {
uint64_t timestamp = kTimestampDefault;
ThreadRecord record;
void Serialize(Serializer& ser, uint32_t ver) { ser | timestamp | record; }
};
// Data passed for exceptions.
struct NotifyException {
uint64_t timestamp = kTimestampDefault;
// 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;
// If automation was requested, then this contains the memory requested
// Otherwise this is just an empty vector.
std::vector<MemoryBlock> memory_blocks;
void Serialize(Serializer& ser, uint32_t ver) {
ser | timestamp | thread | type | exception | hit_breakpoints | other_affected_threads |
memory_blocks;
}
};
// Indicates the loaded modules may have changed. The entire list of current
// modules is sent every time.
struct NotifyModules {
uint64_t timestamp = kTimestampDefault;
uint64_t process_koid = 0;
std::vector<Module> modules;
void Serialize(Serializer& ser, uint32_t ver) {
ser | timestamp | process_koid | modules;
if (ver < 54) {
// 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.
//
// Removed in version 54 but the old client can still use this to resume.
std::vector<ProcessThreadId> stopped_threads;
stopped_threads.push_back({.process = process_koid});
ser | stopped_threads;
}
}
};
struct NotifyIO {
uint64_t timestamp = kTimestampDefault;
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; // Could be 0 if the output is not from an attached process.
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;
void Serialize(Serializer& ser, uint32_t ver) {
ser | timestamp | process_koid | type | data | more_data_available;
}
};
// Redirects a log message in debug_agent to the frontend, serving two purposes:
// 1) Forwards important warnings or errors that the end users would rather know.
// 2) Forwards info and debug logs for debugger developers, when the debug mode is turned on.
struct NotifyLog {
uint64_t timestamp = kTimestampDefault;
enum class Severity : uint32_t {
kDebug, // Not used for now.
kInfo, // Not used for now.
kWarn,
kError,
kLast, // Not a real level.
};
struct Location {
std::string file;
std::string function;
uint32_t line = 0;
void Serialize(Serializer& ser, uint32_t ver) { ser | file | function | line; }
};
Severity severity = Severity::kInfo;
Location location;
std::string log;
void Serialize(Serializer& ser, uint32_t ver) { ser | timestamp | severity | location | log; }
};
// When a filter has been installed recursively, we need to let the front end know about it,
// particularly if it's a weak filter. Otherwise the frontend will take the default approach to not
// treat a process starting event as a weak attach.
struct NotifyComponentDiscovered {
static constexpr uint32_t kSupportedSinceVersion = 63;
// The filter that the backend installed to match this realm.
Filter filter;
void Serialize(Serializer& ser, uint32_t ver) { ser | filter; }
};
// Notify that a component has started.
// Only components of interest, i.e., those matching at least one of the filters, will be notified.
struct NotifyComponentStarting {
uint64_t timestamp = kTimestampDefault;
ComponentInfo component;
void Serialize(Serializer& ser, uint32_t ver) { ser | timestamp | component; }
};
// Notify that a component has exited.
// Only components of interest, i.e., those matching at least one of the filters, will be notified.
struct NotifyComponentExiting {
static constexpr uint32_t kSupportedSinceVersion = 52;
uint64_t timestamp = kTimestampDefault;
ComponentInfo component;
void Serialize(Serializer& ser, uint32_t ver) { ser | timestamp | component; }
};
struct NotifyTestExited {
static constexpr uint32_t kSupportedSinceVersion = 53;
uint64_t timestamp = kTimestampDefault;
std::string url;
void Serialize(Serializer& ser, uint32_t ver) { ser | timestamp | url; }
};
#pragma pack(pop)
} // namespace debug_ipc
#endif // SRC_DEVELOPER_DEBUG_IPC_PROTOCOL_H_