1use ghostscope_protocol::{
2 trace_event::{backtrace_error_label, BacktraceStatus},
3 ParsedInstruction, ParsedTraceEvent,
4};
5
6#[derive(Debug, Clone)]
12pub struct UiTraceEvent {
13 pub trace_id: u64,
14 pub timestamp: u64,
15 pub pid: u32,
16 pub tid: u32,
17 pub items: Vec<TraceDisplayItem>,
18 pub execution_status: Option<u8>,
19}
20
21impl UiTraceEvent {
22 pub fn from_protocol_event(event: &ParsedTraceEvent) -> Self {
23 let execution_status = event.instructions.iter().rev().find_map(|instruction| {
24 if let ghostscope_protocol::ParsedInstruction::EndInstruction {
25 execution_status, ..
26 } = instruction
27 {
28 Some(*execution_status)
29 } else {
30 None
31 }
32 });
33
34 Self {
35 trace_id: event.trace_id,
36 timestamp: event.timestamp,
37 pid: event.pid,
38 tid: event.tid,
39 items: protocol_instructions_to_display_items(&event.instructions),
40 execution_status,
41 }
42 }
43
44 pub fn text_event(
45 trace_id: u64,
46 timestamp: u64,
47 pid: u32,
48 tid: u32,
49 content: String,
50 execution_status: Option<u8>,
51 ) -> Self {
52 Self {
53 trace_id,
54 timestamp,
55 pid,
56 tid,
57 items: vec![TraceDisplayItem::Text { content }],
58 execution_status,
59 }
60 }
61
62 pub fn to_formatted_output(&self) -> Vec<String> {
63 self.items
64 .iter()
65 .flat_map(TraceDisplayItem::to_formatted_output)
66 .collect()
67 }
68
69 pub fn is_error(&self) -> bool {
70 self.execution_status
71 .is_some_and(|status| status == 1 || status == 2)
72 || self.items.iter().any(|item| match item {
73 TraceDisplayItem::ExprError(_) => true,
74 TraceDisplayItem::Backtrace(backtrace) => {
75 backtrace.status != BacktraceStatus::Complete
76 && backtrace.status != BacktraceStatus::Truncated
77 }
78 _ => false,
79 })
80 }
81}
82
83#[derive(Debug, Clone)]
84pub enum TraceDisplayItem {
85 Text { content: String },
86 FormattedText { content: String },
87 Variable(VariableDisplay),
88 ComplexVariable(ComplexVariableDisplay),
89 ExprError(ExprErrorDisplay),
90 Backtrace(BacktraceDisplay),
91}
92
93impl TraceDisplayItem {
94 pub fn to_formatted_output(&self) -> Vec<String> {
95 match self {
96 Self::Text { content } => vec![content.clone()],
97 Self::FormattedText { content } => vec![content.clone()],
98 Self::Variable(variable) => vec![variable.to_formatted_output()],
99 Self::ComplexVariable(variable) => vec![variable.to_formatted_output()],
100 Self::ExprError(error) => vec![error.to_formatted_output()],
101 Self::Backtrace(backtrace) => backtrace.to_formatted_output(),
102 }
103 }
104}
105
106#[derive(Debug, Clone)]
107pub struct VariableDisplay {
108 pub name: String,
109 pub type_name: String,
110 pub formatted_value: String,
111}
112
113impl VariableDisplay {
114 pub fn to_formatted_output(&self) -> String {
115 format!(
116 "{} ({}): {}",
117 self.name, self.type_name, self.formatted_value
118 )
119 }
120}
121
122#[derive(Debug, Clone)]
123pub struct ComplexVariableDisplay {
124 pub name: String,
125 pub access_path: String,
126 pub type_index: u16,
127 pub formatted_value: String,
128}
129
130impl ComplexVariableDisplay {
131 pub fn display_name(&self) -> &str {
132 if self.access_path.is_empty() {
133 &self.name
134 } else {
135 &self.access_path
136 }
137 }
138
139 pub fn to_formatted_output(&self) -> String {
140 self.formatted_value.clone()
141 }
142}
143
144#[derive(Debug, Clone)]
145pub struct ExprErrorDisplay {
146 pub expr: String,
147 pub error_code: u8,
148 pub flags: u8,
149 pub failing_addr: u64,
150}
151
152impl ExprErrorDisplay {
153 pub fn reason(&self) -> &'static str {
154 match self.error_code {
155 1 => "null deref",
156 2 => "read error",
157 3 => "access error",
158 4 => "truncated",
159 5 => "offsets unavailable",
160 6 => "zero length",
161 _ => "error",
162 }
163 }
164
165 pub fn readable_flags(&self) -> Option<String> {
166 if self.flags == 0 {
167 return None;
168 }
169 let mut tags: Vec<&'static str> = Vec::new();
170 let is_memcmp = self.expr.contains("memcmp(");
171 let is_strncmp = self.expr.contains("strncmp(") || self.expr.contains("starts_with(");
172 if is_memcmp {
173 if (self.flags & 0x01) != 0 {
174 tags.push("first-arg read-fail");
175 }
176 if (self.flags & 0x02) != 0 {
177 tags.push("second-arg read-fail");
178 }
179 if (self.flags & 0x04) != 0 {
180 tags.push("len-clamped");
181 }
182 if (self.flags & 0x08) != 0 {
183 tags.push("len=0");
184 }
185 } else if is_strncmp {
186 if (self.flags & 0x01) != 0 {
187 tags.push("read-fail");
188 }
189 if (self.flags & 0x04) != 0 {
190 tags.push("len-clamped");
191 }
192 if (self.flags & 0x08) != 0 {
193 tags.push("len=0");
194 }
195 } else {
196 return Some(format!("0x{:02x}", self.flags));
197 }
198
199 (!tags.is_empty()).then(|| tags.join(","))
200 }
201
202 pub fn addr_text(&self) -> String {
203 if self.failing_addr != 0 {
204 format!("at 0x{:016x}", self.failing_addr)
205 } else {
206 "at NULL".to_string()
207 }
208 }
209
210 pub fn to_formatted_output(&self) -> String {
211 let base = format!(
212 "ExprError: {} ({} {}",
213 self.expr,
214 self.reason(),
215 self.addr_text()
216 );
217 match self.readable_flags() {
218 Some(flags) => format!("{base}, flags: {flags})"),
219 None => format!("{base})"),
220 }
221 }
222}
223
224fn protocol_instructions_to_display_items(
225 instructions: &[ParsedInstruction],
226) -> Vec<TraceDisplayItem> {
227 let mut items = Vec::new();
228 let mut index = 0usize;
229
230 while index < instructions.len() {
231 match &instructions[index] {
232 ParsedInstruction::PrintString { content } => {
233 if content.contains("{}") {
234 let (formatted, consumed) =
235 format_string_with_variable_items(content, instructions, index + 1);
236 items.push(TraceDisplayItem::FormattedText { content: formatted });
237 index += consumed;
238 } else {
239 items.push(TraceDisplayItem::Text {
240 content: content.clone(),
241 });
242 index += 1;
243 }
244 }
245 ParsedInstruction::PrintVariable {
246 name,
247 type_encoding,
248 formatted_value,
249 ..
250 } => {
251 items.push(TraceDisplayItem::Variable(VariableDisplay {
252 name: name.clone(),
253 type_name: format!("{type_encoding:?}"),
254 formatted_value: formatted_value.clone(),
255 }));
256 index += 1;
257 }
258 ParsedInstruction::ExprError {
259 expr,
260 error_code,
261 flags,
262 failing_addr,
263 } => {
264 items.push(TraceDisplayItem::ExprError(ExprErrorDisplay {
265 expr: expr.clone(),
266 error_code: *error_code,
267 flags: *flags,
268 failing_addr: *failing_addr,
269 }));
270 index += 1;
271 }
272 ParsedInstruction::PrintComplexFormat { formatted_output } => {
273 items.push(TraceDisplayItem::FormattedText {
274 content: formatted_output.clone(),
275 });
276 index += 1;
277 }
278 ParsedInstruction::PrintComplexVariable {
279 name,
280 access_path,
281 type_index,
282 formatted_value,
283 ..
284 } => {
285 items.push(TraceDisplayItem::ComplexVariable(ComplexVariableDisplay {
286 name: name.clone(),
287 access_path: access_path.clone(),
288 type_index: *type_index,
289 formatted_value: formatted_value.clone(),
290 }));
291 index += 1;
292 }
293 ParsedInstruction::Backtrace { .. } => {
294 index += 1;
295 }
296 ParsedInstruction::EndInstruction { .. } => {
297 index += 1;
298 }
299 }
300 }
301
302 items
303}
304
305fn format_string_with_variable_items(
306 format_string: &str,
307 instructions: &[ParsedInstruction],
308 start_index: usize,
309) -> (String, usize) {
310 let placeholder_count = format_string.matches("{}").count();
311 let mut consumed = 1;
312 let mut result = String::with_capacity(format_string.len());
313 let mut remaining = format_string;
314
315 for instruction_index in start_index..(start_index + placeholder_count).min(instructions.len())
316 {
317 let Some(pos) = remaining.find("{}") else {
318 break;
319 };
320
321 if let Some(ParsedInstruction::PrintVariable {
322 formatted_value, ..
323 }) = instructions.get(instruction_index)
324 {
325 result.push_str(&remaining[..pos]);
326 result.push_str(formatted_value);
327 consumed += 1;
328 remaining = &remaining[pos + 2..];
329 } else {
330 break;
331 }
332 }
333 result.push_str(remaining);
334
335 (result, consumed)
336}
337
338#[derive(Debug, Clone)]
339pub struct BacktraceDisplay {
340 pub requested_depth: u8,
341 pub physical_frame_count: usize,
342 pub status: BacktraceStatus,
343 pub error_code: u16,
344 pub raw: bool,
345 pub frames: Vec<BacktraceDisplayFrame>,
346}
347
348impl BacktraceDisplay {
349 pub fn header_text(&self) -> String {
350 let frame_word = if self.physical_frame_count == 1 {
351 "frame"
352 } else {
353 "frames"
354 };
355 format!(
356 "backtrace: {}, {} {} (max {})",
357 self.status.label(),
358 self.physical_frame_count,
359 frame_word,
360 self.requested_depth
361 )
362 }
363
364 pub fn stopped_text(&self) -> Option<String> {
365 if self.status == BacktraceStatus::Complete {
366 return None;
367 }
368
369 let suffix = match backtrace_error_label(self.error_code) {
370 Some("unknown") => format!(" (code={})", self.error_code),
371 Some(label) => format!(" ({label}, code={})", self.error_code),
372 None => String::new(),
373 };
374 Some(format!("stopped: {}{}", self.status.label(), suffix))
375 }
376
377 pub fn to_formatted_output(&self) -> Vec<String> {
378 let mut output = Vec::with_capacity(self.frames.len() + 2);
379 output.push(self.header_text());
380 output.extend(
381 self.frames
382 .iter()
383 .map(BacktraceDisplayFrame::to_formatted_output),
384 );
385 if let Some(stopped) = self.stopped_text() {
386 output.push(stopped);
387 }
388 output
389 }
390}
391
392#[derive(Debug, Clone)]
393pub struct BacktraceDisplayFrame {
394 pub index: usize,
395 pub inline: bool,
396 pub function: Option<String>,
397 pub parameters: Vec<String>,
398 pub address: Option<String>,
399 pub location: Option<String>,
400 pub module: String,
401 pub raw_ip: Option<u64>,
402 pub cookie: Option<u64>,
403 pub flags: Option<u16>,
404}
405
406impl BacktraceDisplayFrame {
407 pub fn to_formatted_output(&self) -> String {
408 let mut line = String::from(" #");
409 line.push_str(&self.index.to_string());
410 if self.inline {
411 line.push_str(".inline");
412 }
413 line.push(' ');
414
415 if let Some(function) = &self.function {
416 line.push_str(function);
417 if !self.parameters.is_empty() {
418 line.push('(');
419 line.push_str(&self.parameters.join(", "));
420 line.push(')');
421 }
422 } else if let Some(address) = &self.address {
423 line.push_str(address);
424 } else {
425 line.push_str("<unknown function>");
426 }
427
428 if let Some(location) = &self.location {
429 line.push_str(" at ");
430 line.push_str(location);
431 } else if self.function.is_some() {
432 line.push_str(" at ??");
433 }
434 line.push_str(" [");
435 line.push_str(&self.module);
436 line.push(']');
437
438 if let Some(raw_ip) = self.raw_ip {
439 line.push_str(&format!(" raw=0x{raw_ip:x}"));
440 }
441 if let Some(cookie) = self.cookie {
442 line.push_str(&format!(" cookie=0x{cookie:016x}"));
443 }
444 if let Some(flags) = self.flags {
445 line.push_str(&format!(" flags=0x{flags:x}"));
446 }
447
448 line
449 }
450}