Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

crates/nornir-introspect/src/binscan.rs

//! `binscan` — analyse ONE produced artifact end-to-end into warehouse-ready
//! facts. Slice 1 fills the size + hardening surface; the link graph, binary
//! SBOM and artifact diff are later slices (they extend [`BinaryReport`]).
//!
//! Pure Rust, gatling-parallel over the symbol table (see [`crate::size`]),
//! never shells out. `source_root` (optional) enables DWARF crate + file/line
//! attribution for the per-symbol rows.

use std::path::Path;

use anyhow::Result;
use serde::{Deserialize, Serialize};

use crate::hardening::{self, Hardening};
use crate::size::{self, CrateSize, SectionSize, SymbolSize};

/// The full analysis of one artifact — the header (format/size + checksec), the
/// section + symbol + crate size breakdown.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BinaryReport {
    /// Format / arch / class / total-bytes + the checksec hardening verdict.
    pub header: Hardening,
    /// Per-section sizes (Bloaty view).
    pub sections: Vec<SectionSize>,
    /// Per-symbol sizes (cargo-bloat / twiggy view).
    pub symbols: Vec<SymbolSize>,
    /// Per-crate size roll-up (descending by size).
    pub crates: Vec<CrateSize>,
}

/// Analyse the artifact at `path`. When `source_root` is `Some`, DWARF symbols
/// (scoped to that root) are joined so every symbol lands on its crate + source
/// location.
pub fn analyze(path: &Path, source_root: Option<&Path>) -> Result<BinaryReport> {
    let header = hardening::analyze_hardening(path)?;
    let sizes = size::analyze_sizes(path, source_root)?;
    Ok(BinaryReport {
        header,
        sections: sizes.sections,
        symbols: sizes.symbols,
        crates: sizes.crates,
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    /// End-to-end red-when-broken guard, reusing the self-exe pattern from
    /// `artifact.rs`: analyse the test binary itself (always present, always
    /// carries a symtab + DWARF under the `test` profile) and assert the whole
    /// report is coherent — non-empty section + symbol sizes, a per-crate
    /// roll-up, a DWARF-attributed source location, and a decoded hardening
    /// verdict. Any regression in the section/symbol walk, the DWARF join, or the
    /// checksec decode turns this red.
    #[test]
    fn analyze_selfexe_report() {
        let bin = std::env::current_exe().expect("current test exe");
        let rep = analyze(&bin, Some(Path::new("/"))).expect("analyze");

        // Header / hardening verdict decoded.
        assert!(rep.header.total_bytes > 0, "total bytes");
        assert!(
            matches!(rep.header.relro.as_str(), "none" | "partial" | "full"),
            "relro verdict decoded"
        );
        assert!(!rep.header.arch.is_empty(), "arch decoded");

        // Sizes non-empty.
        assert!(
            rep.sections
                .iter()
                .any(|s| s.name == ".text" && s.size_bytes > 0),
            "non-empty .text section"
        );
        assert!(
            rep.symbols.iter().any(|s| s.size_bytes > 0),
            "sized symbols"
        );
        assert!(!rep.crates.is_empty(), "per-crate roll-up");

        // DWARF join proven: at least one symbol carries a source file.
        assert!(
            rep.symbols.iter().any(|s| s.source_file.is_some()),
            "≥1 symbol attributed to a DWARF source file"
        );
    }
}