use std::path::{Path, PathBuf};
use std::sync::{Arc, OnceLock, RwLock};
static VMLINUX_BYTES_CACHE: OnceLock<RwLock<std::collections::HashMap<PathBuf, Arc<Vec<u8>>>>> =
OnceLock::new();
pub(crate) fn cached_vmlinux_bytes(path: &Path) -> Option<Arc<Vec<u8>>> {
let canon = std::fs::canonicalize(path)
.ok()
.unwrap_or_else(|| path.to_path_buf());
let slot = VMLINUX_BYTES_CACHE.get_or_init(|| RwLock::new(std::collections::HashMap::new()));
{
let read = slot.read().unwrap_or_else(|e| e.into_inner());
if let Some(bytes) = read.get(&canon) {
return Some(Arc::clone(bytes));
}
}
let bytes = std::fs::read(&canon).ok()?;
let arc = Arc::new(bytes);
let mut write = slot.write().unwrap_or_else(|e| e.into_inner());
Some(Arc::clone(write.entry(canon).or_insert(arc)))
}
pub(crate) fn find_vmlinux(kernel_path: &Path) -> Option<PathBuf> {
let dir = kernel_path.parent()?;
let candidate = dir.join("vmlinux");
if candidate.exists() {
return Some(candidate);
}
if let Ok(root) = dir.join("../../..").canonicalize() {
let candidate = root.join("vmlinux");
if candidate.exists() {
return Some(candidate);
}
}
if let Some(name) = kernel_path.file_name().and_then(|n| n.to_str()) {
let version = name.strip_prefix("vmlinuz-").unwrap_or(name);
for candidate in [
PathBuf::from(format!("/usr/lib/debug/boot/vmlinux-{version}")),
PathBuf::from(format!("/boot/vmlinux-{version}")),
PathBuf::from(format!("/lib/modules/{version}/build/vmlinux")),
] {
if candidate.exists() {
return Some(candidate);
}
}
}
if let Some(parent_name) = dir.file_name().and_then(|n| n.to_str()) {
for candidate in [
dir.join("build/vmlinux"),
PathBuf::from(format!("/boot/vmlinux-{parent_name}")),
] {
if candidate.exists() {
return Some(candidate);
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(target_arch = "x86_64")]
fn find_vmlinux_from_bzimage_path() {
let tmp = std::env::temp_dir().join("ktstr-find-vmlinux-test");
let boot_dir = tmp.join("arch/x86/boot");
std::fs::create_dir_all(&boot_dir).unwrap();
let vmlinux = tmp.join("vmlinux");
std::fs::write(&vmlinux, b"ELF").unwrap();
let bzimage = boot_dir.join("bzImage");
std::fs::write(&bzimage, b"kernel").unwrap();
let found = find_vmlinux(&bzimage);
assert_eq!(found, Some(vmlinux));
std::fs::remove_dir_all(&tmp).unwrap();
}
#[test]
fn find_vmlinux_sibling() {
let tmp = std::env::temp_dir().join("ktstr-find-vmlinux-sibling");
std::fs::create_dir_all(&tmp).unwrap();
let vmlinux = tmp.join("vmlinux");
std::fs::write(&vmlinux, b"ELF").unwrap();
let kernel = tmp.join("bzImage");
std::fs::write(&kernel, b"kernel").unwrap();
let found = find_vmlinux(&kernel);
assert_eq!(found, Some(vmlinux));
std::fs::remove_dir_all(&tmp).unwrap();
}
#[test]
fn find_vmlinux_bare_filename() {
assert_eq!(find_vmlinux(Path::new("vmlinuz")), None);
}
#[test]
fn find_vmlinux_root_parent() {
let result = find_vmlinux(Path::new("/vmlinuz"));
if !Path::new("/vmlinux").exists() {
assert_eq!(result, None);
}
}
#[test]
fn find_vmlinux_missing_returns_none() {
let tmp = std::env::temp_dir().join("ktstr-find-vmlinux-none");
std::fs::create_dir_all(&tmp).unwrap();
let kernel = tmp.join("bzImage");
std::fs::write(&kernel, b"kernel").unwrap();
assert_eq!(find_vmlinux(&kernel), None);
std::fs::remove_dir_all(&tmp).unwrap();
}
#[test]
fn cached_vmlinux_bytes_hits_on_second_call() {
let tmp = std::env::temp_dir().join("ktstr-cached-vmlinux-bytes");
std::fs::create_dir_all(&tmp).unwrap();
let vmlinux = tmp.join("vmlinux-test-cache");
std::fs::write(&vmlinux, b"FAKE_VMLINUX_BYTES").unwrap();
let first = cached_vmlinux_bytes(&vmlinux).expect("first read populates cache");
let second = cached_vmlinux_bytes(&vmlinux).expect("second read hits cache");
assert_eq!(first.as_slice(), b"FAKE_VMLINUX_BYTES");
assert!(
Arc::ptr_eq(&first, &second),
"cache hit must return the same Arc; got fresh allocations on each call"
);
std::fs::remove_file(&vmlinux).ok();
std::fs::remove_dir_all(&tmp).ok();
}
#[test]
fn cached_vmlinux_bytes_missing_returns_none() {
let nonexistent = std::env::temp_dir().join("ktstr-cached-vmlinux-bytes-missing-xyzzy");
std::fs::remove_file(&nonexistent).ok();
assert!(cached_vmlinux_bytes(&nonexistent).is_none());
}
}