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) -> Option { 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) -> Result { 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 { ptrace::cont(self.pid(), None)?; self.wait(None) } pub fn kill(&mut self) -> Result { 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(()) } }