univdreams 0.4.0

Byte-identical binary <-> source round-tripping: binary formats, multi-arch decode/encode, a .ud decompiler/compiler, and an x86 emulator with Win16/Win32/Linux hosts.
Documentation
//! Analysis passes over loaded binaries.
//!
//! Function discovery layers signals from highest to lowest confidence:
//!
//! 1. The full symbol table (`.symtab`) when present.
//! 2. The dynamic symbol table (`.dynsym`).
//! 3. Byte-pattern signatures (CRT helpers, libc primitives, …).
//! 4. `.eh_frame` (DWARF CFI) — names are addresses, but sizes are
//!    authoritative; survives stripping.
//!
//! Each [`Function`] in the produced [`FunctionMap`] records every
//! source that contributed to it. Names from higher-confidence sources
//! win over names from lower-confidence sources; sizes are merged
//! preserving any non-zero value.
//!
//! After all sources are merged, a final pass fills in sizes for
//! functions that no source supplied a size for (typically signature
//! matches), using the distance to the next discovered function in
//! the same address window.

#![allow(clippy::cast_possible_truncation)]

pub mod bpf_relocs;
pub mod call_sites;
mod eh_frame;
mod entry;
mod function_map;
mod plt;
mod signatures;
mod symbols;

pub use call_sites::{CallSiteError, discover_from_bpf_call_sites};
pub use eh_frame::{EhFrameError, discover_from_eh_frame};
pub use function_map::{Function, FunctionMap, FunctionSource};
pub use plt::{PltError, discover_plt_thunks};
pub use signatures::discover_from_signatures;
pub use symbols::{SymbolError, discover_from_symbol_tables};

use crate::common::VAddr;
use crate::format::elf::Elf64File;

/// Crate-level error type.
#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error(transparent)]
    Symbol(#[from] SymbolError),
    #[error(transparent)]
    EhFrame(#[from] EhFrameError),
    #[error(transparent)]
    Plt(#[from] PltError),
    #[error(transparent)]
    BpfReloc(#[from] bpf_relocs::BpfRelocError),
    #[error(transparent)]
    CallSite(#[from] CallSiteError),
}

pub type Result<T, E = Error> = std::result::Result<T, E>;

/// Run every available discovery source and merge into a single
/// [`FunctionMap`].
///
/// Sources are run in order of ascending confidence so that, when the
/// merge resolves a name conflict, the higher-confidence record wins.
/// Provenance (`Function::sources`) accumulates from every source that
/// found the address.
///
/// After merging, a size-filling pass closes gaps for functions that
/// were only found via signatures or other size-less sources.
pub fn discover_functions(elf: &Elf64File) -> Result<FunctionMap> {
    let mut map = FunctionMap::new();

    // ELF `e_entry` — the loader-invoked entry. For Solana BPF
    // programs nothing in `.text` calls it, so without this
    // source the address would get buried inside an earlier
    // function whose body spans the entry's address. Naming
    // is `entry_point` for BPF / SBF, `_start` otherwise.
    for f in entry::discover_entry_point(elf) {
        map.insert(f);
    }
    for f in discover_from_eh_frame(elf)? {
        map.insert(f);
    }
    // BPF / SBF: harvest function entries from local `call`
    // targets. Stripped Solana programs only expose
    // entrypoint + custom_panic in `.dynsym`; every other
    // function lives behind a `call` somewhere. We need the
    // syscall relocation map to exclude syscall call sites
    // (whose `imm` is a helper id or Murmur3 hash, not a code
    // offset).
    let bpf_syscalls = bpf_relocs::build_call_site_names(elf)?;
    for f in discover_from_bpf_call_sites(elf, &bpf_syscalls)? {
        map.insert(f);
    }
    for f in discover_from_signatures(elf) {
        map.insert(f);
    }
    for f in discover_plt_thunks(elf)? {
        map.insert(f);
    }
    for f in discover_from_symbol_tables(elf)? {
        map.insert(f);
    }

    fill_in_sizes_from_neighbors(&mut map, elf);

    Ok(map)
}

/// For each function with `size == 0`, set its size to the distance to
/// the next function in the same executable section (or to the end of
/// the section, if it's the last). Functions without a containing
/// executable section keep `size = 0`.
fn fill_in_sizes_from_neighbors(map: &mut FunctionMap, elf: &Elf64File) {
    use crate::format::elf::SHF_EXECINSTR;

    let func_addrs: Vec<u64> = map.iter().map(|f| f.addr.0).collect();

    let exec_sections: Vec<(u64, u64)> = elf
        .sections()
        .filter(|(_, sh, _)| sh.sh_flags & SHF_EXECINSTR != 0 && sh.sh_size > 0)
        .map(|(_, sh, _)| (sh.sh_addr, sh.sh_addr.saturating_add(sh.sh_size)))
        .collect();

    let zero_sized: Vec<u64> = map
        .iter()
        .filter(|f| f.size == 0)
        .map(|f| f.addr.0)
        .collect();

    for addr in zero_sized {
        let Some(&(_, sec_end)) = exec_sections.iter().find(|(s, e)| addr >= *s && addr < *e)
        else {
            continue;
        };
        let next_in_section = func_addrs
            .iter()
            .copied()
            .filter(|&a| a > addr && a < sec_end)
            .min()
            .unwrap_or(sec_end);
        let new_size = next_in_section.saturating_sub(addr);
        map.insert(Function {
            addr: VAddr(addr),
            size: new_size,
            name: String::new(), // ignored on merge: name from existing wins (higher source)
            sources: Vec::new(),
        });
    }
}