use std::collections::HashMap;
use crate::arch::bpf::{BpfVariant, DecodedInsn, InsnKind, call_target, format_insn, jump_target};
use crate::ast::{FnDecl, Stmt};
use crate::ir::Function;
use super::args::infer_bpf_signature;
use super::data_lookup::DataLookup;
use super::idioms::{
annotate_handler_banners, annotate_pda_verify, solana_function_summary,
solana_semantic_comment, solana_syscall_signature,
};
use super::stack_slots::rewrite_slots;
const BPF_FP: &str = "r10";
#[must_use]
#[allow(clippy::too_many_lines)]
pub fn build_function(
f: &Function<DecodedInsn>,
name_at: &HashMap<u64, String>,
call_site_names: &HashMap<u64, String>,
variant: BpfVariant,
data: Option<&dyn DataLookup>,
) -> FnDecl {
let fn_start = f.addr.0;
let fn_end = fn_start.saturating_add(f.size() as u64);
let mut intra_targets: std::collections::BTreeSet<u64> = std::collections::BTreeSet::new();
for block in &f.blocks {
for insn in &block.insns {
if matches!(
insn.kind,
InsnKind::Jmp | InsnKind::JmpCond | InsnKind::JmpCond32
) {
let t = jump_target(insn);
if (fn_start..fn_end).contains(&t) {
intra_targets.insert(t);
}
}
}
}
let signature = infer_bpf_signature(f);
let arity = signature.as_ref().map_or(0, |s| s.params.len() as u8);
let ssa = super::bpf_ssa::build_bpf_ssa(f);
let insns_by_addr = super::bpf_args_ssa::index_by_addr(f);
let mut tracker = RegTracker::new_at_entry(arity);
let mut body = Vec::new();
for block in &f.blocks {
let mut idx = 0;
while idx < block.insns.len() {
let insn = &block.insns[idx];
let mut consumed_extra = 0usize;
let lddw_pair_bytes: Option<Vec<u8>> = if matches!(insn.kind, InsnKind::Lddw) {
block
.insns
.get(idx + 1)
.filter(|next| matches!(next.kind, InsnKind::LddwSecondHalf))
.map(|next| {
let mut b = Vec::with_capacity(16);
b.extend_from_slice(&insn.bytes);
b.extend_from_slice(&next.bytes);
consumed_extra = 1;
b
})
} else {
None
};
if intra_targets.contains(&insn.addr.0) {
body.push(Stmt::Label { addr: insn.addr.0 });
tracker.reset();
}
let call_args = if matches!(insn.kind, InsnKind::Call) {
let mut args = tracker.snapshot_call_args();
for (slot, arg) in args.iter_mut().enumerate() {
if arg.is_none() {
*arg = super::bpf_args_ssa::resolve_arg(
&ssa,
&insns_by_addr,
insn.addr.0,
slot,
data,
);
}
}
Some(args)
} else {
None
};
let text = render_text(
insn,
variant,
name_at,
call_site_names,
&intra_targets,
data,
);
let lifted_to_call = if matches!(insn.kind, InsnKind::Call) {
if let Some(args) = call_args.as_ref() {
let lifted = lift_call_stmt(insn, &text, args, name_at, call_site_names);
if let Some(stmt) = lifted {
body.push(stmt);
true
} else {
false
}
} else {
false
}
} else {
false
};
if !lifted_to_call {
if let Some(lifted) = lift_semantic_stmt(insn, &text, lddw_pair_bytes.as_deref()) {
body.push(lifted);
} else {
body.push(Stmt::asm(text, insn.bytes.to_vec()));
if let Some(cont) = block.insns.get(idx + 1)
&& consumed_extra > 0
{
let cont_text = render_text(
cont,
variant,
name_at,
call_site_names,
&intra_targets,
data,
);
body.push(Stmt::asm(cont_text, cont.bytes.to_vec()));
}
}
}
if let Some(annotation) = call_or_branch_annotation(insn, name_at, &intra_targets) {
body.push(Stmt::Comment(annotation));
}
if matches!(insn.kind, InsnKind::Call)
&& let Some(args) = call_args
{
let callee_name = call_site_names.get(&insn.addr.0).cloned();
if let Some(name) = &callee_name {
let sig = solana_syscall_signature(name);
if let Some(sig_str) = sig {
body.push(Stmt::Comment(sig_str.to_string()));
}
if let Some(semantic) = solana_semantic_comment(name, &args) {
body.push(Stmt::Comment(semantic));
}
}
if !lifted_to_call {
let line = if let Some(name) = callee_name {
let sig = solana_syscall_signature(&name);
let arity = sig.map_or(5, syscall_arity);
format_call_invocation(&name, arity, &args)
} else if let Some(callee) = name_at.get(&call_target(insn)) {
format_call_invocation(callee, 5, &args)
} else {
render_call_args(&args).unwrap_or_default()
};
if !line.is_empty() {
body.push(Stmt::Comment(line));
}
}
}
tracker.apply(insn, data);
if consumed_extra > 0
&& let Some(cont) = block.insns.get(idx + 1)
{
tracker.apply(cont, data);
}
idx += 1 + consumed_extra;
}
}
let mut body = wrap_if_blocks(body, f, &intra_targets);
annotate_pda_verify(&mut body);
annotate_handler_banners(&mut body);
if let Some(summary) = solana_function_summary(&body) {
body.insert(0, Stmt::Comment(summary));
}
FnDecl {
addr: Some(f.addr.0),
name: f.name.clone(),
attrs: Vec::new(),
locals: Vec::new(),
signature,
body,
}
}
fn lift_semantic_stmt(
insn: &DecodedInsn,
text: &str,
lddw_pair_bytes: Option<&[u8]>,
) -> Option<Stmt> {
if insn.opcode == 0x95 {
return Some(Stmt::Return {
value: 0,
bytes: insn.bytes.to_vec(),
});
}
if matches!(insn.opcode, 0xb7 | 0xbf) {
let after = text.strip_prefix("mov64 ")?;
let (dst, src) = after.split_once(", ")?;
return Some(Stmt::Move {
dst: dst.trim().to_string(),
src: src.trim().to_string(),
bytes: insn.bytes.to_vec(),
});
}
let class = insn.opcode & 0x07;
if matches!(insn.kind, InsnKind::Load) && class == 0x01 {
return lift_load(insn, text);
}
if matches!(insn.kind, InsnKind::Store) && class == 0x03 {
return lift_store(insn, text);
}
if matches!(insn.kind, InsnKind::Lddw) {
return lift_lddw(insn, text, lddw_pair_bytes);
}
if matches!(insn.kind, InsnKind::Alu64) && (insn.opcode & 0x07) == 0x07 {
return lift_alu64(insn);
}
None
}
fn lift_alu64(insn: &DecodedInsn) -> Option<Stmt> {
let op_nibble = insn.opcode >> 4;
let op = match op_nibble {
0x0 => "+=",
0x1 => "-=",
0x2 => "*=",
0x3 => "/=",
0x4 => "|=",
0x5 => "&=",
0x6 => "<<=",
0x7 => ">>=",
0x9 => "%=",
0xa => "^=",
_ => return None, };
let dst = format!("r{}", insn.dst);
let src = if (insn.opcode & 0x08) != 0 {
format!("r{}", insn.src)
} else {
#[allow(clippy::cast_sign_loss)]
let imm_u32 = insn.imm as u32;
format!("0x{imm_u32:x}")
};
Some(Stmt::RegArith {
dst,
op: op.into(),
src,
bytes: insn.bytes.to_vec(),
})
}
fn lift_load(insn: &DecodedInsn, text: &str) -> Option<Stmt> {
let rest = text.strip_prefix("ldx")?;
let (size_letter, after) = split_size_letter(rest)?;
let (dst, mem) = after.trim_start().split_once(", ")?;
let mem = mem.trim();
let src = if size_letter == "dw" {
mem.to_string()
} else {
format!("{mem}:u{}", bits_for_size_letter(size_letter))
};
Some(Stmt::Move {
dst: dst.trim().to_string(),
src,
bytes: insn.bytes.to_vec(),
})
}
fn lift_store(insn: &DecodedInsn, text: &str) -> Option<Stmt> {
let rest = text.strip_prefix("stx")?;
let (size_letter, after) = split_size_letter(rest)?;
let (mem, src) = after.trim_start().split_once(", ")?;
let mem = mem.trim();
let dst = if size_letter == "dw" {
mem.to_string()
} else {
format!("{mem}:u{}", bits_for_size_letter(size_letter))
};
Some(Stmt::Move {
dst,
src: src.trim().to_string(),
bytes: insn.bytes.to_vec(),
})
}
fn lift_lddw(_insn: &DecodedInsn, text: &str, pair_bytes: Option<&[u8]>) -> Option<Stmt> {
let bytes = pair_bytes?.to_vec();
if bytes.len() != 16 {
return None;
}
let rest = text.strip_prefix("lddw ")?;
let (dst, rhs) = rest.split_once(", ")?;
let rhs = rhs.trim();
if rhs.starts_with('"') || rhs.contains(" @0x") {
return None;
}
let src = format!("{rhs}:u64");
Some(Stmt::Move {
dst: dst.trim().to_string(),
src,
bytes,
})
}
fn split_size_letter(s: &str) -> Option<(&str, &str)> {
if let Some(after) = s.strip_prefix("dw") {
return Some(("dw", after));
}
let b = s.as_bytes();
if !b.is_empty() && matches!(b[0], b'b' | b'h' | b'w') {
return Some((&s[..1], &s[1..]));
}
None
}
fn bits_for_size_letter(letter: &str) -> u32 {
match letter {
"b" => 8,
"h" => 16,
"w" => 32,
_ => 64, }
}
fn lift_call_stmt(
insn: &DecodedInsn,
text: &str,
call_args: &[Option<String>; 5],
_name_at: &HashMap<u64, String>,
call_site_names: &HashMap<u64, String>,
) -> Option<Stmt> {
let (mnem_prefix, rest) = if let Some(r) = text.strip_prefix("call_local ") {
("call_local", r)
} else {
("call", text.strip_prefix("call ")?)
};
let _ = mnem_prefix;
let name = rest.trim();
if name.starts_with("0x") || name.is_empty() {
return None;
}
let src_nibble = (insn.bytes[1] >> 4) & 0x0f;
let direct_target = if call_site_names.contains_key(&insn.addr.0) {
None
} else {
match (insn.opcode, src_nibble) {
(0x8d, _) | (0x85, 1) => Some(call_target(insn)),
_ => None,
}
};
let arity = if let Some(callee) = call_site_names.get(&insn.addr.0) {
super::idioms::solana_syscall_signature(callee).map_or(5, syscall_arity)
} else {
5
};
let args = call_args_vec(call_args, arity);
Some(Stmt::Call {
name: name.to_string(),
args,
bytes: insn.bytes.to_vec(),
direct_target,
})
}
fn call_args_vec(args: &[Option<String>; 5], arity: usize) -> Vec<String> {
let n = arity.min(5);
let mut parts: Vec<String> = args
.iter()
.take(n)
.map(|s| s.clone().unwrap_or_else(|| "_".into()))
.collect();
while parts.last().is_some_and(|s| s == "_") {
parts.pop();
}
parts
}
fn wrap_if_blocks(
body: Vec<Stmt>,
f: &Function<DecodedInsn>,
intra_targets: &std::collections::BTreeSet<u64>,
) -> Vec<Stmt> {
let _ = intra_targets;
let mut jcc_by_addr: HashMap<u64, (u64, Vec<u8>, String, String)> = HashMap::new();
for block in &f.blocks {
for insn in &block.insns {
if !matches!(insn.kind, InsnKind::JmpCond | InsnKind::JmpCond32) {
continue;
}
let target = jump_target(insn);
if target <= insn.addr.0 {
continue;
}
let cond = invert_bpf_cond(insn);
jcc_by_addr.insert(
insn.addr.0,
(target, insn.bytes.to_vec(), cond, String::new()),
);
}
}
wrap_if_blocks_in_seq(body, &jcc_by_addr)
}
#[allow(clippy::needless_pass_by_value)]
fn wrap_if_blocks_in_seq(
body: Vec<Stmt>,
jcc_by_addr: &HashMap<u64, (u64, Vec<u8>, String, String)>,
) -> Vec<Stmt> {
let mut label_pos: HashMap<u64, usize> = HashMap::new();
for (i, s) in body.iter().enumerate() {
if let Stmt::Label { addr } = s {
label_pos.insert(*addr, i);
}
}
let mut out: Vec<Stmt> = Vec::with_capacity(body.len());
let mut i = 0;
while i < body.len() {
if let Stmt::Asm { bytes, .. } = &body[i]
&& bytes.len() == 8
{
let insn_addr = peek_insn_addr(&body, i);
if let Some(jcc) = insn_addr.and_then(|a| jcc_by_addr.get(&a)) {
let target = jcc.0;
if let Some(&label_idx) = label_pos.get(&target)
&& label_idx > i
{
let inner = &body[i + 1..label_idx];
if let Some((body_stmts, tail_bytes)) =
try_match_while(inner, insn_addr.unwrap(), &out)
{
let body_recursed = wrap_if_blocks_in_seq(body_stmts, jcc_by_addr);
out.push(Stmt::WhileBlock {
cond_text: jcc.2.clone(),
entry_bytes: jcc.1.clone(),
tail_bytes,
body: body_recursed,
});
i = label_idx;
continue;
}
if let Some((then_body, tail, else_body, advance)) = try_split_then_else(
inner,
target,
&label_pos,
&body,
label_idx,
insn_addr.unwrap(),
jcc_by_addr,
) {
out.push(Stmt::IfBlock {
cond_text: jcc.2.clone(),
cond_bytes: jcc.1.clone(),
then_body,
then_tail_jmp: tail,
else_body,
});
i = advance;
continue;
}
if region_is_self_contained(inner, insn_addr.unwrap(), target) {
let inner_owned: Vec<Stmt> = inner.to_vec();
let then_body = wrap_if_blocks_in_seq(inner_owned, jcc_by_addr);
out.push(Stmt::IfBlock {
cond_text: jcc.2.clone(),
cond_bytes: jcc.1.clone(),
then_body,
then_tail_jmp: Vec::new(),
else_body: Vec::new(),
});
i = label_idx;
continue;
}
}
}
}
out.push(body[i].clone());
i += 1;
}
out
}
fn try_match_while(
inner: &[Stmt],
jcc_addr: u64,
out_so_far: &[Stmt],
) -> Option<(Vec<Stmt>, Vec<u8>)> {
let _ = jcc_addr;
let last_idx = inner.iter().rposition(is_byte_bearing_stmt)?;
let Stmt::Asm { text, bytes } = &inner[last_idx] else {
return None;
};
let label_hex = text.strip_prefix("ja label_")?;
let ja_target = u64::from_str_radix(label_hex, 16).ok()?;
let mut found = false;
for s in out_so_far.iter().rev() {
if let Stmt::Label { addr } = s
&& *addr == ja_target
{
found = true;
break;
}
}
if !found {
return None;
}
let body_stmts: Vec<Stmt> = inner[..last_idx].to_vec();
Some((body_stmts, bytes.clone()))
}
type ThenElseSplit = (Vec<Stmt>, Vec<u8>, Vec<Stmt>, usize);
#[allow(clippy::too_many_arguments)]
fn try_split_then_else(
inner: &[Stmt],
target: u64,
label_pos: &HashMap<u64, usize>,
full_body: &[Stmt],
label_idx: usize,
jcc_addr: u64,
jcc_by_addr: &HashMap<u64, (u64, Vec<u8>, String, String)>,
) -> Option<ThenElseSplit> {
let last_byte_idx = inner.iter().rposition(is_byte_bearing_stmt)?;
let Stmt::Asm { text, bytes } = &inner[last_byte_idx] else {
return None;
};
let last_asm_idx = last_byte_idx;
let label_hex = text.strip_prefix("ja label_")?;
let done_addr = u64::from_str_radix(label_hex, 16).ok()?;
if done_addr <= target {
return None;
}
let &done_idx = label_pos.get(&done_addr)?;
if done_idx <= label_idx {
return None;
}
if !region_is_self_contained(&inner[..last_asm_idx], jcc_addr, target) {
return None;
}
let else_slice = &full_body[label_idx..done_idx];
if !region_is_self_contained(else_slice, target, done_addr) {
return None;
}
let tail_jmp = bytes.clone();
let then_inner: Vec<Stmt> = inner[..last_asm_idx].to_vec();
let then_body = wrap_if_blocks_in_seq(then_inner, jcc_by_addr);
let else_body = wrap_if_blocks_in_seq(else_slice.to_vec(), jcc_by_addr);
Some((then_body, tail_jmp, else_body, done_idx))
}
fn is_byte_bearing_stmt(stmt: &Stmt) -> bool {
matches!(
stmt,
Stmt::Asm { .. }
| Stmt::Move { .. }
| Stmt::Return { .. }
| Stmt::Call { .. }
| Stmt::RegArith { .. }
)
}
fn peek_insn_addr(body: &[Stmt], idx: usize) -> Option<u64> {
let mut slots_seen = 0u64;
for j in (0..idx).rev() {
match &body[j] {
Stmt::Asm { bytes, .. }
| Stmt::Return { bytes, .. }
| Stmt::Call { bytes, .. }
| Stmt::RegArith { bytes, .. } => {
slots_seen += bytes.len().max(8) as u64 / 8;
}
Stmt::Move { bytes, .. } => {
if bytes.is_empty() {
slots_seen += 1;
} else {
slots_seen += (bytes.len() as u64) / 8;
}
}
Stmt::Label { addr } => return Some(addr + slots_seen * 8),
_ => {}
}
}
None
}
#[allow(clippy::cast_sign_loss)]
fn invert_bpf_cond(insn: &DecodedInsn) -> String {
let op = insn.opcode >> 4;
let dst = format!("r{}", insn.dst);
let is_reg_src = (insn.opcode & 0x08) != 0;
let rhs = if is_reg_src {
format!("r{}", insn.src)
} else {
format!("0x{:x}", insn.imm as u32)
};
let cmp = match op {
0x1 => "!=", 0x2 | 0x6 => "<=", 0x3 | 0x7 => "<", 0x4 => return format!("({dst} & {rhs}) == 0"), 0x5 => "==", 0xa | 0xc => ">=", 0xb | 0xd => ">", _ => "?",
};
format!("{dst} {cmp} {rhs}")
}
fn region_is_self_contained(region: &[Stmt], jcc_addr: u64, target: u64) -> bool {
for s in region {
if let Stmt::Asm { text, .. } = s {
for tok in text.split_whitespace() {
if let Some(rest) = tok.strip_prefix("label_") {
let hex = rest.trim_end_matches(',');
if let Ok(t) = u64::from_str_radix(hex, 16)
&& (t < jcc_addr || t >= target)
{
return false;
}
}
}
}
}
true
}
fn render_text(
insn: &DecodedInsn,
variant: BpfVariant,
name_at: &HashMap<u64, String>,
call_site_names: &HashMap<u64, String>,
intra_targets: &std::collections::BTreeSet<u64>,
data: Option<&dyn DataLookup>,
) -> String {
if matches!(insn.kind, InsnKind::Call) {
let call_mnem = if insn.opcode == 0x8d {
"call_local"
} else {
"call"
};
if let Some(name) = call_site_names.get(&insn.addr.0) {
return format!("{call_mnem} {name}");
}
let target = call_target(insn);
if let Some(name) = name_at.get(&target) {
return format!("{call_mnem} {name}");
}
}
if matches!(insn.kind, InsnKind::Lddw)
&& let (Some(imm), Some(lookup)) = (insn.imm64, data)
&& let Some(s) = read_inline_string(lookup, imm)
{
return format!("lddw r{}, {s} @0x{imm:x}", insn.dst);
}
let mut text = rewrite_slots(&format_insn(insn, variant), BPF_FP);
if matches!(
insn.kind,
InsnKind::Jmp | InsnKind::JmpCond | InsnKind::JmpCond32
) {
let target = jump_target(insn);
if intra_targets.contains(&target) {
text = rewrite_branch_offset(&text, target);
}
}
text
}
fn rewrite_branch_offset(text: &str, target: u64) -> String {
let label_ref = format!("label_{target:x}");
if let Some((head, _)) = text.rsplit_once(", +0x") {
return format!("{head}, {label_ref}");
}
if let Some((head, _)) = text.rsplit_once(", -0x") {
return format!("{head}, {label_ref}");
}
if let Some((head, _)) = text.rsplit_once(" +0x") {
return format!("{head} {label_ref}");
}
if let Some((head, _)) = text.rsplit_once(" -0x") {
return format!("{head} {label_ref}");
}
text.to_string()
}
fn call_or_branch_annotation(
insn: &DecodedInsn,
name_at: &HashMap<u64, String>,
intra_targets: &std::collections::BTreeSet<u64>,
) -> Option<String> {
match insn.kind {
InsnKind::Jmp | InsnKind::JmpCond | InsnKind::JmpCond32 => {
let target = jump_target(insn);
if intra_targets.contains(&target) {
return None;
}
name_at.get(&target).map(|n| format!("-> {n}"))
}
_ => None,
}
}
struct RegTracker {
state: [Option<String>; 11],
}
impl RegTracker {
fn new_at_entry(arity: u8) -> Self {
let mut state: [Option<String>; 11] = Default::default();
state[10] = Some("r10".into());
for i in 0..arity.min(5) {
state[(i + 1) as usize] = Some(format!("arg_{i}"));
}
Self { state }
}
fn reset(&mut self) {
for (i, s) in self.state.iter_mut().enumerate() {
if i == 10 {
continue;
}
*s = None;
}
}
fn snapshot_call_args(&self) -> [Option<String>; 5] {
[
self.state[1].clone(),
self.state[2].clone(),
self.state[3].clone(),
self.state[4].clone(),
self.state[5].clone(),
]
}
#[allow(clippy::match_same_arms)]
fn apply(&mut self, insn: &DecodedInsn, data: Option<&dyn DataLookup>) {
let class = insn.opcode & 0x07;
let op_nibble = insn.opcode >> 4;
let is_reg_src = (insn.opcode & 0x08) != 0;
let dst = insn.dst as usize;
let src = insn.src as usize;
if dst > 10 {
return;
}
match class {
0x00 => {
if insn.opcode == 0x18 {
let imm = insn.imm64.unwrap_or(0);
let val = data
.and_then(|d| read_inline_string(d, imm))
.unwrap_or_else(|| format!("0x{imm:x}"));
self.state[dst] = Some(val);
} else {
self.state[dst] = None;
}
}
0x01 => {
if src == 10 {
self.state[dst] = Some(render_stack_ref(insn.offset, false));
} else {
self.state[dst] = None;
}
}
0x02 | 0x03 => { }
0x04 | 0x07 => {
#[allow(clippy::cast_sign_loss)]
match op_nibble {
0xb => {
self.state[dst] = if is_reg_src {
self.state[src].clone()
} else {
Some(format!("0x{:x}", insn.imm as u32))
};
}
0x0 => {
if is_reg_src {
self.state[dst] = None;
} else {
let folded = self.state[dst]
.as_deref()
.and_then(|s| fold_stack_add(s, insn.imm));
self.state[dst] = folded;
}
}
_ => {
self.state[dst] = None;
}
}
}
0x05 => {
if matches!(insn.kind, InsnKind::Call | InsnKind::CallReg) {
self.state[0] = Some("<return>".into());
for r in 1..=5 {
self.state[r] = None;
}
}
}
0x06 => {}
_ => {}
}
}
}
fn render_stack_ref(offset: i16, take_addr: bool) -> String {
let prefix = if take_addr { "&" } else { "[" };
let suffix = if take_addr { "" } else { "]" };
if offset >= 0 {
format!("{prefix}arg_{offset:x}{suffix}")
} else {
let abs = u32::from(offset.unsigned_abs());
format!("{prefix}local_{abs:x}{suffix}")
}
}
fn fold_stack_add(base: &str, delta: i32) -> Option<String> {
if base == "r10" {
if delta < 0 {
return Some(format!("&local_{:x}", delta.unsigned_abs()));
}
return Some(format!("&arg_{delta:x}"));
}
if let Some(rest) = base.strip_prefix("&local_") {
let cur = i64::from_str_radix(rest, 16).ok()?;
let new = -cur + i64::from(delta);
if new <= 0 {
return Some(format!("&local_{:x}", new.unsigned_abs()));
}
return Some(format!("&arg_{new:x}"));
}
if let Some(rest) = base.strip_prefix("&arg_") {
let cur = i64::from_str_radix(rest, 16).ok()?;
let new = cur + i64::from(delta);
if new < 0 {
return Some(format!("&local_{:x}", new.unsigned_abs()));
}
return Some(format!("&arg_{new:x}"));
}
None
}
pub(super) fn read_inline_string(lookup: &dyn DataLookup, vaddr: u64) -> Option<String> {
if let Some(s) = read_string_direct(lookup, vaddr, 96) {
return Some(s);
}
read_string_via_slice_descriptor(lookup, vaddr)
}
fn read_string_direct(lookup: &dyn DataLookup, vaddr: u64, max_len: usize) -> Option<String> {
let (_section, bytes, offset) = lookup.section_at(vaddr)?;
let tail = bytes.get(offset..)?;
let mut end = 0;
for (i, &b) in tail.iter().take(max_len).enumerate() {
if b == 0 {
end = i;
break;
}
if !(b == b'\t' || b == b'\n' || (0x20..0x7f).contains(&b)) {
end = i;
break;
}
end = i + 1;
}
if end < 4 {
return None;
}
let s = std::str::from_utf8(&tail[..end]).ok()?;
Some(format!("{s:?}"))
}
fn read_string_via_slice_descriptor(lookup: &dyn DataLookup, vaddr: u64) -> Option<String> {
let (_section, bytes, offset) = lookup.section_at(vaddr)?;
let descriptor = bytes.get(offset..offset + 16)?;
let ptr = u64::from(u32::from_le_bytes(descriptor[4..8].try_into().ok()?));
let len = u64::from_le_bytes(descriptor[8..16].try_into().ok()?);
if len == 0 || len > 1024 || ptr == 0 {
return None;
}
let (_section2, ptr_bytes, ptr_offset) = lookup.section_at(ptr)?;
let slice = ptr_bytes.get(ptr_offset..ptr_offset + len as usize)?;
let s = std::str::from_utf8(slice).ok()?;
if s.chars()
.any(|c| (c as u32) < 0x20 && c != '\t' && c != '\n')
{
return None;
}
Some(format!("{s:?}"))
}
fn format_call_invocation(name: &str, arity: usize, args: &[Option<String>; 5]) -> String {
let n = arity.min(5);
let mut parts = Vec::new();
for slot in args.iter().take(n) {
match slot {
Some(v) => parts.push(v.clone()),
None => parts.push("?".into()),
}
}
while let Some(last) = parts.last() {
if last == "?" {
parts.pop();
} else {
break;
}
}
if parts.is_empty() {
format!("→ {name}()")
} else {
format!("→ {name}({})", parts.join(", "))
}
}
fn syscall_arity(sig: &str) -> usize {
let Some(open) = sig.find('(') else { return 5 };
let Some(close) = sig.rfind(')') else {
return 5;
};
if close <= open + 1 {
return 0;
}
let inner = &sig[open + 1..close];
inner.split(',').count()
}
fn render_call_args(args: &[Option<String>; 5]) -> Option<String> {
let mut parts = Vec::new();
for (i, a) in args.iter().enumerate() {
if let Some(v) = a {
parts.push(format!("r{}={v}", i + 1));
}
}
if parts.is_empty() {
None
} else {
Some(format!("args: {}", parts.join(", ")))
}
}