memgrep 0.1.0

Search for strings in process memory
//! NOTE: Documentation sourced from the `proc(5)` manpage.

use std::{fmt::Display, num::ParseIntError};

use thiserror::Error;

#[allow(clippy::upper_case_acronyms)]
#[derive(Debug)]
pub enum MapsPath {
    Stack,
    Heap,

    /// vDSO (virtual dynamic shared object). See `vdso(7)`.
    VDSO,

    /// vvar page
    VVar,

    /// virtual syscall page
    VSyscall,

    /// Anonymous mapping as obtained via `mmap(2)`.
    /// There is no easy way to coordinate this back to a process's source, short of running it through `gdb(1)`, `strace(1)`, or similar.
    Anonymous,

    /// File
    File(String),
}

impl From<Option<&str>> for MapsPath {
    fn from(path: Option<&str>) -> Self {
        match path {
            None => Self::Anonymous,
            Some(path) => match path {
                "[stack]" => Self::Stack,
                "[heap]" => Self::Heap,
                "[vdso]" => Self::VDSO,
                "[vvar]" => Self::VVar,
                "[vsyscall]" => Self::VSyscall,
                _ => Self::File(path.to_owned()),
            },
        }
    }
}

/// Represents one record (one row) from `/proc/[pid]/maps`
#[derive(Debug)]
pub struct MapsRecord {
    /// Lower bound of the address space in the process that the mapping occupies.
    pub address_lower: usize,

    /// Upper bound of the address space in the process that the mapping occupies.
    pub address_upper: usize,

    /// ```
    /// r/- = read
    /// w/- = write
    /// x/- = execute
    /// s/p = shared / private (copy on write)
    /// ```
    pub perms: String,

    /// Offset into the file.
    pub offset: usize,

    /// Device major [12 bits]:minor [20 bits]
    pub dev: String,

    /// inode on that device. 0 indicates no inode is associated with the memory region as would be the case with BSS (uninitialized data).
    pub inode: u64,

    /// The pathname field will usually be the file that is backing the mapping. For ELF files, you can easily coordinate
    /// with the offset field by looking at the Offset field in the ELF program headers (readelf -l).
    ///
    /// There are additional helpful pseudo-paths (see `MapsPath`).
    ///
    /// If the pathname field is blank, this is an anonymous mapping as obtained via mmap(2).  There is no easy way to coor‐
    /// dinate this back to a process's source, short of running it through gdb(1), strace(1), or similar.
    ///
    /// pathname is shown unescaped except for newline characters, which are replaced with an octal escape sequence.   As  a
    /// result,  it  is not possible to determine whether the original pathname contained a newline character or the literal
    /// \012 character sequence.
    ///
    /// If the mapping is file-backed and the file has been deleted, the string " (deleted)" is appended  to  the  pathname.
    /// Note that this is ambiguous too.
    ///
    /// Under Linux 2.0, there is no field giving pathname.
    pub path: MapsPath,
}

#[derive(Error, Debug)]
pub enum MapsError {
    #[error("Missing field {}", .0)]
    MissingField(String),
    #[error("Address space parse error")]
    AddressSpaceParseError,
    #[error("Parsing error")]
    ParsingError(#[from] ParseIntError),
}

macro_rules! next {
    ($iter:ident, $field:literal) => {
        $iter.next().ok_or_else(|| MapsError::MissingField($field.to_owned()))
    };
}

macro_rules! from_hex {
    ($t:ty, $v:ident) => {
        <$t>::from_str_radix($v, 16)
    };
}

impl MapsRecord {
    pub fn try_from_line<T>(line: T) -> Result<Self, MapsError>
    where
        String: std::convert::From<T>,
    {
        let line_ref: String = line.into();
        let mut iter = line_ref.split_ascii_whitespace();

        let (address_lower, address_upper) = next!(iter, "address_space")?
            .split_once('-')
            .ok_or(MapsError::AddressSpaceParseError)?;
        let perms = next!(iter, "perms")?;
        let offset = next!(iter, "offset")?;
        let dev = next!(iter, "dev")?;
        let inode = next!(iter, "inode")?;
        let path_name = iter.next();

        Ok(Self {
            address_lower: from_hex!(usize, address_lower)?,
            address_upper: from_hex!(usize, address_upper)?,
            perms: perms.to_owned(),
            offset: from_hex!(usize, offset)?,
            dev: dev.to_owned(),
            inode: from_hex!(u64, inode)?,
            path: path_name.into(),
        })
    }
}

impl Display for MapsRecord {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:#018x}-{:#018x}", self.address_lower, self.address_upper)?;
        write!(f, " <{:?}>", self.path)?;
        Ok(())
    }
}