CS110L-Lab/proj-1/deet/src/inferior.rs
2023-03-05 15:51:48 +00:00

125 lines
4.4 KiB
Rust

use nix::sys::ptrace;
use nix::sys::signal;
use nix::sys::signal::Signal;
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
use nix::unistd::Pid;
use std::os::unix::process::CommandExt;
use std::process::Child;
use std::process::Command;
use crate::dwarf_data::DwarfData;
#[derive(Debug)]
pub enum Status {
/// Indicates inferior stopped. Contains the signal that stopped the process, as well as the
/// current instruction pointer that it is stopped at.
Stopped(signal::Signal, usize),
/// Indicates inferior exited normally. Contains the exit status code.
Exited(i32),
/// Indicates the inferior exited due to a signal. Contains the signal that killed the
/// process.
Signaled(signal::Signal),
}
/// This function calls ptrace with PTRACE_TRACEME to enable debugging on a process. You should use
/// pre_exec with Command to call this in the child process.
fn child_traceme() -> Result<(), std::io::Error> {
ptrace::traceme().or(Err(std::io::Error::new(
std::io::ErrorKind::Other,
"ptrace TRACEME failed",
)))
}
pub struct Inferior {
child: Child,
}
impl Inferior {
/// Attempts to start a new inferior process. Returns Some(Inferior) if successful, or None if
/// an error is encountered.
pub fn new(target: &str, args: &Vec<String>) -> Option<Inferior> {
let mut child_cmd = Command::new(target);
child_cmd.args(args);
unsafe {
child_cmd.pre_exec(child_traceme);
}
let child = child_cmd
.spawn()
.expect(format!("Failed to spawn subprocess using command {target}").as_str());
let child_pid = nix::unistd::Pid::from_raw(child.id() as i32);
let fucker = waitpid(child_pid, Some(WaitPidFlag::WSTOPPED)).ok()?;
if let WaitStatus::Stopped(_, signal) = fucker {
if signal != Signal::SIGTRAP {
eprintln!("child stopped by {}, expected SIGTRAP", signal);
return None;
}
} else {
eprintln!("child at status ?, expected to stop");
return None;
}
// println!(
// "Inferior::new not implemented! target={}, args={:?}",
// target, args
// );
Some(Inferior { child })
}
/// Returns the pid of this inferior.
pub fn pid(&self) -> Pid {
nix::unistd::Pid::from_raw(self.child.id() as i32)
}
/// Calls waitpid on this inferior and returns a Status to indicate the state of the process
/// after the waitpid call.
pub fn wait(&self, options: Option<WaitPidFlag>) -> Result<Status, nix::Error> {
Ok(match waitpid(self.pid(), options)? {
WaitStatus::Exited(_pid, exit_code) => Status::Exited(exit_code),
WaitStatus::Signaled(_pid, signal, _core_dumped) => Status::Signaled(signal),
WaitStatus::Stopped(_pid, signal) => {
let regs = ptrace::getregs(self.pid())?;
Status::Stopped(signal, regs.rip as usize)
}
other => panic!("waitpid returned unexpected status: {:?}", other),
})
}
pub fn cont(&self) -> Result<Status, nix::Error> {
ptrace::cont(self.pid(), None)?;
self.wait(None)
}
pub fn kill(&mut self) -> Result<Status, nix::Error> {
let _pid = self.pid();
self.child.kill().expect("Failed to kill subprocess");
self.wait(None)
}
pub fn print_backtrace(&self, debug_data: &DwarfData) -> Result<(), nix::Error> {
let regs = ptrace::getregs(self.pid())?;
let mut pc = regs.rip as usize;
let mut bp = regs.rbp as usize;
loop {
let func_name = debug_data
.get_function_from_addr(pc)
.unwrap_or(String::from("Unknown"));
let file_line = debug_data
.get_line_from_addr(pc)
.unwrap_or(crate::dwarf_data::Line {
file: String::from("Unknown"),
number: 0,
address: 0,
});
// println!("[%rip={:#x}] {} ({})", pc, func_name, file_line);
println!("{} ({})", func_name, file_line);
if func_name == "main" {
break;
}
pc = ptrace::read(self.pid(), (bp + 8) as ptrace::AddressType)? as usize;
bp = ptrace::read(self.pid(), bp as ptrace::AddressType)? as usize;
}
Ok(())
}
}