crates/mimir-ingest/src/boundary/tests.rs
//! The boundary's tables, map and BTF reader.
use std::collections::BTreeSet;
use mimir_schema::{EdgeKind, kind as nk, moniker};
use super::*;
fn gnu() -> Target {
Target::parse("x86_64-unknown-linux-gnu").unwrap()
}
fn musl() -> Target {
Target::parse("x86_64-unknown-linux-musl").unwrap()
}
#[test]
fn every_x86_64_syscall_has_exactly_one_class_list() {
let t = table("x86_64").unwrap();
assert!(t.len() > 380, "{}", t.len());
for s in t {
let lists: Vec<Class> = class::LISTS.iter().filter(|(_, l)| l.contains(&s.name)).map(|(c, _)| *c).collect();
assert_eq!(lists.len(), 1, "{} is in {lists:?}", s.name);
}
// the lists name only real calls
for (c, l) in class::LISTS {
for n in *l {
assert!(syscall("x86_64", n).is_some(), "{c}: {n} is no x86_64 syscall");
}
}
assert_eq!(syscall("x86_64", "read").map(|s| (s.nr, s.entry)), Some((0, "sys_read")));
assert_eq!(syscall("x86_64", "openat").map(|s| s.nr), Some(257));
assert_eq!(syscall("x86_64", "connect").map(|s| s.nr), Some(42));
assert_eq!(syscall("x86_64", "stat").map(|s| s.entry), Some("sys_newstat"));
assert_eq!(class_of("connect"), Class::Network);
assert_eq!(class_of("openat"), Class::File);
assert_eq!(class_of("clone3"), Class::Process);
assert_eq!(class_of("mmap"), Class::Memory);
assert_eq!(class_of("clock_nanosleep"), Class::Time);
assert_eq!(class_of(INDIRECT), Class::Indirect);
}
#[test]
fn the_libc_map_answers_by_libc_and_says_what_is_uncertain() {
let is = |n: &str| syscall("x86_64", n).is_some();
let calls = |s: &str, l: Libc| match libc_map::lookup(s, l, &is) {
Some(Mapped::Calls(c)) => c,
other => panic!("{s}: {other:?}"),
};
// glibc opens with openat, musl with open
assert_eq!(calls("open64", Libc::Glibc), vec![("openat", true)]);
assert_eq!(calls("open", Libc::Musl), vec![("open", true)]);
// a thin wrapper is its own call, in both
assert_eq!(calls("connect", Libc::Glibc), vec![("connect", true)]);
assert_eq!(calls("read", Libc::Musl), vec![("read", true)]);
assert_eq!(calls("lseek64", Libc::Glibc), vec![("lseek", true)]);
// version-dependent / vDSO / contention: uncertain
assert!(calls("fstat64", Libc::Glibc).iter().all(|(_, sure)| !sure));
assert_eq!(calls("clock_gettime", Libc::Glibc), vec![("clock_gettime", false)]);
assert!(calls("pthread_mutex_lock", Libc::Glibc).iter().all(|(c, sure)| *c == "futex" && !sure));
// libc's syslog() is not the syslog system call
assert!(calls("syslog", Libc::Glibc).iter().any(|(c, _)| *c == "socket"));
assert_eq!(calls("syscall", Libc::Glibc), vec![(INDIRECT, false)]);
assert_eq!(libc_map::lookup("memcpy", Libc::Glibc, &is), Some(Mapped::Pure));
assert_eq!(libc_map::lookup("no_such_symbol", Libc::Glibc, &is), None);
assert_eq!(Libc::of_target("x86_64-unknown-linux-musl"), Some(Libc::Musl));
assert_eq!(Libc::of_target("x86_64-apple-darwin"), None);
}
fn ffi(id: &str, link: &str) -> OwnerFn {
OwnerFn { id: id.into(), kind: nk::FFI.into(), name: link.into(), link_name: Some(link.into()) }
}
fn rust_fn(id: &str, name: &str) -> OwnerFn {
OwnerFn { id: id.into(), kind: nk::FN.into(), name: name.into(), link_name: None }
}
#[test]
fn a_dependency_owner_maps_its_ffi_raw_doors_and_named_raw_callers() {
let fns = vec![
ffi("libc open64", "open64"),
ffi("libc connect", "connect"),
ffi("libc syscall", "syscall"),
ffi("libc strlen", "strlen"),
ffi("libc weird", "__weird_thing"),
rust_fn("std statx", "statx"),
rust_fn("std read", "read"),
];
let raw: BTreeSet<String> = ["std statx".to_string()].into();
let mut st = MapStats::default();
let edges = map_edges("libc@0.2.177:lib", &fns, &raw, &gnu(), &mut st);
let got: BTreeSet<(String, String, bool)> = edges.iter().map(|e| (e.from.clone(), e.to.clone(), e.partial == Some(true))).collect();
let sc = |n: &str| moniker::syscall("x86_64", n);
assert!(got.contains(&("libc open64".into(), sc("openat"), false)), "{got:?}");
assert!(got.contains(&("libc connect".into(), sc("connect"), false)));
assert!(got.contains(&("libc syscall".into(), sc(INDIRECT), true)));
// std's `syscall!` fn: named statx, hands libc::syscall a number → statx, uncertain
assert!(got.contains(&("std statx".into(), sc("statx"), true)));
// a fn merely NAMED like a call, that calls no raw door, is not mapped
assert!(!got.iter().any(|(f, _, _)| f == "std read"));
assert!(edges.iter().all(|e| e.kind == EdgeKind::Syscalls));
assert_eq!(st.ffi_pure, 1);
assert_eq!(st.unmapped.get("__weird_thing"), Some(&1));
// musl: open64 → open
let mut st = MapStats::default();
let m = map_edges("libc@0.2.177:lib", &fns[..1], &BTreeSet::new(), &musl(), &mut st);
assert_eq!(m[0].to, sc("open"));
}
#[test]
fn raw_doors_are_recognised_by_their_ids() {
assert!(is_raw_door_target("rust-analyzer cargo libc 0.2.177 unix/linux_like/linux/syscall()."));
assert!(is_raw_door_target("rust-analyzer cargo libc https://github.com/rust-lang/rust/library/libc new/syscall()."));
assert!(!is_raw_door_target("rust-analyzer cargo libc 0.2.177 unix/linux_like/linux/syscalls()."));
assert!(is_raw_door_target("rust-analyzer cargo rustix 1.0.7 backend/arch/x86_64/syscall3_readonly()."));
assert!(!is_raw_door_target("rust-analyzer cargo mycrate 1.0.0 syscall()."));
}
#[test]
fn the_sample_kernel_reads_into_types_functions_and_entry_links() {
let bytes = btf::sample_kernel();
let kr = kernel_rows(&bytes, "6.99.0-test", "@kernel@6.99.0-test+abc:btf").unwrap();
assert_eq!(kr.arch, Some("x86_64"));
let id = |ns: &str, n: &str| moniker::kernel_item("6.99.0-test", ns, n, None);
let nodes: BTreeSet<&str> = kr.rows.nodes.iter().map(|n| n.id.as_str()).collect();
for want in [id("struct", "task_struct"), id("struct", "pt_regs"), id("typedef", "pid_t"), id("func", "__x64_sys_read"), id("enum", "pid_type"), id("struct", "mm_struct")] {
assert!(nodes.contains(want.as_str()), "{want} missing: {nodes:?}");
}
let has = |f: &str, t: &str, k: EdgeKind| kr.rows.edges.iter().any(|e| e.from == f && e.to == t && e.kind == k);
// task_struct uses pid_t, list_head, and (through the anonymous union and a pointer) mm_struct
assert!(has(&id("struct", "task_struct"), &id("typedef", "pid_t"), EdgeKind::UsesType));
assert!(has(&id("struct", "task_struct"), &id("struct", "mm_struct"), EdgeKind::UsesType));
assert!(has(&id("struct", "task_struct"), &id("struct", "list_head"), EdgeKind::UsesType));
// the entry stub's prototype: const struct pt_regs *
assert!(has(&id("func", "__x64_sys_read"), &id("struct", "pt_regs"), EdgeKind::UsesType));
let sig = kr.rows.nodes.iter().find(|n| n.id == id("func", "__x64_sys_read")).and_then(|n| n.ty.clone()).unwrap();
assert_eq!(sig, "long __x64_sys_read(const struct pt_regs * regs)");
// syscalls → their entry: __x64_sys_* or, for a SYSCALL_DEFINE0 call, __do_sys_*
assert!(has(&moniker::syscall("x86_64", "read"), &id("func", "__x64_sys_read"), EdgeKind::Calls));
assert!(has(&moniker::syscall("x86_64", "connect"), &id("func", "__x64_sys_connect"), EdgeKind::Calls));
assert!(has(&moniker::syscall("x86_64", "getpid"), &id("func", "__do_sys_getpid"), EdgeKind::Calls));
assert_eq!(kr.entries_linked, 3);
assert!(has(&moniker::syscall_crate(), &moniker::kernel_crate("6.99.0-test"), EdgeKind::DependsOn));
}
/// The box's own kernel (or `$MIMIR_TEST_BTF`): a real BTF file.
#[test]
fn a_real_kernel_btf_has_task_struct_and_the_read_entry() {
let path = std::env::var_os("MIMIR_TEST_BTF").map(std::path::PathBuf::from).unwrap_or_else(|| BOX_BTF.into());
if !path.is_file() && std::env::var_os("MIMIR_TEST_NO_BTF").is_some() {
eprintln!("no BTF at {} and MIMIR_TEST_NO_BTF set: skipped", path.display());
return;
}
// a container mounts its own /sys over a bind of /sys/kernel/btf: the
// host's file is not there — mount it elsewhere and name it
assert!(
path.is_file(),
"no BTF file at {} (in a container its own /sys hides a bind of /sys/kernel/btf: mount the host's vmlinux elsewhere and set MIMIR_TEST_BTF, or MIMIR_TEST_NO_BTF=1 to skip)",
path.display()
);
let t = std::time::Instant::now();
let kr = read_kernel_file(&path, "box").unwrap_or_else(|e| panic!("{}: {e:#}", path.display()));
let ms = t.elapsed().as_millis();
let ts = moniker::kernel_item("box", "struct", "task_struct", None);
let node = kr.rows.nodes.iter().find(|n| n.id == ts).expect("task_struct");
assert_eq!(node.kind, nk::STRUCT);
assert!(node.ty.as_deref().unwrap_or("").contains("bytes"), "{:?}", node.ty);
let read = moniker::kernel_item("box", "func", "__x64_sys_read", None);
assert!(kr.rows.nodes.iter().any(|n| n.id == read && n.kind == nk::FN), "__x64_sys_read missing");
assert!(kr.rows.edges.iter().any(|e| e.from == moniker::syscall("x86_64", "read") && e.to == read && e.kind == EdgeKind::Calls));
assert!(kr.rows.edges.iter().any(|e| e.from == ts && e.kind == EdgeKind::UsesType), "task_struct uses no type");
eprintln!(
"real BTF {}: {} types → {} nodes, {} edges, {} syscalls linked ({} without an entry in BTF) in {ms} ms",
path.display(),
kr.types,
kr.rows.nodes.len(),
kr.rows.edges.len(),
kr.entries_linked,
kr.entries_missing.len()
);
}
#[test]
fn table_rows_name_every_call_with_its_class() {
let t = table_rows("x86_64").unwrap();
let read = t.nodes.iter().find(|n| n.id == moniker::syscall("x86_64", "read")).unwrap();
assert_eq!((read.kind.as_str(), read.ty.as_deref()), (nk::SYSCALL, Some("file")));
assert_eq!(read.doc.as_deref(), Some("x86_64 #0 → sys_read"));
assert!(t.nodes.iter().any(|n| n.id == moniker::syscall("x86_64", INDIRECT) && n.ty.as_deref() == Some("indirect")));
assert_eq!(t.owner, "@syscall@x86_64.r1:table");
assert_eq!(map_owner("libc@0.2.177:lib", &gnu()), "@syscall@libc-0.2.177-lib.gnu.r1:map");
}
fn arm_gnu() -> Target {
Target::parse("aarch64-unknown-linux-gnu").unwrap()
}
fn arm_musl() -> Target {
Target::parse("aarch64-unknown-linux-musl").unwrap()
}
/// The generic table arm64 builds: its own numbers, no legacy calls, one
/// class each, the same names as x86_64 where the call exists on both.
#[test]
fn the_aarch64_table_is_the_generic_one_with_its_own_numbers_and_classes() {
let t = table("aarch64").expect("an aarch64 table");
assert!(t.len() > 300, "{}", t.len());
for s in t {
let lists = class::LISTS.iter().filter(|(_, l)| l.contains(&s.name)).count();
assert_eq!(lists, 1, "{} is in {lists} class lists", s.name);
}
let nr = |n: &str| syscall("aarch64", n).map(|s| s.nr);
assert_eq!(nr("io_setup"), Some(0));
assert_eq!(nr("openat"), Some(56));
assert_eq!(nr("read"), Some(63));
assert_eq!(nr("newfstatat"), Some(79));
assert_eq!(nr("clone"), Some(220));
assert_eq!(nr("mmap"), Some(222));
assert_eq!(nr("clone3"), Some(435));
assert_eq!(nr("memfd_secret"), Some(447), "the memfd_secret ABI arm64 builds");
assert_eq!(nr("renameat"), Some(38), "the renameat ABI arm64 builds");
assert_eq!(nr("listns"), Some(470), "past the tbl: the UAPI header");
assert_eq!(syscall("aarch64", "fstat").map(|s| s.entry), Some("sys_newfstat"));
for legacy in ["open", "stat", "fork", "pipe", "poll", "arch_prctl", "syscalls"] {
assert!(nr(legacy).is_none(), "{legacy} is no aarch64 call");
}
assert_eq!(entry_prefix("aarch64"), Some("__arm64_"));
let row = table_rows("aarch64").unwrap();
assert_eq!(row.owner, "@syscall@aarch64.r1:table");
let read = row.nodes.iter().find(|n| n.id == moniker::syscall("aarch64", "read")).unwrap();
assert_eq!(read.doc.as_deref(), Some("aarch64 #63 → sys_read"));
assert!(row.nodes.iter().any(|n| n.id == moniker::syscall("aarch64", INDIRECT)));
assert_eq!((arm_gnu().arch, arm_gnu().libc), ("aarch64", Libc::Glibc));
assert_eq!(Target::parse("riscv64gc-unknown-linux-gnu"), None);
}
/// A legacy call the libc map names (written for x86_64) is its stand-in
/// on aarch64, never dropped: the possible set stays a true maximum.
#[test]
fn an_aarch64_map_turns_every_legacy_call_into_its_generic_stand_in() {
assert_eq!(calls_on("aarch64", "open"), vec!["openat"]);
assert_eq!(calls_on("aarch64", "rename"), vec!["renameat", "renameat2"]);
assert_eq!(calls_on("aarch64", "read"), vec!["read"]);
assert_eq!(calls_on("aarch64", INDIRECT), vec![INDIRECT]);
assert!(calls_on("aarch64", "iopl").is_empty());
assert_eq!(calls_on("x86_64", "open"), vec!["open"]);
for (legacy, ins) in GENERIC_STAND_INS {
assert!(syscall("aarch64", legacy).is_none(), "{legacy} exists on aarch64: no stand-in needed");
assert!(syscall("x86_64", legacy).is_some(), "{legacy} is no legacy x86_64 call");
for i in *ins {
assert!(syscall("aarch64", i).is_some(), "{legacy} → {i}: no aarch64 call");
}
}
let fns = vec![
ffi("libc open", "open"),
ffi("libc unlink", "unlink"),
ffi("libc open64", "open64"),
ffi("libc syscall", "syscall"),
rust_fn("std statx", "statx"),
rust_fn("std stat", "stat"),
];
let raw: BTreeSet<String> = ["std statx".to_string(), "std stat".to_string()].into();
let sc = |n: &str| moniker::syscall("aarch64", n);
let mut st = MapStats::default();
let edges = map_edges("libc@0.2.177:lib", &fns, &raw, &arm_musl(), &mut st);
let got: BTreeSet<(String, String)> = edges.iter().map(|e| (e.from.clone(), e.to.clone())).collect();
// musl's `open` is the open call on x86_64; on aarch64 it is openat
assert!(got.contains(&("libc open".into(), sc("openat"))), "{got:?}");
assert!(got.contains(&("libc unlink".into(), sc("unlinkat"))), "a thin wrapper of a legacy call: {got:?}");
assert!(got.contains(&("libc open64".into(), sc("openat"))));
assert!(got.contains(&("libc syscall".into(), sc(INDIRECT))));
assert!(got.contains(&("std statx".into(), sc("statx"))));
assert!(got.contains(&("std stat".into(), sc("newfstatat"))), "a raw caller named like a legacy call");
assert!(edges.iter().all(|e| e.to.contains("aarch64/")), "only aarch64 calls: {got:?}");
assert!(st.unmapped.is_empty(), "{:?}", st.unmapped);
// the maps of two architectures are two owners
assert_eq!(map_owner("libc@0.2.177:lib", &arm_gnu()), "@syscall@libc-0.2.177-lib.aarch64-gnu.r1:map");
assert_ne!(map_owner("libc@0.2.177:lib", &arm_musl()), map_owner("libc@0.2.177:lib", &musl()));
}