#[derive(Debug, Clone)]
pub struct SourceCodeInfo {
pub file_path: String,
pub current_line: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct TargetDebugInfo {
pub target: String,
pub target_type: TargetType,
pub file_path: Option<String>,
pub line_number: Option<u32>,
pub function_name: Option<String>,
pub modules: Vec<ModuleDebugInfo>, }
impl TargetDebugInfo {
pub fn format_for_display(&self, verbose: bool) -> String {
let mut result = String::new();
let module_count = self.modules.len();
let total_addresses: usize = self
.modules
.iter()
.map(|module| module.address_mappings.len())
.sum();
let header_prefix = match self.target_type {
TargetType::Function => "🔧 Function Debug Info",
TargetType::SourceLocation => "📄 Line Debug Info",
TargetType::Address => "📍 Address Debug Info",
};
result.push_str(&format!(
"{header_prefix}: {} ({} modules, {} traceable addresses)\n\n",
self.target, module_count, total_addresses
));
for (module_idx, module) in self.modules.iter().enumerate() {
let is_last_module = module_idx == self.modules.len() - 1;
result.push_str(&module.format_for_display(
is_last_module,
&self.file_path,
self.line_number,
verbose,
));
}
if let TargetType::Address = self.target_type {
let example_addr = self
.modules
.iter()
.flat_map(|m| m.address_mappings.iter())
.map(|m| m.address)
.next();
if let Some(addr) = example_addr {
result.push_str("\n💡 Tips:\n");
result.push_str(&format!(
" - In '-t <module>' mode: use `trace 0x{addr:x} {{ ... }}` (defaults to that module)\n"
));
result.push_str(&format!(
" - 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"
));
}
}
result
}
pub fn format_for_display_styled(&self, verbose: bool) -> Vec<ratatui::text::Line<'static>> {
use crate::components::command_panel::style_builder::StyledLineBuilder;
use ratatui::text::Line;
let mut lines = Vec::new();
let total_addresses: usize = self.modules.iter().map(|m| m.address_mappings.len()).sum();
let header_prefix = match self.target_type {
TargetType::Function => "🔧 Function Debug Info",
TargetType::SourceLocation => "📄 Line Debug Info",
TargetType::Address => "📍 Address Debug Info",
};
lines.push(
StyledLineBuilder::new()
.title(format!(
"{header_prefix}: {} ({} modules, {} addresses)",
self.target,
self.modules.len(),
total_addresses
))
.build(),
);
lines.push(Line::from(""));
for (idx, module) in self.modules.iter().enumerate() {
let is_last = idx + 1 == self.modules.len();
lines.extend(module.format_for_display_styled(
is_last,
&self.file_path,
self.line_number,
verbose,
));
}
if let TargetType::Address = self.target_type {
if let Some(addr) = self
.modules
.iter()
.flat_map(|m| m.address_mappings.iter())
.map(|m| m.address)
.next()
{
lines.push(Line::from(""));
lines.push(
StyledLineBuilder::new()
.styled(
"💡 Tips:",
crate::components::command_panel::style_builder::StylePresets::SECTION,
)
.build(),
);
lines.push(
StyledLineBuilder::new()
.text(" - In '-t <module>' mode: use ")
.value(format!("trace 0x{addr:x} {{ ... }}"))
.text(" (defaults to that module)")
.build(),
);
lines.push(
StyledLineBuilder::new()
.text(" - In '-p <pid>' mode: default module is main executable; for library addresses, start with '-t <that .so>' then use ")
.value(format!("trace 0x{addr:x} {{ ... }}"))
.build(),
);
}
}
lines
}
}
#[derive(Debug, Clone)]
pub struct ModuleDebugInfo {
pub binary_path: String,
pub address_mappings: Vec<AddressMapping>,
}
impl ModuleDebugInfo {
pub fn format_for_display(
&self,
is_last_module: bool,
source_file: &Option<String>,
source_line: Option<u32>,
verbose: bool,
) -> String {
let mut result = String::new();
result.push_str(&format!("📦 {}", &self.binary_path));
if let Some(ref file) = source_file {
if let Some(line) = source_line {
result.push_str(&format!(" @ {file}:{line}\n"));
} else {
result.push_str(&format!(" @ {file}\n"));
}
} else {
result.push('\n');
}
for (addr_idx, mapping) in self.address_mappings.iter().enumerate() {
let is_last_addr = addr_idx == self.address_mappings.len() - 1;
let addr_prefix = match (is_last_module, is_last_addr) {
(true, true) => " └─",
(true, false) => " ├─",
(false, true) => "│ └─",
(false, false) => "│ ├─",
};
let mut pc_description = if let Some(i) = mapping.index {
format!("[{}] 🎯 0x{:x}", i, mapping.address)
} else {
format!("🎯 0x{:x}", mapping.address)
};
if let Some(is_inline) = mapping.is_inline {
pc_description
.push_str(&format!(" — {}", if is_inline { "inline" } else { "call" }));
}
if let (Some(ref file), Some(line)) = (&mapping.source_file, mapping.source_line) {
pc_description.push_str(&format!(" @ {file}:{line}"));
}
result.push_str(&format!("{addr_prefix} {pc_description}\n"));
if !mapping.parameters.is_empty() {
let param_prefix = match (is_last_module, is_last_addr) {
(true, true) => " ├─",
(true, false) => " │ ├─",
(false, true) => "│ ├─",
(false, false) => "│ │ ├─",
};
result.push_str(&format!("{param_prefix} 📥 Parameters\n"));
for (param_idx, param) in mapping.parameters.iter().enumerate() {
let is_last_param =
param_idx == mapping.parameters.len() - 1 && mapping.variables.is_empty();
let item_prefix = match (is_last_module, is_last_addr, is_last_param) {
(true, true, true) => " │ └─",
(true, true, false) => " │ ├─",
(true, false, true) => " │ │ └─",
(true, false, false) => " │ │ ├─",
(false, true, true) => "│ │ └─",
(false, true, false) => "│ │ ├─",
(false, false, true) => "│ │ │ └─",
(false, false, false) => "│ │ │ ├─",
};
let param_line = Self::format_variable_line(param, verbose);
result.push_str(&Self::wrap_long_line(
&format!("{item_prefix} {param_line}"),
80,
item_prefix,
));
}
}
if !mapping.variables.is_empty() {
let var_prefix = match (is_last_module, is_last_addr) {
(true, true) => " └─",
(true, false) => " │ └─",
(false, true) => "│ └─",
(false, false) => "│ │ └─",
};
result.push_str(&format!("{var_prefix} 📦 Variables\n"));
for (var_idx, var) in mapping.variables.iter().enumerate() {
let is_last_var = var_idx == mapping.variables.len() - 1;
let item_prefix = match (is_last_module, is_last_addr, is_last_var) {
(true, true, true) => " └─",
(true, true, false) => " ├─",
(true, false, true) => " │ └─",
(true, false, false) => " │ ├─",
(false, true, true) => "│ └─",
(false, true, false) => "│ ├─",
(false, false, true) => "│ │ └─",
(false, false, false) => "│ │ ├─",
};
let var_line = Self::format_variable_line(var, verbose);
result.push_str(&Self::wrap_long_line(
&format!("{item_prefix} {var_line}"),
80,
item_prefix,
));
}
}
}
result
}
pub fn format_variable_line(var: &VariableDebugInfo, verbose: bool) -> String {
let type_display = var
.type_pretty
.as_ref()
.filter(|pretty| !pretty.is_empty())
.cloned()
.unwrap_or_else(|| "unknown".to_string());
let name = &var.name;
if !verbose || var.location_description.is_empty() || var.location_description == "None" {
format!("{name} ({type_display})")
} else {
let location = &var.location_description;
format!("{name} ({type_display}) = {location}")
}
}
fn wrap_long_line(text: &str, max_width: usize, indent: &str) -> String {
if text.len() <= max_width {
format!("{text}\n")
} else {
let mut result = String::new();
let mut current_line = text.to_string();
while current_line.len() > max_width {
let break_point = current_line
.rfind(' ')
.unwrap_or(max_width.saturating_sub(10));
let (first_part, rest) = current_line.split_at(break_point);
result.push_str(&format!("{first_part}\n"));
let continuation_indent =
format!("{} ", indent.replace("├─", "│ ").replace("└─", " "));
let trimmed_rest = rest.trim();
current_line = format!("{continuation_indent}{trimmed_rest}");
}
if !current_line.trim().is_empty() {
result.push_str(&format!("{current_line}\n"));
}
result
}
}
}
impl ModuleDebugInfo {
pub fn format_for_display_styled(
&self,
is_last_module: bool,
source_file: &Option<String>,
source_line: Option<u32>,
verbose: bool,
) -> Vec<ratatui::text::Line<'static>> {
use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
let mut lines = Vec::new();
let mut builder = StyledLineBuilder::new()
.styled("📦 ", StylePresets::SECTION)
.styled(&self.binary_path, StylePresets::SECTION);
if let Some(ref file) = source_file {
builder = builder.text(" @ ").styled(
if let Some(line) = source_line {
format!("{file}:{line}")
} else {
file.clone()
},
StylePresets::LOCATION,
);
}
lines.push(builder.build());
for (addr_idx, mapping) in self.address_mappings.iter().enumerate() {
let is_last_addr = addr_idx + 1 == self.address_mappings.len();
lines.extend(mapping.format_for_display_styled(is_last_module, is_last_addr, verbose));
}
lines
}
}
#[derive(Debug, Clone)]
pub struct AddressMapping {
pub address: u64,
pub binary_path: String, pub function_name: Option<String>,
pub variables: Vec<VariableDebugInfo>,
pub parameters: Vec<VariableDebugInfo>,
pub source_file: Option<String>,
pub source_line: Option<u32>,
pub is_inline: Option<bool>,
pub index: Option<usize>, }
impl AddressMapping {
pub fn format_for_display_styled(
&self,
is_last_module: bool,
is_last_addr: bool,
verbose: bool,
) -> Vec<ratatui::text::Line<'static>> {
use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
let mut lines = Vec::new();
let prefix = match (is_last_module, is_last_addr) {
(true, true) => " └─",
(true, false) => " ├─",
(false, true) => "│ └─",
(false, false) => "│ ├─",
};
let mut header = StyledLineBuilder::new().styled(prefix, StylePresets::TREE);
if let Some(i) = self.index {
header = header
.text(" ")
.styled(format!("[{i}]"), StylePresets::ADDRESS);
}
header = header.text(" 🎯 ").address(self.address);
if let Some(is_inline) = self.is_inline {
header = header
.text(" ")
.key("—")
.text(" ")
.styled(if is_inline { "inline" } else { "call" }, StylePresets::KEY);
}
if let (Some(ref file), Some(line)) = (&self.source_file, self.source_line) {
header = header
.text(" ")
.key("@")
.text(" ")
.value(format!("{file}:{line}"));
}
lines.push(header.build());
if !self.parameters.is_empty() {
let param_prefix = match (is_last_module, is_last_addr) {
(true, true) => " ├─",
(true, false) => " │ ├─",
(false, true) => "│ ├─",
(false, false) => "│ │ ├─",
};
lines.push(
StyledLineBuilder::new()
.styled(param_prefix, StylePresets::TREE)
.styled(" 📥 Parameters", StylePresets::SECTION)
.build(),
);
for (param_idx, param) in self.parameters.iter().enumerate() {
let is_last_param =
param_idx + 1 == self.parameters.len() && self.variables.is_empty();
let item_prefix = match (is_last_module, is_last_addr, is_last_param) {
(true, true, true) => " │ └─",
(true, true, false) => " │ ├─",
(true, false, true) => " │ │ └─",
(true, false, false) => " │ │ ├─",
(false, true, true) => "│ │ └─",
(false, true, false) => "│ │ ├─",
(false, false, true) => "│ │ │ └─",
(false, false, false) => "│ │ │ ├─",
};
lines.push(Self::format_variable_styled(item_prefix, param, verbose));
}
}
if !self.variables.is_empty() {
let var_prefix = match (is_last_module, is_last_addr) {
(true, true) => " └─",
(true, false) => " │ └─",
(false, true) => "│ └─",
(false, false) => "│ │ └─",
};
lines.push(
StyledLineBuilder::new()
.styled(var_prefix, StylePresets::TREE)
.styled(" 📦 Variables", StylePresets::SECTION)
.build(),
);
for (var_idx, var) in self.variables.iter().enumerate() {
let is_last_var = var_idx + 1 == self.variables.len();
let item_prefix = match (is_last_module, is_last_addr, is_last_var) {
(true, true, true) => " └─",
(true, true, false) => " ├─",
(true, false, true) => " │ └─",
(true, false, false) => " │ ├─",
(false, true, true) => "│ └─",
(false, true, false) => "│ ├─",
(false, false, true) => "│ │ └─",
(false, false, false) => "│ │ ├─",
};
lines.push(Self::format_variable_styled(item_prefix, var, verbose));
}
}
lines
}
fn format_variable_styled(
indent_prefix: &str,
var: &VariableDebugInfo,
verbose: bool,
) -> ratatui::text::Line<'static> {
use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
let type_display = var
.type_pretty
.as_ref()
.filter(|s| !s.is_empty())
.map(|s| s.as_str())
.unwrap_or("unknown");
let mut builder = StyledLineBuilder::new()
.styled(indent_prefix, StylePresets::TREE)
.text(" ")
.value(&var.name)
.key(": ")
.styled(type_display, StylePresets::TYPE);
if let Some(size) = var.size {
builder = builder.text(" ").text(format!("({size} bytes)"));
}
if verbose && !var.location_description.is_empty() && var.location_description != "None" {
builder = builder
.text(" ")
.key("@")
.text(" ")
.styled(&var.location_description, StylePresets::LOCATION);
}
builder.build()
}
}
#[derive(Debug, Clone)]
pub enum TargetType {
Function,
SourceLocation,
Address,
}
#[derive(Debug, Clone)]
pub struct VariableDebugInfo {
pub name: String,
pub type_name: String,
pub type_pretty: Option<String>,
pub location_description: String,
pub size: Option<u64>,
pub scope_start: Option<u64>,
pub scope_end: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct SourceFileInfo {
pub path: String,
pub directory: String,
}
#[derive(Debug, Clone)]
pub struct SourceFileGroup {
pub module_path: String,
pub files: Vec<SourceFileInfo>,
}
#[derive(Debug, Clone)]
pub struct SharedLibraryInfo {
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>, }
#[derive(Debug, Clone)]
pub struct SectionInfo {
pub start_address: u64, pub end_address: u64, pub size: u64, }