use crate::{
container::{Container, SectionKind},
util,
};
#[derive(Debug, Clone)]
pub struct StackTraceHarvest {
pub file_paths: Vec<FilePath>,
pub proc_names: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct FilePath {
pub path: String,
pub is_absolute: bool,
}
pub fn harvest(container: &Container<'_>) -> StackTraceHarvest {
let mut file_paths = Vec::new();
let mut proc_names = Vec::new();
let mut seen_files = std::collections::HashSet::new();
let mut seen_procs = std::collections::HashSet::new();
for section in container.sections() {
if section.kind != SectionKind::RoData {
continue;
}
harvest_section(
section.data,
&mut file_paths,
&mut proc_names,
&mut seen_files,
&mut seen_procs,
);
}
StackTraceHarvest {
file_paths,
proc_names,
}
}
fn harvest_section(
data: &[u8],
file_paths: &mut Vec<FilePath>,
proc_names: &mut Vec<String>,
seen_files: &mut std::collections::HashSet<String>,
seen_procs: &mut std::collections::HashSet<String>,
) {
let mut offset = 0;
while offset < data.len() {
let Some(cstr) = util::slice_cstring(data, offset, 4096) else {
offset = offset.saturating_add(1);
continue;
};
if cstr.is_empty() {
offset = offset.saturating_add(1);
continue;
}
let advance = cstr.len().saturating_add(1);
if let Ok(s) = std::str::from_utf8(cstr) {
if s.ends_with(".nim") && s.len() >= 5 && is_printable(s) {
let path = s.strip_prefix('@').unwrap_or(s);
if path.ends_with(".nim") && !path.is_empty() && seen_files.insert(path.to_owned())
{
let is_absolute = path.starts_with('/')
|| (path.len() >= 3 && path.as_bytes().get(1).copied() == Some(b':'));
file_paths.push(FilePath {
path: path.to_owned(),
is_absolute,
});
}
}
else if s.len() <= 128
&& s.len() >= 2
&& is_nim_proc_name(s)
&& !looks_like_path(s)
&& seen_procs.insert(s.to_owned())
{
proc_names.push(s.to_owned());
}
}
offset = offset.saturating_add(advance);
}
}
fn is_printable(s: &str) -> bool {
s.bytes().all(|b| (0x20..=0x7E).contains(&b))
}
fn is_nim_proc_name(s: &str) -> bool {
let Some(&first) = s.as_bytes().first() else {
return false;
};
if !first.is_ascii_alphabetic() {
return false;
}
s.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_')
}
fn looks_like_path(s: &str) -> bool {
s.contains('/') || s.contains('\\') || s.contains("://") || s.ends_with(".nim")
}