Skip to main content

ghostscope_protocol/
trace_event.rs

1use crate::TypeKind;
2use serde::{Deserialize, Serialize};
3use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
4
5/// Each trace event contains multiple instructions followed by EndInstruction
6#[repr(C, packed)]
7#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
8pub struct TraceEventHeader {
9    pub magic: u32,
10}
11
12pub const TRACE_EVENT_HEADER_SIZE: usize = std::mem::size_of::<TraceEventHeader>();
13pub const TRACE_EVENT_HEADER_MAGIC_OFFSET: usize = std::mem::offset_of!(TraceEventHeader, magic);
14
15#[repr(C, packed)]
16#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
17pub struct TraceEventMessage {
18    pub trace_id: u64,
19    pub timestamp: u64,
20    pub pid: u32,
21    pub tid: u32,
22    // Followed by variable-length instruction sequence ending with EndInstruction
23}
24
25pub const TRACE_EVENT_MESSAGE_SIZE: usize = std::mem::size_of::<TraceEventMessage>();
26pub const TRACE_EVENT_MESSAGE_TRACE_ID_OFFSET: usize =
27    std::mem::offset_of!(TraceEventMessage, trace_id);
28pub const TRACE_EVENT_MESSAGE_TIMESTAMP_OFFSET: usize =
29    std::mem::offset_of!(TraceEventMessage, timestamp);
30pub const TRACE_EVENT_MESSAGE_PID_OFFSET: usize = std::mem::offset_of!(TraceEventMessage, pid);
31pub const TRACE_EVENT_MESSAGE_TID_OFFSET: usize = std::mem::offset_of!(TraceEventMessage, tid);
32
33/// Instruction types for trace events
34#[repr(u8)]
35#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
36pub enum InstructionType {
37    PrintStringIndex = 0x01,     // print "string" (using string table index)
38    PrintVariableIndex = 0x02,   // print variable (using variable name index)
39    PrintComplexVariable = 0x03, // print complex variable (with full type info)
40    PrintComplexFormat = 0x05,   // print with complex variables in format args
41    Backtrace = 0x10,            // backtrace instruction
42    /// Structured runtime expression error/warning (control-flow or print context)
43    ExprError = 0x20,
44
45    // Control instructions
46    EndInstruction = 0xFF, // marks end of instruction sequence
47}
48
49/// Common instruction header
50#[repr(C, packed)]
51#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable, Unaligned)]
52pub struct InstructionHeader {
53    pub inst_type: u8,    // InstructionType
54    pub data_length: u16, // Length of instruction data following this header
55    pub reserved: u8,
56}
57
58pub const INSTRUCTION_HEADER_SIZE: usize = std::mem::size_of::<InstructionHeader>();
59pub const INSTRUCTION_HEADER_INST_TYPE_OFFSET: usize =
60    std::mem::offset_of!(InstructionHeader, inst_type);
61pub const INSTRUCTION_HEADER_DATA_LENGTH_OFFSET: usize =
62    std::mem::offset_of!(InstructionHeader, data_length);
63pub const INSTRUCTION_HEADER_RESERVED_OFFSET: usize =
64    std::mem::offset_of!(InstructionHeader, reserved);
65
66/// Per-variable runtime status for data acquisition
67#[repr(u8)]
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69pub enum VariableStatus {
70    Ok = 0,
71    NullDeref = 1,
72    ReadError = 2,
73    AccessError = 3,
74    Truncated = 4,
75    /// Required runtime offsets/proc mapping not available at eBPF time
76    /// (e.g., no (pid,module) offsets to compute address)
77    OffsetsUnavailable = 5,
78    /// Requested dynamic length is <= 0; no bytes were read
79    ZeroLength = 6,
80}
81
82/// Payload carried by ReadError variable statuses.
83#[repr(C, packed)]
84#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
85pub struct VariableReadErrorPayload {
86    pub errno: i32,
87    pub addr: u64,
88}
89
90pub const VARIABLE_READ_ERROR_PAYLOAD_LEN: usize = std::mem::size_of::<VariableReadErrorPayload>();
91pub const VARIABLE_READ_ERROR_PAYLOAD_ERRNO_OFFSET: usize =
92    std::mem::offset_of!(VariableReadErrorPayload, errno);
93pub const VARIABLE_READ_ERROR_PAYLOAD_ADDR_OFFSET: usize =
94    std::mem::offset_of!(VariableReadErrorPayload, addr);
95
96/// Print string instruction data (most optimized)
97#[repr(C, packed)]
98#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable, Unaligned)]
99pub struct PrintStringIndexData {
100    pub string_index: u16, // Index into string table
101}
102
103/// Print variable instruction data (optimized with name index)
104#[repr(C, packed)]
105#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable, Unaligned)]
106pub struct PrintVariableIndexData {
107    pub var_name_index: u16, // Index into variable name table
108    pub type_encoding: u8,   // TypeKind
109    pub data_len: u16,       // Length of variable data that follows
110    pub type_index: u16,     // Index into type table (new field)
111    pub status: u8, // Variable read status: see VariableStatus. For script variables this is 0.
112                    // Followed by variable data
113}
114
115/// Print complex variable instruction data (enhanced with full type info)
116#[repr(C, packed)]
117#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable, Unaligned)]
118pub struct PrintComplexVariableData {
119    pub var_name_index: u16, // Index into variable name table
120    pub type_index: u16,     // Index into type table for complete type information
121    pub access_path_len: u8, // Length of access path description (e.g., "person.name.first")
122    pub status: u8,          // Variable read status: see VariableStatus
123    pub data_len: u16,       // Length of variable data that follows
124                             // Followed by access_path (UTF-8 string) then variable data
125}
126
127/// Complex format print instruction data (with full type info)
128#[repr(C, packed)]
129#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable, Unaligned)]
130pub struct PrintComplexFormatData {
131    pub format_string_index: u16, // Index into string table for format string
132    pub arg_count: u8,            // Number of arguments
133    pub reserved: u8,             // Padding for alignment
134                                  // Followed by complex argument data:
135                                  // [var_name_index:u16, type_index:u16, access_path_len:u8, status:u8,
136                                  //  access_path:bytes, data_len:u16, data:bytes] * arg_count
137}
138
139/// Fixed prefix for each PrintComplexFormat argument.
140///
141/// The full argument is variable-length:
142/// `PrintComplexFormatArgPrefix`, then `access_path`, then `data_len:u16`,
143/// then `data`.
144#[repr(C, packed)]
145#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable, Unaligned)]
146pub struct PrintComplexFormatArgPrefix {
147    pub var_name_index: u16,
148    pub type_index: u16,
149    pub access_path_len: u8,
150    pub status: u8,
151}
152
153pub const PRINT_COMPLEX_FORMAT_DATA_ARG_COUNT_OFFSET: usize =
154    std::mem::offset_of!(PrintComplexFormatData, arg_count);
155
156pub const PRINT_COMPLEX_FORMAT_ARG_VAR_NAME_INDEX_OFFSET: usize =
157    std::mem::offset_of!(PrintComplexFormatArgPrefix, var_name_index);
158pub const PRINT_COMPLEX_FORMAT_ARG_TYPE_INDEX_OFFSET: usize =
159    std::mem::offset_of!(PrintComplexFormatArgPrefix, type_index);
160pub const PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_LEN_OFFSET: usize =
161    std::mem::offset_of!(PrintComplexFormatArgPrefix, access_path_len);
162pub const PRINT_COMPLEX_FORMAT_ARG_STATUS_OFFSET: usize =
163    std::mem::offset_of!(PrintComplexFormatArgPrefix, status);
164pub const PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_OFFSET: usize =
165    std::mem::size_of::<PrintComplexFormatArgPrefix>();
166pub const PRINT_COMPLEX_FORMAT_ARG_DATA_LEN_SIZE: usize = std::mem::size_of::<u16>();
167pub const PRINT_COMPLEX_FORMAT_ARG_FIXED_HEADER_LEN: usize =
168    PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_OFFSET + PRINT_COMPLEX_FORMAT_ARG_DATA_LEN_SIZE;
169
170// Note: historical PrintVariableError has been removed; per-variable errors
171// are carried via status in PrintVariableIndex/ComplexFormat.
172
173/// Backtrace instruction data
174#[repr(C, packed)]
175#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable, Unaligned)]
176pub struct BacktraceData {
177    pub requested_depth: u8,
178    pub frame_count: u8,
179    pub flags: u8,
180    pub status: u8,
181    pub error_code: u16,
182    pub reserved: u16,
183    // Followed by BacktraceFrameData[requested_depth].
184}
185
186pub const BACKTRACE_DATA_SIZE: usize = std::mem::size_of::<BacktraceData>();
187pub const BACKTRACE_DATA_REQUESTED_DEPTH_OFFSET: usize =
188    std::mem::offset_of!(BacktraceData, requested_depth);
189pub const BACKTRACE_DATA_FRAME_COUNT_OFFSET: usize =
190    std::mem::offset_of!(BacktraceData, frame_count);
191pub const BACKTRACE_DATA_FLAGS_OFFSET: usize = std::mem::offset_of!(BacktraceData, flags);
192pub const BACKTRACE_DATA_STATUS_OFFSET: usize = std::mem::offset_of!(BacktraceData, status);
193pub const BACKTRACE_DATA_ERROR_CODE_OFFSET: usize = std::mem::offset_of!(BacktraceData, error_code);
194
195#[repr(C, packed)]
196#[derive(
197    Debug, Clone, Copy, FromBytes, KnownLayout, Immutable, Unaligned, Serialize, Deserialize,
198)]
199pub struct BacktraceFrameData {
200    pub module_cookie: u64,
201    /// Module-normalized DWARF PC / ELF virtual address.
202    pub pc: u64,
203    /// Runtime instruction pointer as observed in the target process.
204    pub raw_ip: u64,
205    pub flags: u16,
206    pub reserved: u16,
207    pub reserved2: u32,
208}
209
210pub const BACKTRACE_FRAME_DATA_SIZE: usize = std::mem::size_of::<BacktraceFrameData>();
211pub const BACKTRACE_FRAME_MODULE_COOKIE_OFFSET: usize =
212    std::mem::offset_of!(BacktraceFrameData, module_cookie);
213pub const BACKTRACE_FRAME_PC_OFFSET: usize = std::mem::offset_of!(BacktraceFrameData, pc);
214pub const BACKTRACE_FRAME_RAW_IP_OFFSET: usize = std::mem::offset_of!(BacktraceFrameData, raw_ip);
215pub const BACKTRACE_FRAME_FLAGS_OFFSET: usize = std::mem::offset_of!(BacktraceFrameData, flags);
216
217#[repr(u8)]
218#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
219pub enum BacktraceStatus {
220    Complete = 0,
221    Truncated = 1,
222    DwarfUnavailable = 2,
223    UnsupportedCfi = 3,
224    OffsetsUnavailable = 4,
225    ReadError = 5,
226    InternalError = 6,
227    InvalidFrame = 7,
228    NoUnwindRowsForPc = 8,
229}
230
231impl BacktraceStatus {
232    pub fn from_u8(value: u8) -> Self {
233        match value {
234            0 => Self::Complete,
235            1 => Self::Truncated,
236            2 => Self::DwarfUnavailable,
237            3 => Self::UnsupportedCfi,
238            4 => Self::OffsetsUnavailable,
239            5 => Self::ReadError,
240            6 => Self::InternalError,
241            7 => Self::InvalidFrame,
242            8 => Self::NoUnwindRowsForPc,
243            _ => Self::InternalError,
244        }
245    }
246
247    pub fn label(self) -> &'static str {
248        match self {
249            Self::Complete => "complete",
250            Self::Truncated => "truncated",
251            Self::DwarfUnavailable => "dwarf unavailable",
252            Self::UnsupportedCfi => "unsupported CFI",
253            Self::OffsetsUnavailable => "offsets unavailable",
254            Self::ReadError => "read error",
255            Self::InternalError => "internal error",
256            Self::InvalidFrame => "invalid frame",
257            Self::NoUnwindRowsForPc => "no unwind rows for PC",
258        }
259    }
260}
261
262pub const BACKTRACE_ERROR_NONE: u16 = 0;
263pub const BACKTRACE_ERROR_RETURN_ADDRESS_READ: u16 = 1;
264pub const BACKTRACE_ERROR_FRAME_POINTER_READ: u16 = 2;
265pub const BACKTRACE_ERROR_NEXT_IP_BELOW_USER: u16 = 3;
266pub const BACKTRACE_ERROR_NEXT_IP_KERNEL_LIKE: u16 = 4;
267pub const BACKTRACE_ERROR_NEXT_CFA_ZERO: u16 = 5;
268pub const BACKTRACE_ERROR_NEXT_CFA_NOT_ADVANCING: u16 = 6;
269
270pub fn backtrace_error_label(error_code: u16) -> Option<&'static str> {
271    match error_code {
272        BACKTRACE_ERROR_NONE => None,
273        BACKTRACE_ERROR_RETURN_ADDRESS_READ => Some("return-address-read-failed"),
274        BACKTRACE_ERROR_FRAME_POINTER_READ => Some("frame-pointer-read-failed"),
275        BACKTRACE_ERROR_NEXT_IP_BELOW_USER => Some("next-ip-below-user-range"),
276        BACKTRACE_ERROR_NEXT_IP_KERNEL_LIKE => Some("next-ip-kernel-like"),
277        BACKTRACE_ERROR_NEXT_CFA_ZERO => Some("next-cfa-zero"),
278        BACKTRACE_ERROR_NEXT_CFA_NOT_ADVANCING => Some("next-cfa-not-advancing"),
279        _ => Some("unknown"),
280    }
281}
282
283pub const BACKTRACE_FLAG_RAW: u8 = 0x01;
284pub const BACKTRACE_FLAG_FULL: u8 = 0x02;
285pub const BACKTRACE_FLAG_INLINE: u8 = 0x04;
286/// ExprError instruction data - structured warning for runtime expression failure
287#[repr(C, packed)]
288#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable, Unaligned)]
289pub struct ExprErrorData {
290    pub string_index: u16, // Index into string table for pretty expression text
291    pub error_code: u8,    // Error code (semantic defined by compiler)
292    pub flags: u8,         // Optional flags bitfield (e.g., which side failed)
293    pub failing_addr: u64, // Optional: address involved in failure (0 if unknown)
294}
295
296pub const EXPR_ERROR_DATA_SIZE: usize = std::mem::size_of::<ExprErrorData>();
297pub const EXPR_ERROR_DATA_STRING_INDEX_OFFSET: usize =
298    std::mem::offset_of!(ExprErrorData, string_index);
299pub const EXPR_ERROR_DATA_ERROR_CODE_OFFSET: usize =
300    std::mem::offset_of!(ExprErrorData, error_code);
301pub const EXPR_ERROR_DATA_FLAGS_OFFSET: usize = std::mem::offset_of!(ExprErrorData, flags);
302pub const EXPR_ERROR_DATA_FAILING_ADDR_OFFSET: usize =
303    std::mem::offset_of!(ExprErrorData, failing_addr);
304
305/// End instruction data - marks the end of instruction sequence
306#[repr(C, packed)]
307#[derive(Debug, Clone, Copy, FromBytes, KnownLayout, Immutable, Unaligned)]
308pub struct EndInstructionData {
309    pub total_instructions: u16, // Total number of instructions before this EndInstruction
310    pub execution_status: u8,    // 0=success, 1=partial_failure, 2=complete_failure
311    pub reserved: u8,            // Padding for alignment
312}
313
314pub const END_INSTRUCTION_DATA_OFFSET: usize = INSTRUCTION_HEADER_SIZE;
315pub const END_INSTRUCTION_TOTAL_INSTRUCTIONS_OFFSET: usize =
316    std::mem::offset_of!(EndInstructionData, total_instructions);
317pub const END_INSTRUCTION_EXECUTION_STATUS_OFFSET: usize =
318    std::mem::offset_of!(EndInstructionData, execution_status);
319
320/// High-level instruction representation for compilation and parsing
321#[derive(Debug, Clone, Serialize, Deserialize)]
322pub enum Instruction {
323    PrintStringIndex {
324        string_index: u16,
325    },
326    PrintVariableIndex {
327        var_name_index: u16,
328        type_encoding: TypeKind,
329        type_index: u16, // Index into type table (new field)
330        data: Vec<u8>,
331    },
332    /// Structured runtime expression error/warning
333    ExprError {
334        string_index: u16,
335        error_code: u8,
336        flags: u8,
337        failing_addr: u64,
338    },
339    Backtrace {
340        requested_depth: u8,
341        frame_count: u8,
342        flags: u8,
343        status: BacktraceStatus,
344        error_code: u16,
345        frames: Vec<BacktraceFrameData>,
346    },
347    EndInstruction {
348        total_instructions: u16,
349        execution_status: u8, // 0=success, 1=partial_failure, 2=complete_failure
350    },
351}
352
353impl Instruction {
354    /// Get the instruction type
355    pub fn instruction_type(&self) -> InstructionType {
356        match self {
357            Instruction::PrintStringIndex { .. } => InstructionType::PrintStringIndex,
358            Instruction::PrintVariableIndex { .. } => InstructionType::PrintVariableIndex,
359            Instruction::ExprError { .. } => InstructionType::ExprError,
360            Instruction::Backtrace { .. } => InstructionType::Backtrace,
361            Instruction::EndInstruction { .. } => InstructionType::EndInstruction,
362        }
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369
370    #[test]
371    fn test_instruction_types() {
372        let inst1 = Instruction::PrintStringIndex { string_index: 0 };
373        assert_eq!(inst1.instruction_type(), InstructionType::PrintStringIndex);
374    }
375
376    #[test]
377    fn test_instruction_types_basic() {
378        let inst = Instruction::EndInstruction {
379            total_instructions: 5,
380            execution_status: 0,
381        };
382        assert_eq!(inst.instruction_type(), InstructionType::EndInstruction);
383    }
384
385    #[test]
386    fn backtrace_status_wire_values_and_labels_are_stable() {
387        assert_eq!(BacktraceStatus::UnsupportedCfi as u8, 3);
388        assert_eq!(BacktraceStatus::NoUnwindRowsForPc as u8, 8);
389        assert_eq!(
390            BacktraceStatus::from_u8(8),
391            BacktraceStatus::NoUnwindRowsForPc
392        );
393        assert_eq!(
394            BacktraceStatus::NoUnwindRowsForPc.label(),
395            "no unwind rows for PC"
396        );
397        assert_eq!(
398            BacktraceStatus::from_u8(255),
399            BacktraceStatus::InternalError
400        );
401    }
402
403    #[test]
404    fn protocol_layout_constants_match_wire_format() {
405        assert_eq!(TRACE_EVENT_HEADER_SIZE, 4);
406        assert_eq!(TRACE_EVENT_HEADER_MAGIC_OFFSET, 0);
407        assert_eq!(TRACE_EVENT_MESSAGE_SIZE, 24);
408        assert_eq!(TRACE_EVENT_MESSAGE_TRACE_ID_OFFSET, 0);
409        assert_eq!(TRACE_EVENT_MESSAGE_TIMESTAMP_OFFSET, 8);
410        assert_eq!(TRACE_EVENT_MESSAGE_PID_OFFSET, 16);
411        assert_eq!(TRACE_EVENT_MESSAGE_TID_OFFSET, 20);
412        assert_eq!(INSTRUCTION_HEADER_SIZE, 4);
413        assert_eq!(INSTRUCTION_HEADER_INST_TYPE_OFFSET, 0);
414        assert_eq!(INSTRUCTION_HEADER_DATA_LENGTH_OFFSET, 1);
415        assert_eq!(INSTRUCTION_HEADER_RESERVED_OFFSET, 3);
416        assert_eq!(END_INSTRUCTION_DATA_OFFSET, 4);
417        assert_eq!(END_INSTRUCTION_TOTAL_INSTRUCTIONS_OFFSET, 0);
418        assert_eq!(END_INSTRUCTION_EXECUTION_STATUS_OFFSET, 2);
419        assert_eq!(VARIABLE_READ_ERROR_PAYLOAD_LEN, 12);
420        assert_eq!(VARIABLE_READ_ERROR_PAYLOAD_ERRNO_OFFSET, 0);
421        assert_eq!(VARIABLE_READ_ERROR_PAYLOAD_ADDR_OFFSET, 4);
422        assert_eq!(EXPR_ERROR_DATA_SIZE, 12);
423        assert_eq!(EXPR_ERROR_DATA_STRING_INDEX_OFFSET, 0);
424        assert_eq!(EXPR_ERROR_DATA_ERROR_CODE_OFFSET, 2);
425        assert_eq!(EXPR_ERROR_DATA_FLAGS_OFFSET, 3);
426        assert_eq!(EXPR_ERROR_DATA_FAILING_ADDR_OFFSET, 4);
427        assert_eq!(BACKTRACE_DATA_SIZE, 8);
428        assert_eq!(BACKTRACE_DATA_REQUESTED_DEPTH_OFFSET, 0);
429        assert_eq!(BACKTRACE_DATA_FRAME_COUNT_OFFSET, 1);
430        assert_eq!(BACKTRACE_DATA_FLAGS_OFFSET, 2);
431        assert_eq!(BACKTRACE_DATA_STATUS_OFFSET, 3);
432        assert_eq!(BACKTRACE_DATA_ERROR_CODE_OFFSET, 4);
433        assert_eq!(BACKTRACE_FRAME_DATA_SIZE, 32);
434        assert_eq!(BACKTRACE_FRAME_MODULE_COOKIE_OFFSET, 0);
435        assert_eq!(BACKTRACE_FRAME_PC_OFFSET, 8);
436        assert_eq!(BACKTRACE_FRAME_RAW_IP_OFFSET, 16);
437        assert_eq!(BACKTRACE_FRAME_FLAGS_OFFSET, 24);
438        assert_eq!(PRINT_COMPLEX_FORMAT_DATA_ARG_COUNT_OFFSET, 2);
439        assert_eq!(PRINT_COMPLEX_FORMAT_ARG_VAR_NAME_INDEX_OFFSET, 0);
440        assert_eq!(PRINT_COMPLEX_FORMAT_ARG_TYPE_INDEX_OFFSET, 2);
441        assert_eq!(PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_LEN_OFFSET, 4);
442        assert_eq!(PRINT_COMPLEX_FORMAT_ARG_STATUS_OFFSET, 5);
443        assert_eq!(PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_OFFSET, 6);
444        assert_eq!(PRINT_COMPLEX_FORMAT_ARG_DATA_LEN_SIZE, 2);
445        assert_eq!(PRINT_COMPLEX_FORMAT_ARG_FIXED_HEADER_LEN, 8);
446    }
447}