use std::collections::BTreeMap;
use crate::{
container::{Container, SymbolKind},
demangle::symbol,
inits::{self, InitKind},
shims, sites, types,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EntrypointKind {
EntryShim,
ModuleInit,
ModuleDatInit,
ProcSymbol,
EnclosingFunction,
RttiDestructor,
RttiTraceImpl,
}
impl EntrypointKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::EntryShim => "EntryShim",
Self::ModuleInit => "ModuleInit",
Self::ModuleDatInit => "ModuleDatInit",
Self::ProcSymbol => "ProcSymbol",
Self::EnclosingFunction => "EnclosingFunction",
Self::RttiDestructor => "RttiDestructor",
Self::RttiTraceImpl => "RttiTraceImpl",
}
}
}
impl core::fmt::Display for EntrypointKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone)]
pub struct CodeEntrypoint {
pub va: u64,
pub kind: EntrypointKind,
pub name: String,
pub size: Option<u64>,
}
pub fn build(container: &Container<'_>) -> Vec<CodeEntrypoint> {
let mut map: BTreeMap<u64, CodeEntrypoint> = BTreeMap::new();
for s in shims::scan(container) {
insert(
&mut map,
s.address,
EntrypointKind::EntryShim,
s.symbol_name.clone(),
container,
);
}
for f in inits::scan(container) {
let kind = match f.kind {
InitKind::Init | InitKind::HcrInit => EntrypointKind::ModuleInit,
InitKind::DatInit => EntrypointKind::ModuleDatInit,
};
let name = if f.module_path.path.is_empty() {
f.symbol_name.clone()
} else {
f.module_path.path.clone()
};
insert(&mut map, f.address, kind, name, container);
}
for sym in container.symbols() {
if sym.kind != SymbolKind::Function || sym.vm_addr == 0 {
continue;
}
let raw = sym.name.as_ref();
let Some(d) = symbol::parse(raw) else {
continue;
};
if d.item_id.is_none() {
continue;
}
let entry = CodeEntrypoint {
va: sym.vm_addr,
kind: EntrypointKind::ProcSymbol,
name: d.identifier.into_owned(),
size: if sym.size > 0 { Some(sym.size) } else { None },
};
map.entry(sym.vm_addr).or_insert(entry);
}
for rs in sites::scan(container) {
let Some(func) = container.function_at_va(rs.call_addr) else {
continue;
};
let name = rs
.enclosing_function
.clone()
.unwrap_or_else(|| func.name.as_ref().to_owned());
let entry = CodeEntrypoint {
va: func.vm_addr,
kind: EntrypointKind::EnclosingFunction,
name,
size: if func.size > 0 { Some(func.size) } else { None },
};
map.entry(func.vm_addr).or_insert(entry);
}
for t in types::build(container) {
if let Some(d) = &t.destructor {
let name = code_ref_name(d);
insert(
&mut map,
d.address,
EntrypointKind::RttiDestructor,
name,
container,
);
}
if let Some(tr) = &t.trace_impl {
let name = code_ref_name(tr);
insert(
&mut map,
tr.address,
EntrypointKind::RttiTraceImpl,
name,
container,
);
}
}
map.into_values().collect()
}
fn insert(
map: &mut BTreeMap<u64, CodeEntrypoint>,
va: u64,
kind: EntrypointKind,
name: String,
container: &Container<'_>,
) {
map.entry(va).or_insert_with(|| CodeEntrypoint {
va,
kind,
name,
size: size_at(container, va),
});
}
fn size_at(container: &Container<'_>, va: u64) -> Option<u64> {
container
.function_at_va(va)
.filter(|s| s.vm_addr == va && s.size > 0)
.map(|s| s.size)
}
fn code_ref_name(r: &types::CodeRef) -> String {
r.function
.clone()
.or_else(|| r.symbol_name.clone())
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kind_as_str_is_stable() {
assert_eq!(EntrypointKind::EntryShim.as_str(), "EntryShim");
assert_eq!(EntrypointKind::ProcSymbol.to_string(), "ProcSymbol");
assert_eq!(EntrypointKind::RttiDestructor.as_str(), "RttiDestructor");
}
#[test]
fn code_ref_name_prefers_function_then_symbol() {
let with_fn = types::CodeRef {
address: 0,
function: Some("foo".into()),
module: None,
symbol_name: Some("foo__m_u1".into()),
};
assert_eq!(code_ref_name(&with_fn), "foo");
let sym_only = types::CodeRef {
address: 0,
function: None,
module: None,
symbol_name: Some("rawSym".into()),
};
assert_eq!(code_ref_name(&sym_only), "rawSym");
}
#[test]
fn build_tags_shims_procs_and_skips_non_nim() {
use crate::container::{self, Arch, Format, Symbol, SymbolKind};
use std::borrow::Cow;
let bytes = vec![0u8; 16];
let symbols = vec![
Symbol {
name: Cow::Borrowed("NimMain"),
vm_addr: 0x1000,
size: 0,
kind: SymbolKind::Function,
},
Symbol {
name: Cow::Borrowed("parseInt__strutils_u42"),
vm_addr: 0x2000,
size: 16,
kind: SymbolKind::Function,
},
Symbol {
name: Cow::Borrowed("memcpy"),
vm_addr: 0x3000,
size: 0,
kind: SymbolKind::Function,
},
];
let c = container::assemble(&bytes, Format::Elf, Arch::Amd64, 0, vec![], symbols);
let eps = build(&c);
let shim = eps.iter().find(|e| e.va == 0x1000).expect("shim present");
assert_eq!(shim.kind, EntrypointKind::EntryShim);
let proc = eps.iter().find(|e| e.va == 0x2000).expect("proc present");
assert_eq!(proc.kind, EntrypointKind::ProcSymbol);
assert_eq!(proc.name, "parseInt");
assert_eq!(proc.size, Some(16));
assert!(
eps.iter().all(|e| e.va != 0x3000),
"non-Nim symbol excluded"
);
assert!(eps.windows(2).all(|w| w[0].va <= w[1].va));
}
}