use regex::Regex;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::process::Stdio;
use std::time::Duration;
use wait_timeout::ChildExt;
pub mod error;
pub mod mappings;
pub mod memory;
pub mod registers;
pub mod siginfo;
pub mod stacktrace;
#[derive(Debug, Clone)]
pub enum ExecType<'a> {
Local(&'a [&'a str]),
Remote(&'a str),
Core { target: &'a str, core: &'a str },
}
#[derive(Debug)]
pub struct GdbCommand<'a> {
exec_type: ExecType<'a>,
args: Vec<String>,
stdin: Option<&'a PathBuf>,
commands_cnt: usize,
timeout: u64,
}
impl<'a> GdbCommand<'a> {
pub fn new(exec_type: &'a ExecType) -> GdbCommand<'a> {
GdbCommand {
exec_type: exec_type.clone(),
args: Vec::new(),
stdin: None,
commands_cnt: 0,
timeout: 0,
}
}
pub fn stdin<T: Into<Option<&'a PathBuf>>>(&mut self, file: T) -> &'a mut GdbCommand {
self.stdin = file.into();
self
}
pub fn ex<T: Into<String>>(&mut self, cmd: T) -> &'a mut GdbCommand {
self.args.push("-ex".to_string());
self.args
.push(format!("p \"gdb-command-start-{}\"", self.commands_cnt));
self.args.push("-ex".to_string());
self.args.push(cmd.into());
self.args.push("-ex".to_string());
self.args
.push(format!("p \"gdb-command-end-{}\"", self.commands_cnt));
self.commands_cnt += 1;
self
}
pub fn raw(&self) -> error::Result<Vec<u8>> {
let mut gdb = Command::new("gdb");
let mut gdb_args = Vec::new();
match &self.exec_type {
ExecType::Local(args) => {
if !Path::new(args[0]).exists() {
return Err(error::Error::NoFile(args[0].to_string()));
}
gdb_args.push(args[0].to_string())
}
ExecType::Remote(pid) => {
gdb_args.push("-p".to_string());
gdb_args.push(pid.to_string());
}
ExecType::Core { target, core } => {
if !Path::new(target).exists() {
return Err(error::Error::NoFile(target.to_string()));
}
if !Path::new(core).exists() {
return Err(error::Error::NoFile(core.to_string()));
}
gdb_args.push(target.to_string());
gdb_args.push(core.to_string());
}
}
gdb_args.append(&mut vec![
"--batch".to_string(),
"-ex".to_string(),
"set backtrace limit 2000".to_string(),
"-ex".to_string(),
"set disassembly-flavor intel".to_string(),
"-ex".to_string(),
"set filename-display absolute".to_string(),
]);
gdb_args.append(&mut self.args.clone());
gdb.args(&gdb_args);
let output =
if self.timeout != 0 {
let mut child = gdb
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
if child
.wait_timeout(Duration::from_secs(self.timeout))
.unwrap()
.is_none()
{
let _ = child.kill();
return Err(error::Error::Gdb(format!(
"Timeout error: {} sec exceeded",
self.timeout,
)));
}
child.wait_with_output()
} else {
gdb.output()
};
if let Err(e) = output {
return Err(error::Error::Gdb(e.to_string()));
}
let mut output = output.unwrap();
output.stdout.append(&mut output.stderr.clone());
Ok(output.stdout)
}
pub fn r(&mut self) -> &'a mut GdbCommand {
self.args.push("-ex".to_string());
let mut run_command = "r".to_string();
if let ExecType::Local(args) = self.exec_type {
if args.len() > 1 {
run_command.push(' ');
run_command += &args[1..].join(" ");
}
}
if let Some(stdin) = self.stdin {
run_command += &format!(" < {}", stdin.display());
}
self.args.push(run_command);
self
}
pub fn c(&mut self) -> &'a mut GdbCommand {
self.args.push("-ex".to_string());
self.args.push("c".to_string());
self
}
pub fn bt(&mut self) -> &'a mut GdbCommand {
self.ex("bt")
}
pub fn disassembly(&mut self) -> &'a mut GdbCommand {
self.ex("x/16i $pc")
}
pub fn regs(&mut self) -> &'a mut GdbCommand {
self.ex("i r")
}
pub fn mappings(&mut self) -> &'a mut GdbCommand {
self.ex("info proc mappings")
}
pub fn cmdline(&mut self) -> &'a mut GdbCommand {
self.ex("info proc cmdline")
}
pub fn env(&mut self) -> &'a mut GdbCommand {
self.ex("show environment")
}
pub fn status(&mut self) -> &'a mut GdbCommand {
self.ex("info proc status")
}
pub fn sources(&mut self) -> &'a mut GdbCommand {
self.ex("info sources")
}
pub fn bmain(&mut self) -> &'a mut GdbCommand {
self.args.push("-ex".to_string());
self.args.push("b main".to_string());
self
}
pub fn timeout(&mut self, timeout: u64) -> &'a mut GdbCommand {
self.timeout = timeout;
self
}
pub fn list<T: Into<Option<&'a str>>>(&mut self, location: T) -> &'a mut GdbCommand {
if let Some(loc) = location.into() {
self.ex(format!("list {loc}"))
} else {
self.ex("list")
}
}
pub fn mem<T: AsRef<str>>(&mut self, expr: T, size: usize) -> &'a mut GdbCommand {
self.ex(format!("x/{}bx {}", size, expr.as_ref()))
}
pub fn siginfo(&mut self) -> &'a mut GdbCommand {
self.ex("p/x $_siginfo")
}
pub fn launch(&self) -> error::Result<Vec<String>> {
let stdout = self.raw()?;
let output = String::from_utf8_lossy(&stdout);
self.parse(output)
}
pub fn parse<T: AsRef<str>>(&self, output: T) -> error::Result<Vec<String>> {
let lines: Vec<String> = output.as_ref().lines().map(|l| l.to_string()).collect();
let mut results = Vec::new();
(0..self.commands_cnt).for_each(|_| results.push(String::new()));
let re_start = Regex::new(r#"^\$\d+\s*=\s*"gdb-command-start-(\d+)"$"#).unwrap();
let re_end = Regex::new(r#"^\$\d+\s*=\s*"gdb-command-end-(\d+)"$"#).unwrap();
let mut start = 0;
let mut cmd_idx = 0;
for (i, line) in lines.iter().enumerate() {
if let Some(caps) = re_start.captures(line) {
cmd_idx = caps.get(1).unwrap().as_str().parse::<usize>()?;
start = i;
}
if let Some(caps) = re_end.captures(line) {
let end_idx = caps.get(1).unwrap().as_str().parse::<usize>()?;
if end_idx == cmd_idx && cmd_idx < self.commands_cnt {
results[cmd_idx] = lines[start + 1..i].join("\n");
}
}
}
Ok(results)
}
}