1use crate::call_tree::{CallTreeManager, NodeId, ThreadCallTree};
7use crate::error::{CallTraceError, Result};
8use crate::register_reader::{ArgumentValue, CapturedArgument};
9use libc;
10use serde::{Deserialize, Serialize};
11use std::fs::File;
12use std::io::Write;
13
14#[derive(Debug, Serialize, Deserialize)]
16pub struct TraceSession {
17 pub metadata: SessionMetadata,
18 pub threads: Vec<ThreadTrace>,
19 pub thread_relationships: Vec<ThreadRelationship>,
20 pub statistics: SessionStatistics,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub crash: Option<CrashInfo>,
23}
24
25#[derive(Debug, Serialize, Deserialize)]
27pub struct CrashInfo {
28 pub signal: i32,
29 pub signal_name: String,
30 pub thread_id: u64,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub fault_address: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub instruction_pointer: Option<String>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub stack_pointer: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub register_context: Option<crate::register_reader::RegisterContext>,
39 pub backtrace: Vec<StackFrame>,
40 pub crash_time: String,
41 pub crash_timestamp: f64,
42}
43
44#[derive(Debug, Serialize, Deserialize)]
46pub struct StackFrame {
47 pub address: String,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub function_name: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub library_name: Option<String>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub offset: Option<String>,
54}
55
56#[derive(Debug, Serialize, Deserialize)]
58pub struct SessionMetadata {
59 pub start_time: String,
60 pub end_time: String,
61 pub duration_ms: f64,
62 pub process_info: ProcessInfo,
63 pub calltrace_version: String,
64}
65
66#[derive(Debug, Serialize, Deserialize)]
68pub struct ProcessInfo {
69 pub pid: u32,
70 pub architecture: String,
71 pub executable_path: Option<String>,
72 pub environment_variables: std::collections::HashMap<String, String>,
73}
74
75#[derive(Debug, Serialize, Deserialize)]
77pub struct ThreadTrace {
78 pub thread_id: u64,
79 pub thread_name: String,
80 pub is_main_thread: bool,
81 pub start_time: String,
82 pub end_time: Option<String>,
83 pub parent_thread_id: Option<u64>,
84 pub creation_function: Option<String>,
85 pub statistics: ThreadStatistics,
86 pub call_tree: Option<CallNodeJson>,
87}
88
89#[derive(Debug, Serialize, Deserialize)]
91pub struct ThreadRelationship {
92 pub parent_thread_id: u64,
93 pub child_thread_id: u64,
94 pub creation_function: String,
95 pub creation_time: String,
96}
97
98#[derive(Debug, Serialize, Deserialize)]
100pub struct CallNodeJson {
101 pub id: u32,
102 pub function: String,
103 pub address: String,
104 pub call_site: String,
105 pub start_time: String,
106 pub end_time: Option<String>,
107 pub duration_us: Option<u64>,
108 pub call_depth: usize,
109 pub signature: Option<String>,
110 pub arguments: Vec<ArgumentJson>,
111 pub return_value: Option<ArgumentValueJson>,
112 pub children: Vec<CallNodeJson>,
113
114 pub is_pthread_create: bool,
116 pub created_thread_id: Option<u64>,
117
118 pub source_file: Option<String>,
120 pub line_number: Option<u32>,
121}
122
123#[derive(Debug, Serialize, Deserialize)]
125pub struct ArgumentJson {
126 pub name: String,
127 pub type_name: String,
128 pub value: ArgumentValueJson,
129 pub location: ArgumentLocationJson,
130}
131
132#[derive(Debug, Serialize, Deserialize)]
134#[serde(tag = "type", content = "value")]
135pub enum ArgumentValueJson {
136 Integer(u64),
137 Float(f32),
138 Double(f64),
139 Pointer {
140 address: String,
141 string_value: Option<String>,
142 },
143 String(String),
144 Raw(String), Object(String), Error(String),
147}
148
149#[derive(Debug, Serialize, Deserialize)]
151pub struct ArgumentLocationJson {
152 pub class: String, pub register_index: Option<usize>,
154 pub stack_offset: Option<usize>,
155 pub size: usize,
156}
157
158#[derive(Debug, Serialize, Deserialize)]
160pub struct ThreadStatistics {
161 pub total_function_calls: u64,
162 pub max_call_depth: usize,
163 pub total_errors: u64,
164}
165
166#[derive(Debug, Serialize, Deserialize)]
168pub struct SessionStatistics {
169 pub total_threads: u64,
170 pub total_function_calls: u64,
171 pub total_nodes: u64,
172 pub session_duration_us: u64,
173}
174
175pub struct JsonOutputGenerator {
177 pretty_print: bool,
178 include_arguments: bool,
179 include_source_info: bool,
180}
181
182impl JsonOutputGenerator {
183 pub fn new() -> Self {
185 Self {
186 pretty_print: true,
187 include_arguments: true,
188 include_source_info: true,
189 }
190 }
191
192 pub fn configure(
194 &mut self,
195 pretty_print: bool,
196 include_arguments: bool,
197 include_source_info: bool,
198 ) {
199 self.pretty_print = pretty_print;
200 self.include_arguments = include_arguments;
201 self.include_source_info = include_source_info;
202 }
203
204 pub fn generate_output(&self, manager: &CallTreeManager) -> Result<TraceSession> {
206 let stats = manager.get_statistics();
207
208 let current_time_us = std::time::SystemTime::now()
210 .duration_since(std::time::UNIX_EPOCH)
211 .unwrap_or_default()
212 .as_micros() as u64;
213 let session_start_us = current_time_us.saturating_sub(stats.session_duration_us);
214
215 let metadata = SessionMetadata {
216 start_time: format_timestamp(session_start_us),
217 end_time: format_timestamp(current_time_us),
218 duration_ms: stats.session_duration_us as f64 / 1000.0,
219 process_info: ProcessInfo {
220 pid: std::process::id(),
221 architecture: crate::string_constants::X86_64.to_string(),
222 executable_path: None, environment_variables: collect_environment_variables(),
224 },
225 calltrace_version: env!("CARGO_PKG_VERSION").to_string(),
226 };
227
228 let mut threads = Vec::new();
230 let thread_ids = manager.get_thread_ids();
231
232 for thread_id in thread_ids {
233 if let Some(thread_tree) = manager.get_thread_tree(thread_id) {
234 let tree = thread_tree.read().unwrap();
235 let thread_trace = self.generate_thread_trace(&tree, manager)?;
236 threads.push(thread_trace);
237 }
238 }
239
240 let thread_relationships = Vec::new(); let session_stats = SessionStatistics {
245 total_threads: stats.total_threads,
246 total_function_calls: 0, total_nodes: stats.total_nodes,
248 session_duration_us: stats.session_duration_us,
249 };
250
251 Ok(TraceSession {
252 metadata,
253 threads,
254 thread_relationships,
255 statistics: session_stats,
256 crash: None,
257 })
258 }
259
260 fn generate_thread_trace(
262 &self,
263 thread_tree: &ThreadCallTree,
264 manager: &CallTreeManager,
265 ) -> Result<ThreadTrace> {
266 let call_tree = if let Some(root_id) = thread_tree.root_node {
267 self.generate_call_node_json(root_id, manager)?
268 } else {
269 None
270 };
271
272 Ok(ThreadTrace {
273 thread_id: thread_tree.thread_id,
274 thread_name: thread_tree.thread_name.clone(),
275 is_main_thread: thread_tree.is_main_thread,
276 start_time: format_timestamp(thread_tree.start_time),
277 end_time: thread_tree.end_time.map(format_timestamp),
278 parent_thread_id: thread_tree.parent_thread_id,
279 creation_function: thread_tree.creation_function.clone(),
280 statistics: ThreadStatistics {
281 total_function_calls: thread_tree.total_calls,
282 max_call_depth: thread_tree.max_depth,
283 total_errors: thread_tree.total_errors,
284 },
285 call_tree,
286 })
287 }
288
289 fn generate_call_node_json(
291 &self,
292 node_id: NodeId,
293 manager: &CallTreeManager,
294 ) -> Result<Option<CallNodeJson>> {
295 let node_ref = manager.get_any_node(node_id).ok_or_else(|| {
296 CallTraceError::InvalidArgument(format!("Node {} not found", node_id))
297 })?;
298
299 let node = node_ref.read().unwrap();
300
301 let arguments = if self.include_arguments {
303 node.arguments
304 .iter()
305 .map(|arg| self.convert_argument_to_json(arg))
306 .collect()
307 } else {
308 Vec::new()
309 };
310
311 let return_value = if self.include_arguments {
313 node.return_value
314 .as_ref()
315 .map(|rv| self.convert_return_value_to_json(rv))
316 } else {
317 None
318 };
319
320 let mut children = Vec::new();
322 for &child_id in &node.children {
323 if let Some(child_json) = self.generate_call_node_json(child_id, manager)? {
324 children.push(child_json);
325 }
326 }
327
328 let (source_file, line_number) = if self.include_source_info {
330 node.function_info.as_ref().map_or((None, None), |info| {
331 (info.source_file.clone(), info.line_number)
332 })
333 } else {
334 (None, None)
335 };
336
337 let function_display_name = if !node.function_name.starts_with("0x") {
339 node.function_name.clone()
341 } else {
342 crate::format_address_with_prefix("func", node.function_address & 0xFFFF)
344 };
345
346 Ok(Some(CallNodeJson {
347 id: node.id,
348 function: function_display_name,
349 address: crate::format_address(node.function_address),
350 call_site: crate::format_address(node.call_site),
351 start_time: format_timestamp(node.start_time),
352 end_time: node.end_time.map(format_timestamp),
353 duration_us: node.duration_us,
354 call_depth: node.call_depth,
355 signature: node.function_info.as_ref().map(|_| "TODO".to_string()), arguments,
357 return_value,
358 children,
359 is_pthread_create: node.is_pthread_create,
360 created_thread_id: node.created_thread_id,
361 source_file,
362 line_number,
363 }))
364 }
365
366 fn convert_return_value_to_json(&self, return_value: &ArgumentValue) -> ArgumentValueJson {
368 match return_value {
369 ArgumentValue::Integer(val) => ArgumentValueJson::Integer(*val),
370 ArgumentValue::Float(val) => ArgumentValueJson::Float(*val),
371 ArgumentValue::Double(val) => ArgumentValueJson::Double(*val),
372 ArgumentValue::Pointer(addr) => ArgumentValueJson::Pointer {
373 address: crate::format_address(*addr),
374 string_value: None, },
376 ArgumentValue::String(s) => ArgumentValueJson::String(s.clone()),
377 ArgumentValue::Raw(bytes) => ArgumentValueJson::Raw(hex::encode(bytes)),
378 ArgumentValue::Struct {
379 type_name,
380 members: _,
381 size,
382 } => {
383 ArgumentValueJson::Object(format!("struct {} (size: {})", type_name, size))
385 }
386 ArgumentValue::Array {
387 element_type,
388 elements: _,
389 count,
390 element_size,
391 } => {
392 ArgumentValueJson::Object(format!(
394 "{}[{}] (element_size: {})",
395 element_type, count, element_size
396 ))
397 }
398 ArgumentValue::Union {
399 type_name,
400 raw_data: _,
401 size,
402 } => ArgumentValueJson::Object(format!("union {} (size: {})", type_name, size)),
403 ArgumentValue::Null => ArgumentValueJson::Pointer {
404 address: crate::string_constants::NULL_ADDRESS.to_string(),
405 string_value: Some(crate::string_constants::NULL_STRING.to_string()),
406 },
407 ArgumentValue::Unknown {
408 type_name,
409 raw_data,
410 error,
411 } => {
412 let error_info = error
413 .as_ref()
414 .map(|e| format!(": {}", e))
415 .unwrap_or_default();
416 ArgumentValueJson::Object(format!(
417 "unknown {} (size: {}){}",
418 type_name,
419 raw_data.len(),
420 error_info
421 ))
422 }
423 }
424 }
425
426 fn convert_argument_to_json(&self, arg: &CapturedArgument) -> ArgumentJson {
428 let value_json = if arg.valid {
429 match &arg.value {
430 ArgumentValue::Integer(val) => ArgumentValueJson::Integer(*val),
431 ArgumentValue::Float(val) => ArgumentValueJson::Float(*val),
432 ArgumentValue::Double(val) => ArgumentValueJson::Double(*val),
433 ArgumentValue::Pointer(addr) => ArgumentValueJson::Pointer {
434 address: crate::format_address(*addr),
435 string_value: None, },
437 ArgumentValue::String(s) => ArgumentValueJson::String(s.clone()),
438 ArgumentValue::Raw(bytes) => ArgumentValueJson::Raw(hex::encode(bytes)),
439 ArgumentValue::Struct {
440 type_name,
441 members: _,
442 size,
443 } => {
444 ArgumentValueJson::Object(format!("struct {} (size: {})", type_name, size))
446 }
447 ArgumentValue::Array {
448 element_type,
449 elements: _,
450 count,
451 element_size,
452 } => {
453 ArgumentValueJson::Object(format!(
455 "{}[{}] (element_size: {})",
456 element_type, count, element_size
457 ))
458 }
459 ArgumentValue::Union {
460 type_name,
461 raw_data: _,
462 size,
463 } => ArgumentValueJson::Object(format!("union {} (size: {})", type_name, size)),
464 ArgumentValue::Null => ArgumentValueJson::Pointer {
465 address: crate::string_constants::NULL_ADDRESS.to_string(),
466 string_value: Some(crate::string_constants::NULL_STRING.to_string()),
467 },
468 ArgumentValue::Unknown {
469 type_name,
470 raw_data,
471 error,
472 } => {
473 let error_info = error
474 .as_ref()
475 .map(|e| format!(": {}", e))
476 .unwrap_or_default();
477 ArgumentValueJson::Object(format!(
478 "unknown {} (size: {}){}",
479 type_name,
480 raw_data.len(),
481 error_info
482 ))
483 }
484 }
485 } else {
486 ArgumentValueJson::Error(
487 arg.error
488 .clone()
489 .unwrap_or_else(|| crate::string_constants::UNKNOWN_ERROR.to_string()),
490 )
491 };
492
493 ArgumentJson {
494 name: arg.name.clone(),
495 type_name: arg.type_name.clone(),
496 value: value_json,
497 location: ArgumentLocationJson {
498 class: format!("{:?}", arg.location.class).to_lowercase(),
499 register_index: arg.location.register_index,
500 stack_offset: arg.location.stack_offset,
501 size: arg.location.size,
502 },
503 }
504 }
505
506 pub fn write_to_file(&self, trace_session: &TraceSession, file_path: &str) -> Result<()> {
508 let mut file = File::create(file_path)
509 .map_err(|e| CallTraceError::JsonError(serde_json::Error::io(e)))?;
510
511 if self.pretty_print {
512 let json_string = serde_json::to_string_pretty(trace_session)?;
513 file.write_all(json_string.as_bytes())
514 .map_err(|e| CallTraceError::JsonError(serde_json::Error::io(e)))?;
515 } else {
516 serde_json::to_writer(&mut file, trace_session)?;
517 }
518
519 Ok(())
520 }
521
522 pub fn to_string(&self, trace_session: &TraceSession) -> Result<String> {
524 if self.pretty_print {
525 serde_json::to_string_pretty(trace_session).map_err(CallTraceError::JsonError)
526 } else {
527 serde_json::to_string(trace_session).map_err(CallTraceError::JsonError)
528 }
529 }
530}
531
532impl Default for JsonOutputGenerator {
533 fn default() -> Self {
534 Self::new()
535 }
536}
537
538#[allow(dead_code)]
540fn get_function_name_from_address(address: u64) -> Option<String> {
541 use libc::{dladdr, Dl_info};
542 use std::ffi::{c_void, CStr};
543
544 let mut info: Dl_info = unsafe { std::mem::zeroed() };
545 let result = unsafe { dladdr(address as *const c_void, &mut info) };
546
547 if result != 0 && !info.dli_sname.is_null() {
548 unsafe {
549 let name = CStr::from_ptr(info.dli_sname)
550 .to_string_lossy()
551 .into_owned();
552
553 Some(demangle_function_name(&name))
555 }
556 } else {
557 None
558 }
559}
560
561#[allow(dead_code)]
563fn demangle_function_name(mangled: &str) -> String {
564 if mangled.starts_with("_Z") {
566 mangled.to_string() } else {
570 mangled.to_string()
571 }
572}
573
574fn format_timestamp(timestamp_us: u64) -> String {
576 if timestamp_us == 0 {
577 return "0".to_string();
578 }
579
580 let seconds = timestamp_us / 1_000_000;
582 let microseconds = timestamp_us % 1_000_000;
583
584 if microseconds == 0 {
586 format!("{}", seconds)
587 } else {
588 format!("{}.{:06}", seconds, microseconds)
589 }
590}
591
592fn collect_environment_variables() -> std::collections::HashMap<String, String> {
594 let mut env_vars = std::collections::HashMap::new();
595
596 let important_vars = [
598 "PATH",
599 "LD_LIBRARY_PATH",
600 "LD_PRELOAD",
601 "USER",
602 "HOME",
603 "SHELL",
604 "LANG",
605 "LC_ALL",
606 "LC_CTYPE",
607 "TERM",
608 "DISPLAY",
609 "PWD",
610 "TMPDIR",
611 "TMP",
612 "TEMP",
613 "CALLTRACE_OUTPUT",
615 "CALLTRACE_CAPTURE_ARGS",
616 "CALLTRACE_MAX_DEPTH",
617 "CALLTRACE_PRETTY_JSON",
618 "CALLTRACE_DEBUG",
619 "CALLTRACE_FILTER",
620 "CARGO_TARGET_DIR",
622 "RUST_LOG",
623 "RUST_BACKTRACE",
624 "DEBUG",
625 "VERBOSE",
626 "CC",
628 "CXX",
629 "CFLAGS",
630 "CXXFLAGS",
631 "LDFLAGS",
632 ];
633
634 for var_name in &important_vars {
636 if let Ok(value) = std::env::var(var_name) {
637 env_vars.insert(var_name.to_string(), value);
638 }
639 }
640
641 for (key, value) in std::env::vars() {
643 if key.starts_with("CALLTRACE_") && !env_vars.contains_key(&key) {
644 env_vars.insert(key, value);
645 }
646 }
647
648 const MAX_ENV_VARS: usize = 50;
650 if env_vars.len() > MAX_ENV_VARS {
651 let mut limited_vars = std::collections::HashMap::new();
653 for (key, value) in env_vars.into_iter().take(MAX_ENV_VARS) {
654 limited_vars.insert(key, value);
655 }
656 env_vars = limited_vars;
657 }
658
659 env_vars
660}
661
662#[allow(dead_code)]
664mod hex {
665 pub fn encode(bytes: &[u8]) -> String {
666 bytes.iter().map(|b| format!("{:02x}", b)).collect()
667 }
668}