Skip to main content

ghostscope_ui/events/
debug_info.rs

1/// Source code information for display in TUI
2#[derive(Debug, Clone)]
3pub struct SourceCodeInfo {
4    pub file_path: String,
5    pub current_line: Option<usize>,
6}
7
8/// Debug information for a target (function or source location)
9#[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>, // Grouped by module/binary
17}
18
19impl TargetDebugInfo {
20    /// Format target debug info with tree-style layout for display
21    pub fn format_for_display(&self, verbose: bool) -> String {
22        let mut result = String::new();
23
24        // Calculate statistics
25        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        // Header by target type
33        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        // Format modules with tree structure - modules will show their own paths and source info
44        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        // Suggestions for address targets
55        if let TargetType::Address = self.target_type {
56            // Try to pick the first address for an example
57            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    /// Styled version for display (pre-styled lines for UI rendering)
78    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        // Title line by type
85        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        // Suggestions for address targets
114        if let TargetType::Address = self.target_type {
115            // Example address from first mapping
116            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/// Debug information for a module (binary) containing one or more addresses
153#[derive(Debug, Clone)]
154pub struct ModuleDebugInfo {
155    pub binary_path: String,
156    pub address_mappings: Vec<AddressMapping>,
157}
158
159impl ModuleDebugInfo {
160    /// Format module info with tree-style layout for display
161    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        // Module header with full path and source info
171        result.push_str(&format!("šŸ“¦ {}", &self.binary_path));
172
173        // Add source information if available
174        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            // Enhanced PC address display with optional index + classification/source
194            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            // Format parameters
210            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            // Format variables
245            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    /// Overload helper: build from VariableDebugInfo
283    pub fn format_variable_line(var: &VariableDebugInfo, verbose: bool) -> String {
284        // Use enhanced DWARF type display (includes type name and size)
285        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    /// Wrap long lines with proper indentation
302    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                // Create continuation line with proper indentation
317                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    /// Styled module info lines
334    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/// Debug information for a specific address within a module
372#[derive(Debug, Clone)]
373pub struct AddressMapping {
374    pub address: u64,
375    pub binary_path: String, // Full binary path for this address
376    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>, // 1-based global index for selection
383}
384
385impl AddressMapping {
386    /// Styled address mapping lines with tree prefixes
387    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        // Header line with index + address + optional classification and source location
405        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/// Type of target being inspected
536#[derive(Debug, Clone)]
537pub enum TargetType {
538    Function,
539    SourceLocation,
540    Address,
541}
542
543/// Variable debug information
544#[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/// Source file information
556#[derive(Debug, Clone)]
557pub struct SourceFileInfo {
558    pub path: String,
559    pub directory: String,
560}
561
562/// Group of source files for a specific module
563#[derive(Debug, Clone)]
564pub struct SourceFileGroup {
565    pub module_path: String,
566    pub files: Vec<SourceFileInfo>,
567}
568
569/// Shared library information (similar to GDB's "info share" output)
570#[derive(Debug, Clone)]
571pub struct SharedLibraryInfo {
572    pub from_address: u64,               // Starting address in memory
573    pub to_address: u64,                 // Ending address in memory
574    pub symbols_read: bool,              // Whether symbols were successfully read
575    pub debug_info_available: bool,      // Whether debug information is available
576    pub library_path: String,            // Full path to the library file
577    pub size: u64,                       // Size of the library in memory
578    pub debug_file_path: Option<String>, // Path to separate debug file (if via .gnu_debuglink)
579}
580
581/// Section information for executable files
582#[derive(Debug, Clone)]
583pub struct SectionInfo {
584    pub start_address: u64, // Starting address of the section
585    pub end_address: u64,   // Ending address of the section
586    pub size: u64,          // Size of the section in bytes
587}