mod platform;
use std::io::{self, IoSliceMut};
use anyhow::{Context, Result};
use clap::Parser;
use nix::errno::Errno;
use nix::libc::{SYS_write, STDERR_FILENO, STDOUT_FILENO};
use nix::sys::uio::{process_vm_readv, RemoteIoVec};
use nix::sys::wait::WaitStatus;
use nix::sys::{ptrace, wait};
use nix::unistd::Pid;
use platform::{PlatformRegs, SysWriteArgs};
#[derive(Parser, Debug)]
#[clap(version, about, long_about = None)]
pub struct CatpArgs {
pub pid: u32,
#[clap(short, long, action, default_value_t = false)]
pub verbose: bool,
}
fn vm_read_data(pid: Pid, ptr: usize, len: usize) -> Result<Vec<u8>> {
let mut buf = vec![0u8; len];
let mut local = [IoSliceMut::new(buf.as_mut_slice())];
let remote = [RemoteIoVec {
base: ptr as usize,
len: len as usize,
}];
process_vm_readv(pid, &mut local, &remote)?;
Ok(buf)
}
fn read_data(pid: Pid, ptr: usize, len: usize) -> Result<Vec<u8>> {
vm_read_data(pid, ptr, len)
}
pub fn catp<T: io::Write, S: io::Write>(
args: CatpArgs,
stdout: &mut T,
stderr: &mut S,
) -> Result<()> {
let verbose = args.verbose;
if verbose {
eprintln!("{:#?}", args);
}
let pid = Pid::from_raw(args.pid as i32);
let opts = ptrace::Options::PTRACE_O_TRACESYSGOOD;
ptrace::attach(pid).with_context(|| "attach")?;
let mut first_ptrace_stop = true;
let mut in_syscall = false;
loop {
match wait::wait() {
Ok(WaitStatus::PtraceSyscall(pid)) => {
if in_syscall {
in_syscall = false;
} else {
let regs = ptrace::getregs(pid).with_context(|| "getregs")?;
let sysno = regs.get_sysno();
if sysno == SYS_write as usize {
if verbose {
eprintln!("{:?}, {:?}", pid, regs);
}
let fd: i32 = regs.get_fd();
if fd == STDOUT_FILENO || fd == STDERR_FILENO {
let ptr = regs.get_ptr();
let len = regs.get_len();
let data = read_data(pid, ptr, len).with_context(|| "read_data")?;
if verbose {
eprintln!("{}, {:?} {:?} {:?}", fd, ptr, len, data);
}
if fd == STDOUT_FILENO {
stdout.write_all(&data)?;
} else {
stderr.write_all(&data)?;
}
}
}
in_syscall = true;
}
ptrace::syscall(pid, None).with_context(|| "syscall")?;
}
Ok(WaitStatus::Stopped(pid, sig)) => {
if first_ptrace_stop
&& ptrace::setoptions(pid, opts)
.with_context(|| "setoptions")
.is_ok()
{
first_ptrace_stop = false;
ptrace::syscall(pid, None).with_context(|| "syscall")?
} else {
ptrace::syscall(pid, sig).with_context(|| "syscall")?
}
}
Ok(_) => break,
Err(Errno::ECHILD) => break,
Err(e) => {
return Err(e.into());
}
}
}
Ok(())
}