mod error;
#[cfg(any(feature = "linux", feature = "windows"))]
use std::cell::OnceCell;
use std::{
fs::File,
io::{BufWriter, Write},
path::{Path, PathBuf},
sync::Arc,
};
pub use isr_core::Profile;
pub use isr_dl::{ProgressContext, ProgressEvent, ProgressFn, ProgressWriter};
#[cfg(feature = "linux")]
pub use isr_dl_linux::{
ArtifactPolicy, FilenamePolicy, LinuxBanner, LinuxVersionSignature, UbuntuSymbolDownloader,
UbuntuSymbolPaths, UbuntuSymbolRequest, UbuntuVersionSignature,
};
#[cfg(feature = "windows")]
pub use isr_dl_windows::{CodeView, ImageSignature, SymbolDownloader, SymbolRequest};
use memmap2::Mmap;
use rkyv::ser::{Serializer, allocator::Arena, writer::IoWriter};
pub use self::error::Error;
pub const PROFILE_FILE_EXTENSION: &str = "isr";
pub struct Entry {
profile_path: PathBuf,
data: Mmap,
}
impl Entry {
pub fn new(profile_path: PathBuf) -> Result<Self, Error> {
let data = unsafe { Mmap::map(&File::open(&profile_path)?)? };
Ok(Self { profile_path, data })
}
pub fn profile_path(&self) -> &Path {
&self.profile_path
}
pub fn profile(&self) -> Result<Profile<'_>, Error> {
let archived = rkyv::access::<_, rkyv::rancor::Error>(&self.data)?;
Ok(Profile::from_archived(archived))
}
pub unsafe fn profile_unchecked(&self) -> Result<Profile<'_>, Error> {
let archived = unsafe { rkyv::access_unchecked(&self.data) };
Ok(Profile::from_archived(archived))
}
#[cfg(feature = "json")]
pub fn to_json(&self) -> Result<serde_json::Value, Error> {
let archived =
rkyv::access::<isr_core::schema::ArchivedProfile, rkyv::rancor::Error>(&self.data)?;
let deserialized =
rkyv::deserialize::<isr_core::schema::Profile, rkyv::rancor::Error>(archived)?;
Ok(serde_json::to_value(&deserialized)?)
}
#[allow(unused)]
fn encode(writer: impl Write, profile: &isr_core::schema::Profile) -> Result<(), Error> {
let writer = BufWriter::new(writer);
let mut writer = IoWriter::new(writer);
let mut arena = Arena::new();
let mut serializer = Serializer::new(&mut writer, arena.acquire(), ());
rkyv::api::serialize_using::<_, rkyv::rancor::Error>(profile, &mut serializer)?;
Ok(())
}
}
pub struct IsrCache {
#[cfg(feature = "linux")]
ubuntu_downloader: OnceCell<UbuntuSymbolDownloader>,
#[cfg(feature = "windows")]
symbol_downloader: OnceCell<SymbolDownloader>,
#[allow(unused)]
output_directory: PathBuf,
#[allow(unused)]
progress: Option<ProgressFn>,
#[allow(unused)]
offline: bool,
}
impl IsrCache {
pub fn new(output_directory: impl Into<PathBuf>) -> Result<Self, Error> {
let output_directory = output_directory.into();
std::fs::create_dir_all(&output_directory)?;
Ok(Self {
#[cfg(feature = "linux")]
ubuntu_downloader: OnceCell::new(),
#[cfg(feature = "windows")]
symbol_downloader: OnceCell::new(),
output_directory,
progress: None,
offline: false,
})
}
pub fn with_progress(self, f: impl Fn(ProgressEvent<'_>) + Send + Sync + 'static) -> Self {
Self {
progress: Some(Arc::new(f)),
..self
}
}
pub fn with_offline(self, offline: bool) -> Self {
Self { offline, ..self }
}
#[cfg(feature = "linux")]
pub fn with_ubuntu_downloader(self, ubuntu_downloader: UbuntuSymbolDownloader) -> Self {
Self {
ubuntu_downloader: OnceCell::from(ubuntu_downloader),
..self
}
}
#[cfg(feature = "linux")]
pub fn ubuntu_downloader(&self) -> &UbuntuSymbolDownloader {
self.ubuntu_downloader.get_or_init(|| {
UbuntuSymbolDownloader::builder()
.output_directory(self.output_directory.join("ubuntu"))
.maybe_progress(self.progress.clone())
.build()
})
}
#[cfg(feature = "windows")]
pub fn with_symbol_downloader(self, symbol_downloader: SymbolDownloader) -> Self {
Self {
symbol_downloader: OnceCell::from(symbol_downloader),
..self
}
}
#[cfg(feature = "windows")]
pub fn symbol_downloader(&self) -> &SymbolDownloader {
self.symbol_downloader.get_or_init(|| {
SymbolDownloader::builder()
.output_directory(self.output_directory.join("windows"))
.maybe_progress(self.progress.clone())
.build()
})
}
#[cfg(feature = "linux")]
pub fn entry_from_linux_banner(&self, linux_banner: &str) -> Result<Entry, Error> {
let banner = linux_banner
.parse::<LinuxBanner>()
.map_err(isr_dl::Error::from)?;
let output_paths = match banner.version_signature {
Some(LinuxVersionSignature::Ubuntu(version_signature)) => {
self.download_from_ubuntu_version_signature(version_signature)?
}
_ => {
return Err(Error::Downloader(isr_dl::Error::Other(Box::new(
isr_dl_linux::DownloaderError::InvalidBanner,
))));
}
};
let output_directory = output_paths.output_directory;
let profile_path = output_directory
.join("profile")
.with_extension(PROFILE_FILE_EXTENSION);
with_part_file(profile_path, |profile_file| {
let kernel_file = File::open(output_directory.join("vmlinux-dbgsym"))?;
let systemmap_file = File::open(output_directory.join("System.map"))?;
isr_dwarf::create_profile(kernel_file, systemmap_file, |profile| {
Entry::encode(profile_file, profile)
})?;
Ok(())
})
}
#[cfg(feature = "linux")]
pub fn download_from_ubuntu_version_signature(
&self,
version_signature: UbuntuVersionSignature,
) -> Result<UbuntuSymbolPaths, Error> {
let request = UbuntuSymbolRequest::builder()
.version_signature(version_signature)
.linux_image(
ArtifactPolicy::builder()
.deb(FilenamePolicy::custom("linux-image.deb"))
.extract(FilenamePolicy::custom("vmlinuz"))
.build(),
)
.linux_image_dbgsym(
ArtifactPolicy::builder()
.deb(FilenamePolicy::custom("linux-image-dbgsym.deb"))
.extract(FilenamePolicy::custom("vmlinux-dbgsym"))
.build(),
)
.linux_modules(
ArtifactPolicy::builder()
.deb(FilenamePolicy::custom("linux-modules.deb"))
.extract(FilenamePolicy::custom("System.map"))
.build(),
)
.build();
if let Some(paths) = self.ubuntu_downloader().lookup(&request) {
return Ok(paths);
}
if self.offline {
return Err(Error::Downloader(isr_dl::Error::ArtifactNotFound));
}
let paths = self.ubuntu_downloader().download(request)?;
Ok(paths)
}
#[cfg(feature = "windows")]
pub fn entry_from_codeview(&self, codeview: CodeView) -> Result<Entry, Error> {
let output_directory = self
.output_directory
.join("windows")
.join(codeview.subdirectory());
let pdb_path = self.download_from_codeview(codeview)?;
let profile_path = output_directory
.join("profile")
.with_extension(PROFILE_FILE_EXTENSION);
with_part_file(profile_path, |profile_file| {
let pdb_file = File::open(&pdb_path)?;
isr_pdb::create_profile(pdb_file, |profile| Entry::encode(profile_file, profile))?;
Ok(())
})
}
#[cfg(feature = "windows")]
pub fn entry_from_pe(&self, path: impl AsRef<Path>) -> Result<Entry, Error> {
self.entry_from_codeview(CodeView::from_path(path).map_err(isr_dl::Error::from)?)
}
#[cfg(feature = "windows")]
pub fn download_from_codeview(&self, codeview: CodeView) -> Result<PathBuf, Error> {
let request = codeview.into();
if let Some(pdb_path) = self.symbol_downloader().lookup(&request) {
tracing::debug!(path = %pdb_path.display(), "found cached PE image");
return Ok(pdb_path);
}
if self.offline {
return Err(Error::Downloader(isr_dl::Error::ArtifactNotFound));
}
let pdb_path = self.symbol_downloader().download(request)?;
Ok(pdb_path)
}
#[cfg(feature = "windows")]
pub fn download_from_image_signature(
&self,
image_signature: ImageSignature,
) -> Result<PathBuf, Error> {
let request = image_signature.into();
if let Some(image_path) = self.symbol_downloader().lookup(&request) {
tracing::debug!(path = %image_path.display(), "found cached PE image");
return Ok(image_path);
}
if self.offline {
return Err(Error::Downloader(isr_dl::Error::ArtifactNotFound));
}
let image_path = self.symbol_downloader().download(request)?;
Ok(image_path)
}
}
#[allow(unused)]
fn with_part_file<F>(profile_path: PathBuf, f: F) -> Result<Entry, Error>
where
F: FnOnce(File) -> Result<(), Error>,
{
if profile_path.exists() {
tracing::debug!(
profile_path = %profile_path.display(),
"profile already exists"
);
return Entry::new(profile_path);
}
let tmp = profile_path.with_added_extension("part");
let file = File::create(&tmp)?;
f(file)?;
std::fs::rename(&tmp, &profile_path)?;
Entry::new(profile_path)
}