use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use anyhow::bail;
use log::LevelFilter;
use crate::run::Options;
#[derive(clap::Args)]
pub struct CommonArgs {
#[arg(long, global = true, value_name = "DIR")]
pub state_directory: Option<PathBuf>,
#[arg(long, global = true, default_value = "info")]
pub log_level: LevelFilter,
}
impl CommonArgs {
pub fn apply(&self, options: Options) -> anyhow::Result<Options> {
Ok(options
.state_dir(self.state_directory.as_deref())?
.log_level(self.log_level))
}
}
#[derive(clap::Args)]
pub struct ComponentArgs {
#[arg(long, value_name = "PATH")]
pub component: Vec<PathBuf>,
#[arg(long = "arg", value_name = "NAME:KEY[=VALUE]")]
pub args: Vec<String>,
}
pub fn component_args_split(args: &ComponentArgs) -> anyhow::Result<Vec<(PathBuf, Vec<String>)>> {
let mut keyed: BTreeMap<String, Vec<String>> = BTreeMap::new();
for entry in &args.args {
let Some((name, flag)) = entry.split_once(':') else {
bail!("`--arg {entry}` is not <name>:<key>[=<value>]");
};
keyed
.entry(name.to_string())
.or_default()
.push(format!("--{flag}"));
}
check_component_names(&args.component, &keyed)?;
Ok(args
.component
.iter()
.map(|path| {
let argv = keyed
.get(&component_name(path))
.cloned()
.unwrap_or_default();
(path.clone(), argv)
})
.collect())
}
fn component_name(path: &Path) -> String {
path.file_stem()
.unwrap_or_default()
.to_string_lossy()
.into_owned()
}
fn check_component_names(
components: &[PathBuf],
component_args: &BTreeMap<String, Vec<String>>,
) -> anyhow::Result<()> {
let mut names: Vec<String> = components.iter().map(|p| component_name(p)).collect();
names.sort();
if let Some(dup) = names.windows(2).find(|w| w[0] == w[1]) {
bail!(
"two --component paths share the name `{}`; \
their `--arg {}:<key>` flags cannot be told apart",
dup[0],
dup[0],
);
}
if let Some(unmatched) = component_args.keys().find(|n| !names.contains(n)) {
bail!(
"`--arg {unmatched}:<key>` names no --component{}",
match names.as_slice() {
[] => " (none were given)".to_string(),
loaded => format!("; loaded: {}", loaded.join(", ")),
}
);
}
Ok(())
}
#[derive(Debug, Clone)]
pub enum AddrSpec {
Virt(u64),
Phys(u64),
Reg(RegName),
Symbol(String),
}
impl FromStr for AddrSpec {
type Err = lexopt::Error;
fn from_str(value: &str) -> Result<AddrSpec, lexopt::Error> {
parse_addr_spec(value)
}
}
#[derive(Debug, Clone, Copy)]
pub enum RegName {
Rip,
Rsp,
Rbp,
Rax,
Rbx,
Rcx,
Rdx,
Rsi,
Rdi,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
Cr0,
Cr2,
Cr3,
Cr4,
KernelGsBase,
Lstar,
}
impl RegName {
fn parse(s: &str) -> Option<RegName> {
Some(match s {
"rip" => RegName::Rip,
"rsp" => RegName::Rsp,
"rbp" => RegName::Rbp,
"rax" => RegName::Rax,
"rbx" => RegName::Rbx,
"rcx" => RegName::Rcx,
"rdx" => RegName::Rdx,
"rsi" => RegName::Rsi,
"rdi" => RegName::Rdi,
"r8" => RegName::R8,
"r9" => RegName::R9,
"r10" => RegName::R10,
"r11" => RegName::R11,
"r12" => RegName::R12,
"r13" => RegName::R13,
"r14" => RegName::R14,
"r15" => RegName::R15,
"cr0" => RegName::Cr0,
"cr2" => RegName::Cr2,
"cr3" => RegName::Cr3,
"cr4" => RegName::Cr4,
"kernel_gs_base" | "gs_base" => RegName::KernelGsBase,
"lstar" => RegName::Lstar,
_ => return None,
})
}
}
#[derive(Debug, Clone)]
pub enum Needle {
Hex(Vec<u8>),
Ascii(String),
Utf16(String),
U64(u64),
U32(u32),
}
impl Needle {
pub fn bytes(&self) -> Vec<u8> {
match self {
Needle::Hex(bytes) => bytes.clone(),
Needle::Ascii(s) => s.clone().into_bytes(),
Needle::Utf16(s) => s.encode_utf16().flat_map(u16::to_le_bytes).collect(),
Needle::U64(value) => value.to_le_bytes().to_vec(),
Needle::U32(value) => value.to_le_bytes().to_vec(),
}
}
}
impl FromStr for Needle {
type Err = lexopt::Error;
fn from_str(value: &str) -> Result<Needle, lexopt::Error> {
let Some((kind, body)) = value.split_once(':') else {
return Err(lexopt::Error::ParsingFailed {
value: value.to_string(),
error: "expected hex:, ascii:, utf16:, u64: or u32:".into(),
});
};
Ok(match kind {
"hex" => Needle::Hex(parse_hex_bytes(body)?),
"ascii" => Needle::Ascii(body.to_string()),
"utf16" => Needle::Utf16(body.to_string()),
"u64" => Needle::U64(parse_addr(body)?),
"u32" => Needle::U32(parse_addr(body)? as u32),
_ => {
return Err(lexopt::Error::ParsingFailed {
value: value.to_string(),
error: "expected hex:, ascii:, utf16:, u64: or u32:".into(),
});
},
})
}
}
#[derive(Debug, Clone, Copy, Default)]
pub enum MemFormat {
#[default]
Hex,
Raw,
Words,
Ascii,
}
impl FromStr for MemFormat {
type Err = lexopt::Error;
fn from_str(value: &str) -> Result<MemFormat, lexopt::Error> {
Ok(match value {
"hex" => MemFormat::Hex,
"raw" => MemFormat::Raw,
"words" => MemFormat::Words,
"ascii" => MemFormat::Ascii,
_ => {
return Err(lexopt::Error::ParsingFailed {
value: value.to_string(),
error: "expected hex, raw, words or ascii".into(),
});
},
})
}
}
#[derive(Debug, Clone, Copy)]
pub enum ModuleScope {
Kernel,
Pid(u32),
All,
}
impl FromStr for ModuleScope {
type Err = lexopt::Error;
fn from_str(value: &str) -> Result<ModuleScope, lexopt::Error> {
Ok(match value {
"kernel" => ModuleScope::Kernel,
"all" => ModuleScope::All,
pid => ModuleScope::Pid(parse_addr(pid)? as u32),
})
}
}
#[derive(Debug, Clone, clap::Subcommand)]
pub enum Query {
Info,
Regs {
#[arg(long)]
all: bool,
},
Translate {
addr: AddrSpec,
#[arg(long, value_parser = parse_addr)]
cr3: Option<u64>,
},
Read {
addr: AddrSpec,
#[arg(long, value_parser = parse_len, default_value = "0x40")]
len: u64,
#[arg(long, value_parser = parse_addr)]
cr3: Option<u64>,
#[arg(long, default_value = "hex")]
format: MemFormat,
},
Disasm {
addr: AddrSpec,
#[arg(long, default_value_t = 10)]
count: u32,
#[arg(long, value_parser = parse_len, default_value = "0")]
len: u64,
#[arg(long, value_parser = parse_addr)]
cr3: Option<u64>,
#[arg(long, default_value_t = 64)]
bits: u32,
},
Search {
addr: AddrSpec,
needle: Needle,
#[arg(long, value_parser = parse_len, conflicts_with = "end")]
len: Option<u64>,
#[arg(long, value_parser = parse_addr)]
end: Option<u64>,
#[arg(long, value_parser = parse_len, default_value = "1")]
stride: u64,
#[arg(long, value_parser = parse_addr)]
cr3: Option<u64>,
#[arg(long, default_value_t = u32::MAX, hide_default_value = true)]
max_hits: u32,
},
Xrefs {
addr: AddrSpec,
#[arg(long, value_parser = parse_addr)]
cr3: Option<u64>,
#[arg(long, default_value_t = u32::MAX, hide_default_value = true)]
max_hits: u32,
},
Ps,
Modules {
#[arg(default_value = "all")]
scope: ModuleScope,
},
Resolve {
spec: String,
#[arg(long)]
pid: Option<u32>,
},
}
#[derive(clap::Args)]
pub struct InspectArgs {
pub checkpoint: PathBuf,
#[command(subcommand)]
pub query: Query,
#[command(flatten)]
pub common: CommonArgs,
}
impl InspectArgs {
pub fn options(&self) -> anyhow::Result<Options> {
self.common.apply(Options::default())
}
}
pub fn parse_len(value: &str) -> Result<u64, lexopt::Error> {
let (digits, scale) = match value.as_bytes().last() {
Some(b'K' | b'k') => (&value[..value.len() - 1], 1u64 << 10),
Some(b'M' | b'm') => (&value[..value.len() - 1], 1u64 << 20),
Some(b'G' | b'g') => (&value[..value.len() - 1], 1u64 << 30),
_ => (value, 1),
};
let n = parse_addr(digits)?;
n.checked_mul(scale)
.ok_or_else(|| lexopt::Error::ParsingFailed {
value: value.to_string(),
error: "length overflows 64 bits".into(),
})
}
pub fn parse_addr(value: &str) -> Result<u64, lexopt::Error> {
let parsed = match value
.strip_prefix("0x")
.or_else(|| value.strip_prefix("0X"))
{
Some(hex) => u64::from_str_radix(hex, 16),
None => value.parse(),
};
parsed.map_err(|_| lexopt::Error::ParsingFailed {
value: value.to_string(),
error: "expected a decimal or 0x-prefixed number".into(),
})
}
pub fn parse_hex_bytes(value: &str) -> Result<Vec<u8>, lexopt::Error> {
let bad = |what: &'static str| lexopt::Error::ParsingFailed {
value: value.to_string(),
error: what.into(),
};
let digits: String = value.chars().filter(|c| !c.is_whitespace()).collect();
if digits.is_empty() || !digits.len().is_multiple_of(2) {
return Err(bad("expected an even number of hex digits"));
}
digits
.as_bytes()
.chunks(2)
.map(|pair| {
let s = std::str::from_utf8(pair).map_err(|_| bad("expected hex digits"))?;
u8::from_str_radix(s, 16).map_err(|_| bad("expected hex digits"))
})
.collect()
}
pub fn parse_addr_spec(value: &str) -> Result<AddrSpec, lexopt::Error> {
if let Some(name) = value.strip_prefix('@') {
return RegName::parse(name).map(AddrSpec::Reg).ok_or_else(|| {
lexopt::Error::ParsingFailed {
value: value.to_string(),
error: "no such register; see `baryl inspect --help`".into(),
}
});
}
if let Some(pa) = value
.strip_prefix("p:")
.or_else(|| value.strip_prefix("phys:"))
{
return Ok(AddrSpec::Phys(parse_addr(pa)?));
}
match parse_addr(value) {
Ok(va) => Ok(AddrSpec::Virt(va)),
Err(_) => Ok(AddrSpec::Symbol(value.to_string())),
}
}