use std::hash::{Hash, Hasher};
use std::io;
use std::ops::{Deref, Range};
use std::path::Path;
use std::sync::Arc;
use crate::{Pid, Tid};
use memmap2::Mmap;
pub(crate) const VDSO_PATH: &str = "[vdso]";
#[derive(Clone)]
pub struct ModulePath(Arc<str>);
impl ModulePath {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
#[must_use]
pub fn as_path(&self) -> &Path {
Path::new(self.as_str())
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.as_str().is_empty()
}
pub(crate) fn is_bracketed_mapping(&self) -> bool {
self.as_str().starts_with('[')
}
pub(crate) fn is_vdso(&self) -> bool {
self.as_str() == VDSO_PATH
}
pub(super) fn from_mmap(mmap: Arc<Mmap>, range: Range<usize>) -> io::Result<Self> {
let bytes = mmap
.get(range)
.ok_or_else(|| super::invalid_data("module path range is outside the spool"))?;
let path =
std::str::from_utf8(bytes).map_err(|err| super::invalid_data(err.to_string()))?;
Ok(Self(Arc::from(path)))
}
}
impl Deref for ModulePath {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl AsRef<str> for ModulePath {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<std::ffi::OsStr> for ModulePath {
fn as_ref(&self) -> &std::ffi::OsStr {
std::ffi::OsStr::new(self.as_str())
}
}
impl AsRef<Path> for ModulePath {
fn as_ref(&self) -> &Path {
Path::new(self.as_str())
}
}
impl std::borrow::Borrow<str> for ModulePath {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl From<String> for ModulePath {
fn from(path: String) -> Self {
Self(Arc::from(path.into_boxed_str()))
}
}
impl From<&str> for ModulePath {
fn from(path: &str) -> Self {
Self(Arc::from(path))
}
}
impl From<ModulePath> for std::rc::Rc<str> {
fn from(path: ModulePath) -> Self {
path.as_str().into()
}
}
impl std::fmt::Debug for ModulePath {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(fmt)
}
}
impl std::fmt::Display for ModulePath {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fmt.write_str(self.as_str())
}
}
impl PartialEq for ModulePath {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for ModulePath {}
impl Hash for ModulePath {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct ModuleRecord {
pub(crate) id: u32,
pub(crate) owner: ModuleOwner,
pub(crate) start: u64,
pub(crate) end: u64,
pub(crate) file_offset: u64,
pub(crate) inode: u64,
pub(crate) device_major: u32,
pub(crate) device_minor: u32,
pub(crate) inode_generation: u64,
pub(crate) path: ModulePath,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum ModuleOwner {
Process(Pid),
Kernel,
}
impl ModuleOwner {
pub(super) fn from_wire(process_id: i32, is_kernel: bool) -> io::Result<Self> {
if is_kernel {
return Ok(Self::Kernel);
}
Pid::try_from(process_id)
.map(Self::Process)
.map_err(|error| super::invalid_data(error.to_string()))
}
pub(crate) const fn pid(self) -> Option<Pid> {
match self {
Self::Process(pid) => Some(pid),
Self::Kernel => None,
}
}
pub(crate) const fn wire_process_id(self) -> i32 {
match self {
Self::Process(pid) => pid.get(),
Self::Kernel => -1,
}
}
pub(crate) const fn is_kernel(self) -> bool {
matches!(self, Self::Kernel)
}
}
impl ModuleRecord {
#[must_use]
pub const fn id(&self) -> u32 {
self.id
}
#[must_use]
pub const fn pid(&self) -> Option<Pid> {
self.owner.pid()
}
#[must_use]
pub const fn address_range(&self) -> std::ops::Range<u64> {
self.start..self.end
}
#[must_use]
pub const fn file_offset(&self) -> u64 {
self.file_offset
}
#[must_use]
pub const fn inode(&self) -> u64 {
self.inode
}
#[must_use]
pub const fn device_major(&self) -> u32 {
self.device_major
}
#[must_use]
pub const fn device_minor(&self) -> u32 {
self.device_minor
}
#[must_use]
pub const fn inode_generation(&self) -> u64 {
self.inode_generation
}
#[must_use]
pub const fn path(&self) -> &ModulePath {
&self.path
}
#[must_use]
pub const fn is_kernel(&self) -> bool {
self.owner.is_kernel()
}
pub(crate) const fn wire_process_id(&self) -> i32 {
self.owner.wire_process_id()
}
#[cfg(test)]
pub(crate) fn set_pid(&mut self, pid: Pid) {
self.owner = ModuleOwner::Process(pid);
}
pub fn new(
id: u32,
process_id: Pid,
addresses: std::ops::Range<u64>,
file_offset: u64,
path: impl Into<ModulePath>,
) -> crate::Result<Self> {
if addresses.start >= addresses.end {
return Err(crate::Error::message(
crate::ErrorKind::InvalidInput,
"module address range must be non-empty",
));
}
Ok(Self {
id,
owner: ModuleOwner::Process(process_id),
start: addresses.start,
end: addresses.end,
file_offset,
inode: 0,
device_major: 0,
device_minor: 0,
inode_generation: 0,
path: path.into(),
})
}
#[must_use]
pub fn file_identity(
mut self,
device_major: u32,
device_minor: u32,
inode: u64,
inode_generation: u64,
) -> Self {
self.device_major = device_major;
self.device_minor = device_minor;
self.inode = inode;
self.inode_generation = inode_generation;
self
}
pub fn kernel(
id: u32,
addresses: std::ops::Range<u64>,
path: impl Into<ModulePath>,
) -> crate::Result<Self> {
if addresses.start >= addresses.end {
return Err(crate::Error::message(
crate::ErrorKind::InvalidInput,
"module address range must be non-empty",
));
}
Ok(Self {
id,
owner: ModuleOwner::Kernel,
start: addresses.start,
end: addresses.end,
file_offset: 0,
inode: 0,
device_major: 0,
device_minor: 0,
inode_generation: 0,
path: path.into(),
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FrameMode {
User,
Kernel,
TruncatedStackMarker,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct FrameRecord {
pub module_id: Option<u32>,
pub file_relative_ip: u64,
pub abs_ip: u64,
pub mode: FrameMode,
}
impl FrameRecord {
#[must_use]
pub fn truncated_stack_marker() -> Self {
Self {
module_id: None,
file_relative_ip: 0,
abs_ip: 0,
mode: FrameMode::TruncatedStackMarker,
}
}
#[must_use]
pub fn is_truncated_stack_marker(&self) -> bool {
*self == Self::truncated_stack_marker()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SampleRecord {
pub timestamp_ns: u64,
pub process_id: Pid,
pub thread_id: Tid,
pub(crate) stack_id: u32,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ThreadRecord {
pub process_id: Pid,
pub thread_id: Tid,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PythonRuntimeRecord {
pub timestamp_ns: u64,
pub process_id: Pid,
pub is_python_runtime: bool,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::mmap_from_bytes;
#[test]
fn mmap_module_path_validates_utf8_and_range() {
let mmap = mmap_from_bytes(b"prefix:/lib/libc.so\xff[vdso]");
let path = ModulePath::from_mmap(mmap.clone(), 7..19).expect("valid path");
let vdso = ModulePath::from_mmap(mmap.clone(), 20..26).expect("valid vdso path");
assert_eq!(path.as_str(), "/lib/libc.so");
assert_eq!(path.as_path(), Path::new("/lib/libc.so"));
assert_eq!(path, ModulePath::from("/lib/libc.so"));
assert!(!path.is_bracketed_mapping());
assert!(vdso.is_bracketed_mapping());
assert!(ModulePath::from_mmap(mmap.clone(), 19..20).is_err());
assert!(ModulePath::from_mmap(mmap, 100..101).is_err());
}
}