1#[derive(Debug, Clone)]
3pub struct SourceCodeInfo {
4 pub file_path: String,
5 pub current_line: Option<usize>,
6}
7
8#[derive(Debug, Clone)]
10pub struct TargetDebugInfo {
11 pub target: String,
12 pub target_type: TargetType,
13 pub file_path: Option<String>,
14 pub line_number: Option<u32>,
15 pub function_name: Option<String>,
16 pub modules: Vec<ModuleDebugInfo>, }
18
19impl TargetDebugInfo {
20 pub fn format_for_display(&self, verbose: bool) -> String {
22 let mut result = String::new();
23
24 let module_count = self.modules.len();
26 let total_addresses: usize = self
27 .modules
28 .iter()
29 .map(|module| module.address_mappings.len())
30 .sum();
31
32 let header_prefix = match self.target_type {
34 TargetType::Function => "š§ Function Debug Info",
35 TargetType::SourceLocation => "š Line Debug Info",
36 TargetType::Address => "š Address Debug Info",
37 };
38 result.push_str(&format!(
39 "{header_prefix}: {} ({} modules, {} traceable addresses)\n\n",
40 self.target, module_count, total_addresses
41 ));
42
43 for (module_idx, module) in self.modules.iter().enumerate() {
45 let is_last_module = module_idx == self.modules.len() - 1;
46 result.push_str(&module.format_for_display(
47 is_last_module,
48 &self.file_path,
49 self.line_number,
50 verbose,
51 ));
52 }
53
54 if let TargetType::Address = self.target_type {
56 let example_addr = self
58 .modules
59 .iter()
60 .flat_map(|m| m.address_mappings.iter())
61 .map(|m| m.address)
62 .next();
63 if let Some(addr) = example_addr {
64 result.push_str("\nš” Tips:\n");
65 result.push_str(&format!(
66 " - In '-t <module>' mode: use `trace 0x{addr:x} {{ ... }}` (defaults to that module)\n"
67 ));
68 result.push_str(&format!(
69 " - In '-p <pid>' mode: default module is the main executable; for library addresses, start GhostScope with '-t <that .so>' then use `trace 0x{addr:x} {{ ... }}`\n"
70 ));
71 }
72 }
73
74 result
75 }
76
77 pub fn format_for_display_styled(&self, verbose: bool) -> Vec<ratatui::text::Line<'static>> {
79 use crate::components::command_panel::style_builder::StyledLineBuilder;
80 use ratatui::text::Line;
81
82 let mut lines = Vec::new();
83
84 let total_addresses: usize = self.modules.iter().map(|m| m.address_mappings.len()).sum();
86 let header_prefix = match self.target_type {
87 TargetType::Function => "š§ Function Debug Info",
88 TargetType::SourceLocation => "š Line Debug Info",
89 TargetType::Address => "š Address Debug Info",
90 };
91 lines.push(
92 StyledLineBuilder::new()
93 .title(format!(
94 "{header_prefix}: {} ({} modules, {} addresses)",
95 self.target,
96 self.modules.len(),
97 total_addresses
98 ))
99 .build(),
100 );
101 lines.push(Line::from(""));
102
103 for (idx, module) in self.modules.iter().enumerate() {
104 let is_last = idx + 1 == self.modules.len();
105 lines.extend(module.format_for_display_styled(
106 is_last,
107 &self.file_path,
108 self.line_number,
109 verbose,
110 ));
111 }
112
113 if let TargetType::Address = self.target_type {
115 if let Some(addr) = self
117 .modules
118 .iter()
119 .flat_map(|m| m.address_mappings.iter())
120 .map(|m| m.address)
121 .next()
122 {
123 lines.push(Line::from(""));
124 lines.push(
125 StyledLineBuilder::new()
126 .styled(
127 "š” Tips:",
128 crate::components::command_panel::style_builder::StylePresets::SECTION,
129 )
130 .build(),
131 );
132 lines.push(
133 StyledLineBuilder::new()
134 .text(" - In '-t <module>' mode: use ")
135 .value(format!("trace 0x{addr:x} {{ ... }}"))
136 .text(" (defaults to that module)")
137 .build(),
138 );
139 lines.push(
140 StyledLineBuilder::new()
141 .text(" - In '-p <pid>' mode: default module is main executable; for library addresses, start with '-t <that .so>' then use ")
142 .value(format!("trace 0x{addr:x} {{ ... }}"))
143 .build(),
144 );
145 }
146 }
147
148 lines
149 }
150}
151
152#[derive(Debug, Clone)]
154pub struct ModuleDebugInfo {
155 pub binary_path: String,
156 pub address_mappings: Vec<AddressMapping>,
157}
158
159impl ModuleDebugInfo {
160 pub fn format_for_display(
162 &self,
163 is_last_module: bool,
164 source_file: &Option<String>,
165 source_line: Option<u32>,
166 verbose: bool,
167 ) -> String {
168 let mut result = String::new();
169
170 result.push_str(&format!("š¦ {}", &self.binary_path));
172
173 if let Some(ref file) = source_file {
175 if let Some(line) = source_line {
176 result.push_str(&format!(" @ {file}:{line}\n"));
177 } else {
178 result.push_str(&format!(" @ {file}\n"));
179 }
180 } else {
181 result.push('\n');
182 }
183
184 for (addr_idx, mapping) in self.address_mappings.iter().enumerate() {
185 let is_last_addr = addr_idx == self.address_mappings.len() - 1;
186 let addr_prefix = match (is_last_module, is_last_addr) {
187 (true, true) => " āā",
188 (true, false) => " āā",
189 (false, true) => "ā āā",
190 (false, false) => "ā āā",
191 };
192
193 let mut pc_description = if let Some(i) = mapping.index {
195 format!("[{}] šÆ 0x{:x}", i, mapping.address)
196 } else {
197 format!("šÆ 0x{:x}", mapping.address)
198 };
199 if let Some(is_inline) = mapping.is_inline {
200 pc_description
201 .push_str(&format!(" ā {}", if is_inline { "inline" } else { "call" }));
202 }
203 if let (Some(ref file), Some(line)) = (&mapping.source_file, mapping.source_line) {
204 pc_description.push_str(&format!(" @ {file}:{line}"));
205 }
206
207 result.push_str(&format!("{addr_prefix} {pc_description}\n"));
208
209 if !mapping.parameters.is_empty() {
211 let param_prefix = match (is_last_module, is_last_addr) {
212 (true, true) => " āā",
213 (true, false) => " ā āā",
214 (false, true) => "ā āā",
215 (false, false) => "ā ā āā",
216 };
217
218 result.push_str(&format!("{param_prefix} š„ Parameters\n"));
219
220 for (param_idx, param) in mapping.parameters.iter().enumerate() {
221 let is_last_param =
222 param_idx == mapping.parameters.len() - 1 && mapping.variables.is_empty();
223 let item_prefix = match (is_last_module, is_last_addr, is_last_param) {
224 (true, true, true) => " ā āā",
225 (true, true, false) => " ā āā",
226 (true, false, true) => " ā ā āā",
227 (true, false, false) => " ā ā āā",
228 (false, true, true) => "ā ā āā",
229 (false, true, false) => "ā ā āā",
230 (false, false, true) => "ā ā ā āā",
231 (false, false, false) => "ā ā ā āā",
232 };
233
234 let param_line = Self::format_variable_line(param, verbose);
235
236 result.push_str(&Self::wrap_long_line(
237 &format!("{item_prefix} {param_line}"),
238 80,
239 item_prefix,
240 ));
241 }
242 }
243
244 if !mapping.variables.is_empty() {
246 let var_prefix = match (is_last_module, is_last_addr) {
247 (true, true) => " āā",
248 (true, false) => " ā āā",
249 (false, true) => "ā āā",
250 (false, false) => "ā ā āā",
251 };
252
253 result.push_str(&format!("{var_prefix} š¦ Variables\n"));
254
255 for (var_idx, var) in mapping.variables.iter().enumerate() {
256 let is_last_var = var_idx == mapping.variables.len() - 1;
257 let item_prefix = match (is_last_module, is_last_addr, is_last_var) {
258 (true, true, true) => " āā",
259 (true, true, false) => " āā",
260 (true, false, true) => " ā āā",
261 (true, false, false) => " ā āā",
262 (false, true, true) => "ā āā",
263 (false, true, false) => "ā āā",
264 (false, false, true) => "ā ā āā",
265 (false, false, false) => "ā ā āā",
266 };
267
268 let var_line = Self::format_variable_line(var, verbose);
269
270 result.push_str(&Self::wrap_long_line(
271 &format!("{item_prefix} {var_line}"),
272 80,
273 item_prefix,
274 ));
275 }
276 }
277 }
278
279 result
280 }
281
282 pub fn format_variable_line(var: &VariableDebugInfo, verbose: bool) -> String {
284 let type_display = var
286 .type_pretty
287 .as_ref()
288 .filter(|pretty| !pretty.is_empty())
289 .cloned()
290 .unwrap_or_else(|| "unknown".to_string());
291
292 let name = &var.name;
293 if !verbose || var.location_description.is_empty() || var.location_description == "None" {
294 format!("{name} ({type_display})")
295 } else {
296 let location = &var.location_description;
297 format!("{name} ({type_display}) = {location}")
298 }
299 }
300
301 fn wrap_long_line(text: &str, max_width: usize, indent: &str) -> String {
303 if text.len() <= max_width {
304 format!("{text}\n")
305 } else {
306 let mut result = String::new();
307 let mut current_line = text.to_string();
308
309 while current_line.len() > max_width {
310 let break_point = current_line
311 .rfind(' ')
312 .unwrap_or(max_width.saturating_sub(10));
313 let (first_part, rest) = current_line.split_at(break_point);
314 result.push_str(&format!("{first_part}\n"));
315
316 let continuation_indent =
318 format!("{} ", indent.replace("āā", "ā ").replace("āā", " "));
319 let trimmed_rest = rest.trim();
320 current_line = format!("{continuation_indent}{trimmed_rest}");
321 }
322
323 if !current_line.trim().is_empty() {
324 result.push_str(&format!("{current_line}\n"));
325 }
326
327 result
328 }
329 }
330}
331
332impl ModuleDebugInfo {
333 pub fn format_for_display_styled(
335 &self,
336 is_last_module: bool,
337 source_file: &Option<String>,
338 source_line: Option<u32>,
339 verbose: bool,
340 ) -> Vec<ratatui::text::Line<'static>> {
341 use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
342
343 let mut lines = Vec::new();
344
345 let mut builder = StyledLineBuilder::new()
346 .styled("š¦ ", StylePresets::SECTION)
347 .styled(&self.binary_path, StylePresets::SECTION);
348
349 if let Some(ref file) = source_file {
350 builder = builder.text(" @ ").styled(
351 if let Some(line) = source_line {
352 format!("{file}:{line}")
353 } else {
354 file.clone()
355 },
356 StylePresets::LOCATION,
357 );
358 }
359
360 lines.push(builder.build());
361
362 for (addr_idx, mapping) in self.address_mappings.iter().enumerate() {
363 let is_last_addr = addr_idx + 1 == self.address_mappings.len();
364 lines.extend(mapping.format_for_display_styled(is_last_module, is_last_addr, verbose));
365 }
366
367 lines
368 }
369}
370
371#[derive(Debug, Clone)]
373pub struct AddressMapping {
374 pub address: u64,
375 pub binary_path: String, pub function_name: Option<String>,
377 pub variables: Vec<VariableDebugInfo>,
378 pub parameters: Vec<VariableDebugInfo>,
379 pub source_file: Option<String>,
380 pub source_line: Option<u32>,
381 pub is_inline: Option<bool>,
382 pub index: Option<usize>, }
384
385impl AddressMapping {
386 pub fn format_for_display_styled(
388 &self,
389 is_last_module: bool,
390 is_last_addr: bool,
391 verbose: bool,
392 ) -> Vec<ratatui::text::Line<'static>> {
393 use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
394
395 let mut lines = Vec::new();
396
397 let prefix = match (is_last_module, is_last_addr) {
398 (true, true) => " āā",
399 (true, false) => " āā",
400 (false, true) => "ā āā",
401 (false, false) => "ā āā",
402 };
403
404 let mut header = StyledLineBuilder::new().styled(prefix, StylePresets::TREE);
406 if let Some(i) = self.index {
407 header = header
408 .text(" ")
409 .styled(format!("[{i}]"), StylePresets::ADDRESS);
410 }
411 header = header.text(" šÆ ").address(self.address);
412
413 if let Some(is_inline) = self.is_inline {
414 header = header
415 .text(" ")
416 .key("ā")
417 .text(" ")
418 .styled(if is_inline { "inline" } else { "call" }, StylePresets::KEY);
419 }
420 if let (Some(ref file), Some(line)) = (&self.source_file, self.source_line) {
421 header = header
422 .text(" ")
423 .key("@")
424 .text(" ")
425 .value(format!("{file}:{line}"));
426 }
427
428 lines.push(header.build());
429
430 if !self.parameters.is_empty() {
431 let param_prefix = match (is_last_module, is_last_addr) {
432 (true, true) => " āā",
433 (true, false) => " ā āā",
434 (false, true) => "ā āā",
435 (false, false) => "ā ā āā",
436 };
437
438 lines.push(
439 StyledLineBuilder::new()
440 .styled(param_prefix, StylePresets::TREE)
441 .styled(" š„ Parameters", StylePresets::SECTION)
442 .build(),
443 );
444
445 for (param_idx, param) in self.parameters.iter().enumerate() {
446 let is_last_param =
447 param_idx + 1 == self.parameters.len() && self.variables.is_empty();
448 let item_prefix = match (is_last_module, is_last_addr, is_last_param) {
449 (true, true, true) => " ā āā",
450 (true, true, false) => " ā āā",
451 (true, false, true) => " ā ā āā",
452 (true, false, false) => " ā ā āā",
453 (false, true, true) => "ā ā āā",
454 (false, true, false) => "ā ā āā",
455 (false, false, true) => "ā ā ā āā",
456 (false, false, false) => "ā ā ā āā",
457 };
458
459 lines.push(Self::format_variable_styled(item_prefix, param, verbose));
460 }
461 }
462
463 if !self.variables.is_empty() {
464 let var_prefix = match (is_last_module, is_last_addr) {
465 (true, true) => " āā",
466 (true, false) => " ā āā",
467 (false, true) => "ā āā",
468 (false, false) => "ā ā āā",
469 };
470
471 lines.push(
472 StyledLineBuilder::new()
473 .styled(var_prefix, StylePresets::TREE)
474 .styled(" š¦ Variables", StylePresets::SECTION)
475 .build(),
476 );
477
478 for (var_idx, var) in self.variables.iter().enumerate() {
479 let is_last_var = var_idx + 1 == self.variables.len();
480 let item_prefix = match (is_last_module, is_last_addr, is_last_var) {
481 (true, true, true) => " āā",
482 (true, true, false) => " āā",
483 (true, false, true) => " ā āā",
484 (true, false, false) => " ā āā",
485 (false, true, true) => "ā āā",
486 (false, true, false) => "ā āā",
487 (false, false, true) => "ā ā āā",
488 (false, false, false) => "ā ā āā",
489 };
490
491 lines.push(Self::format_variable_styled(item_prefix, var, verbose));
492 }
493 }
494
495 lines
496 }
497
498 fn format_variable_styled(
499 indent_prefix: &str,
500 var: &VariableDebugInfo,
501 verbose: bool,
502 ) -> ratatui::text::Line<'static> {
503 use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
504
505 let type_display = var
506 .type_pretty
507 .as_ref()
508 .filter(|s| !s.is_empty())
509 .map(|s| s.as_str())
510 .unwrap_or("unknown");
511
512 let mut builder = StyledLineBuilder::new()
513 .styled(indent_prefix, StylePresets::TREE)
514 .text(" ")
515 .value(&var.name)
516 .key(": ")
517 .styled(type_display, StylePresets::TYPE);
518
519 if let Some(size) = var.size {
520 builder = builder.text(" ").text(format!("({size} bytes)"));
521 }
522
523 if verbose && !var.location_description.is_empty() && var.location_description != "None" {
524 builder = builder
525 .text(" ")
526 .key("@")
527 .text(" ")
528 .styled(&var.location_description, StylePresets::LOCATION);
529 }
530
531 builder.build()
532 }
533}
534
535#[derive(Debug, Clone)]
537pub enum TargetType {
538 Function,
539 SourceLocation,
540 Address,
541}
542
543#[derive(Debug, Clone)]
545pub struct VariableDebugInfo {
546 pub name: String,
547 pub type_name: String,
548 pub type_pretty: Option<String>,
549 pub location_description: String,
550 pub size: Option<u64>,
551 pub scope_start: Option<u64>,
552 pub scope_end: Option<u64>,
553}
554
555#[derive(Debug, Clone)]
557pub struct SourceFileInfo {
558 pub path: String,
559 pub directory: String,
560}
561
562#[derive(Debug, Clone)]
564pub struct SourceFileGroup {
565 pub module_path: String,
566 pub files: Vec<SourceFileInfo>,
567}
568
569#[derive(Debug, Clone)]
571pub struct SharedLibraryInfo {
572 pub from_address: u64, pub to_address: u64, pub symbols_read: bool, pub debug_info_available: bool, pub library_path: String, pub size: u64, pub debug_file_path: Option<String>, }
580
581#[derive(Debug, Clone)]
583pub struct SectionInfo {
584 pub start_address: u64, pub end_address: u64, pub size: u64, }