#![forbid(unused_must_use)]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![allow(clippy::collapsible_if)]
#![allow(clippy::single_match)]
#![allow(clippy::manual_flatten)]
#![allow(clippy::needless_lifetimes)]
#![allow(clippy::needless_late_init)]
pub mod container;
pub mod dbi;
pub mod encoder;
pub mod globals;
pub mod guid;
pub mod hash;
pub mod lines;
pub mod modi;
pub mod taster;
pub use ::uuid::Uuid;
pub use ms_pdb_msf as msf;
pub use ms_pdb_msfz as msfz;
mod embedded_sources;
pub mod names;
pub mod parser;
pub mod pdbi;
mod stream_index;
pub mod syms;
pub mod tpi;
pub mod types;
pub mod utils;
pub mod writer;
pub use bstr::BStr;
pub use container::{Container, StreamReader};
pub use msfz::StreamData;
pub use stream_index::{Stream, StreamIndexIsNilError, StreamIndexU16, NIL_STREAM_INDEX};
pub use sync_file::{RandomAccessFile, ReadAt, WriteAt};
use anyhow::bail;
use globals::gsi::GlobalSymbolIndex;
use globals::gss::GlobalSymbolStream;
use globals::psi::PublicSymbolIndex;
use names::{NameIndex, NamesStream};
use std::cell::OnceCell;
use std::fmt::Debug;
use std::fs::File;
use std::path::Path;
use syms::{Pub, Sym};
use zerocopy::{FromZeros, IntoBytes};
#[cfg(test)]
#[static_init::dynamic]
static INIT_LOGGER: () = {
tracing_subscriber::fmt()
.with_ansi(false)
.with_test_writer()
.with_file(true)
.with_line_number(true)
.with_max_level(tracing::Level::DEBUG)
.compact()
.without_time()
.finish();
};
pub struct Pdb<F = sync_file::RandomAccessFile> {
container: Container<F>,
dbi_header: dbi::DbiStreamHeader,
dbi_substreams: dbi::DbiSubstreamRanges,
pdbi: pdbi::PdbiStream,
names: OnceCell<NamesStream<Vec<u8>>>,
tpi_header: OnceCell<tpi::CachedTypeStreamHeader>,
ipi_header: OnceCell<tpi::CachedTypeStreamHeader>,
dbi_modules_cell: OnceCell<dbi::ModInfoSubstream<Vec<u8>>>,
dbi_sources_cell: OnceCell<Vec<u8>>,
gss: OnceCell<Box<GlobalSymbolStream>>,
gsi: OnceCell<Box<GlobalSymbolIndex>>,
psi: OnceCell<Box<PublicSymbolIndex>>,
}
#[derive(Copy, Clone, Eq, PartialEq)]
enum AccessMode {
Read,
ReadWrite,
}
impl<F: ReadAt> Pdb<F> {
fn from_file_access(file: F, access_mode: AccessMode) -> anyhow::Result<Box<Self>> {
use crate::taster::{what_flavor, Flavor};
let Some(flavor) = what_flavor(&file)? else {
bail!("The file is not a recognized PDB or PDZ format.");
};
let container = match (flavor, access_mode) {
(Flavor::PortablePdb, _) => bail!("Portable PDBs are not supported."),
(Flavor::Pdb, AccessMode::Read) => Container::Msf(msf::Msf::open_with_file(file)?),
(Flavor::Pdb, AccessMode::ReadWrite) => {
Container::Msf(msf::Msf::modify_with_file(file)?)
}
(Flavor::Pdz, AccessMode::Read) => Container::Msfz(msfz::Msfz::from_file(file)?),
(Flavor::Pdz, AccessMode::ReadWrite) => {
bail!("The MSFZ file format is read-only.")
}
};
let dbi_header = dbi::read_dbi_stream_header(&container)?;
let stream_len = container.stream_len(Stream::DBI.into());
let dbi_substreams = if stream_len != 0 {
dbi::DbiSubstreamRanges::from_sizes(&dbi_header, stream_len as usize)?
} else {
dbi::DbiSubstreamRanges::default()
};
let pdbi_stream_data = container.read_stream_to_vec(Stream::PDB.into())?;
let pdbi = pdbi::PdbiStream::parse(&pdbi_stream_data)?;
Ok(Box::new(Self {
container,
dbi_header,
dbi_substreams,
pdbi,
tpi_header: OnceCell::new(),
ipi_header: OnceCell::new(),
names: OnceCell::new(),
dbi_modules_cell: Default::default(),
dbi_sources_cell: Default::default(),
gss: OnceCell::new(),
gsi: OnceCell::new(),
psi: OnceCell::new(),
}))
}
pub fn pdbi(&self) -> &pdbi::PdbiStream {
&self.pdbi
}
pub fn named_streams(&self) -> &pdbi::NamedStreams {
&self.pdbi.named_streams
}
pub fn named_streams_mut(&mut self) -> &mut pdbi::NamedStreams {
&mut self.pdbi.named_streams
}
pub fn named_stream(&self, name: &str) -> Option<u32> {
self.pdbi.named_streams().get(name)
}
pub fn named_stream_err(&self, name: &str) -> anyhow::Result<u32> {
if let Some(s) = self.pdbi.named_streams().get(name) {
Ok(s)
} else {
anyhow::bail!("There is no stream with the name {:?}.", name);
}
}
pub fn dbi_header(&self) -> &dbi::DbiStreamHeader {
&self.dbi_header
}
pub fn dbi_substreams(&self) -> &dbi::DbiSubstreamRanges {
&self.dbi_substreams
}
pub fn tpi_header(&self) -> anyhow::Result<&tpi::CachedTypeStreamHeader> {
self.tpi_or_ipi_header(Stream::TPI, &self.tpi_header)
}
pub fn ipi_header(&self) -> anyhow::Result<&tpi::CachedTypeStreamHeader> {
self.tpi_or_ipi_header(Stream::IPI, &self.ipi_header)
}
fn tpi_or_ipi_header<'s>(
&'s self,
stream: Stream,
cell: &'s OnceCell<tpi::CachedTypeStreamHeader>,
) -> anyhow::Result<&'s tpi::CachedTypeStreamHeader> {
get_or_init_err(cell, || {
let r = self.get_stream_reader(stream.into())?;
let mut header = tpi::TypeStreamHeader::new_zeroed();
let header_bytes = header.as_mut_bytes();
let bytes_read = r.read_at(header_bytes, 0)?;
if bytes_read == 0 {
return Ok(tpi::CachedTypeStreamHeader { header: None });
}
if bytes_read < header_bytes.len() {
bail!(
"The type stream (stream {}) does not contain enough data for a valid header.",
stream
);
}
Ok(tpi::CachedTypeStreamHeader {
header: Some(header),
})
})
}
pub fn names(&self) -> anyhow::Result<&NamesStream<Vec<u8>>> {
get_or_init_err(&self.names, || {
if let Some(stream) = self.named_stream(names::NAMES_STREAM_NAME) {
let stream_data = self.read_stream_to_vec(stream)?;
Ok(NamesStream::parse(stream_data)?)
} else {
let stream_data = names::EMPTY_NAMES_STREAM_DATA.to_vec();
Ok(NamesStream::parse(stream_data)?)
}
})
}
pub fn get_name(&self, offset: NameIndex) -> anyhow::Result<&BStr> {
let names = self.names()?;
names.get_string(offset)
}
pub fn binding_key(&self) -> BindingKey {
let pdbi = self.pdbi();
pdbi.binding_key()
}
pub fn has_feature(&self, feature_code: pdbi::FeatureCode) -> bool {
self.pdbi.has_feature(feature_code)
}
pub fn mini_pdb(&self) -> bool {
self.has_feature(pdbi::FeatureCode::MINI_PDB)
}
#[inline]
pub fn gss(&self) -> anyhow::Result<&GlobalSymbolStream> {
if let Some(gss) = self.gss.get() {
Ok(gss)
} else {
self.gss_slow()
}
}
#[inline(never)]
fn gss_slow(&self) -> anyhow::Result<&GlobalSymbolStream> {
let box_ref = get_or_init_err(&self.gss, || -> anyhow::Result<Box<GlobalSymbolStream>> {
Ok(Box::new(self.read_gss()?))
})?;
Ok(box_ref)
}
pub fn gss_drop(&mut self) {
self.gss.take();
}
#[inline(never)]
pub fn gsi(&self) -> anyhow::Result<&GlobalSymbolIndex> {
if let Some(gsi) = self.gsi.get() {
Ok(gsi)
} else {
self.gsi_slow()
}
}
#[inline(never)]
fn gsi_slow(&self) -> anyhow::Result<&GlobalSymbolIndex> {
let box_ref = get_or_init_err(&self.gsi, || -> anyhow::Result<Box<GlobalSymbolIndex>> {
Ok(Box::new(self.read_gsi()?))
})?;
Ok(box_ref)
}
pub fn gsi_drop(&mut self) {
self.gsi.take();
}
#[inline]
pub fn psi(&self) -> anyhow::Result<&PublicSymbolIndex> {
if let Some(psi) = self.psi.get() {
Ok(psi)
} else {
self.psi_slow()
}
}
#[inline(never)]
fn psi_slow(&self) -> anyhow::Result<&PublicSymbolIndex> {
let box_ref = get_or_init_err(&self.psi, || -> anyhow::Result<Box<PublicSymbolIndex>> {
Ok(Box::new(self.read_psi()?))
})?;
Ok(box_ref)
}
pub fn psi_drop(&mut self) {
self.psi.take();
}
pub fn find_public_by_name(&self, name: &BStr) -> anyhow::Result<Option<Pub<'_>>> {
let gss = self.gss()?;
let psi = self.psi()?;
psi.find_symbol_by_name(gss, name)
}
pub fn find_global_by_name(&self, name: &'_ BStr) -> anyhow::Result<Option<Sym<'_>>> {
let gss = self.gss()?;
let gsi = self.gsi()?;
gsi.find_symbol(gss, name)
}
pub fn flush_all(&mut self) -> anyhow::Result<bool>
where
F: WriteAt,
{
let mut any = false;
if self.pdbi.named_streams.modified {
let pdbi_data = self.pdbi.to_bytes()?;
let mut w = self.msf_mut_err()?.write_stream(Stream::PDB.into())?;
w.set_contents(&pdbi_data)?;
self.pdbi.named_streams.modified = false;
any = true;
}
Ok(any)
}
}
fn get_or_init_err<T, E, F: FnOnce() -> Result<T, E>>(cell: &OnceCell<T>, f: F) -> Result<&T, E> {
if let Some(value) = cell.get() {
return Ok(value);
}
match f() {
Ok(value) => {
let _ = cell.set(value);
Ok(cell.get().unwrap())
}
Err(e) => Err(e),
}
}
impl Pdb<RandomAccessFile> {
pub fn open(file_name: &Path) -> anyhow::Result<Box<Pdb<RandomAccessFile>>> {
let f = File::open(file_name)?;
let random_file = RandomAccessFile::from(f);
Self::from_file_access(random_file, AccessMode::Read)
}
pub fn open_from_file(file: File) -> anyhow::Result<Box<Self>> {
let random_file = RandomAccessFile::from(file);
Self::from_file_access(random_file, AccessMode::Read)
}
pub fn modify(filename: &Path) -> anyhow::Result<Box<Pdb<sync_file::RandomAccessFile>>> {
let file = File::options().read(true).write(true).open(filename)?;
let random_file = sync_file::RandomAccessFile::from(file);
Self::from_file_access(random_file, AccessMode::ReadWrite)
}
pub fn modify_from_file(file: File) -> anyhow::Result<Box<Self>> {
let random_file = RandomAccessFile::from(file);
Self::from_file_access(random_file, AccessMode::ReadWrite)
}
}
impl<F: ReadAt> Pdb<F> {
pub fn open_from_random_file(random_file: F) -> anyhow::Result<Box<Self>> {
Self::from_file_access(random_file, AccessMode::Read)
}
pub fn modify_from_random_file(random_file: F) -> anyhow::Result<Box<Self>> {
Self::from_file_access(random_file, AccessMode::ReadWrite)
}
}
impl<F> std::ops::Deref for Pdb<F> {
type Target = Container<F>;
fn deref(&self) -> &Self::Target {
&self.container
}
}
impl<F> std::ops::DerefMut for Pdb<F> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.container
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct BindingKey {
pub guid: uuid::Uuid,
pub age: u32,
}
impl Debug for BindingKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.age > 0x1000 {
write!(f, "{:?} age 0x{:x}", self.guid, self.age)
} else {
write!(f, "{:?} age {}", self.guid, self.age)
}
}
}