diff options
author | Santo Cariotti <santo@dcariotti.me> | 2023-10-16 13:46:48 +0200 |
---|---|---|
committer | Santo Cariotti <santo@dcariotti.me> | 2023-10-16 13:46:48 +0200 |
commit | c7d99a1dff61d64ffc26caa8068cf41d5202af3b (patch) | |
tree | d704cf4234a38aa632f2b9066ad08da1489fca46 | |
parent | f9207a326c7f0e5861ee9489313861fdcd7bbff0 (diff) |
Trace attaching a pid
-rw-r--r-- | src/cli.rs | 22 | ||||
-rw-r--r-- | src/main.rs | 54 | ||||
-rw-r--r-- | src/registers.rs | 1 | ||||
-rw-r--r-- | src/trace.rs | 104 | ||||
-rw-r--r-- | src/ui.rs | 131 |
5 files changed, 201 insertions, 111 deletions
diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..685b26f --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,22 @@ +use clap::Parser; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +pub struct Args { + /// Command to execute from ptrace + #[arg(short, long)] + pub command: Option<String>, + + /// Attach the tracing to an existing process ID. We're using the `-p` short flag because + /// strace uses it + #[arg(short = 'p', long)] + pub attach: Option<i32>, + + /// Write the output to a file instead of the standard output + #[arg(short = 'f', long = "file")] + pub file_to_print: Option<String>, + + /// If defined, it hides the TUI + #[arg(long = "no-tui", default_value_t = false)] + pub no_tui: bool, +} diff --git a/src/main.rs b/src/main.rs index 19a9533..136c8c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,50 +1,46 @@ +mod cli; mod registers; mod trace; mod ui; -use std::{ - io::{self, Write}, - str, -}; +use crate::cli::Args; use crate::trace::{exec, trace}; -use crate::ui::run_tui; +use crate::ui::UI; + use clap::Parser; use fork::{fork, Fork}; use nix::unistd::Pid; - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Args { - /// Command to execute from ptrace - command: String, - - /// Write the output to a file instead of the standard output - #[arg(short = 'f', long = "file")] - file_to_print: Option<String>, - - /// If defined, it hides the TUI - #[arg(long = "no-tui", default_value_t = false)] - no_tui: bool, -} +use trace::attach; /// Create a fork of the program and execute the process in the child. Parent gets the pid /// value and trace it. fn main() -> anyhow::Result<()> { let args = Args::parse(); - let pid = match fork() { - Ok(Fork::Child) => return exec(&args.command), - Ok(Fork::Parent(child)) => Pid::from_raw(child), - Err(err) => panic!("fork() failed: {err}"), + let pid = if args.command.is_some() { + match fork() { + Ok(Fork::Child) => return exec(&args.command.unwrap()), + Ok(Fork::Parent(child)) => Pid::from_raw(child), + Err(err) => panic!("fork() failed: {err}"), + } + } else if args.attach.is_some() { + let pid = Pid::from_raw(args.attach.unwrap()); + + if attach(pid).is_ok() { + pid + } else { + panic!("Unable to attach to process {pid}"); + } + } else { + panic!("You must define a command or a PID to attach"); }; - let registers = trace(pid, args.file_to_print)?; if !args.no_tui { - run_tui(pid, ®isters)?; + let mut ui = UI::new(); + + ui.start(pid, &args)?; } else { - for line in registers { - writeln!(io::stdout(), "{}", line.output())?; - } + trace(pid, &args, true)?; } Ok(()) diff --git a/src/registers.rs b/src/registers.rs index d73424d..cf2aa98 100644 --- a/src/registers.rs +++ b/src/registers.rs @@ -6,6 +6,7 @@ use ratatui::{ }; /// Struct used to manipulate registers data from https://docs.rs/libc/0.2.147/libc/struct.user_regs_struct.html +#[derive(Debug)] pub struct RegistersData { orig_rax: u64, rdi: u64, diff --git a/src/trace.rs b/src/trace.rs index 3612519..c097fba 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -1,3 +1,4 @@ +use crate::cli::Args; use crate::registers::RegistersData; use nix::{ sys::{ @@ -7,7 +8,14 @@ use nix::{ }, unistd::Pid, }; -use std::{fs::File, io::Write, os::unix::process::CommandExt, process::Command}; +use std::{ + fs::File, + // fs::File, + io::{self, Write}, + os::unix::process::CommandExt, + process::Command, + str, +}; /// Exec the `command` value tracing it with `ptrace` lib pub fn exec(command: &str) -> anyhow::Result<()> { @@ -25,54 +33,82 @@ pub fn exec(command: &str) -> anyhow::Result<()> { Ok(()) } -/// Trace a process with `pid` ID and returns a list of `RegistersData` -pub fn trace(pid: Pid, file_to_print: Option<String>) -> anyhow::Result<Vec<RegistersData>> { - // Since you have to do 2 syscalls (start and end) you have to alternate the print value, - // because it could be equals except for the `rax` register. - let mut have_to_print = true; +/// Attach a ptrace status to a `pid` +pub fn attach(pid: Pid) -> anyhow::Result<()> { + ptrace::attach(pid)?; + + Ok(()) +} +/// Trace a process with `pid` ID and returns a list of `RegistersData` +pub fn trace(pid: Pid, args: &Args, run_loop: bool) -> anyhow::Result<Vec<RegistersData>> { // First wait for the parent process _ = waitpid(pid, None)?; - // If `fiole_to_print` is not None, create a new file with that value for redirecting all the + // FIXME: file writing on attachment + // If `file_to_print` is not None, create a new file with that value for redirecting all the // output (also in stdout) let mut f = None; - if let Some(filename) = file_to_print { + if let Some(filename) = &args.file_to_print { f = Some(File::create(filename)?); } let mut lines: Vec<RegistersData> = Vec::new(); - loop { - have_to_print ^= true; - ptrace::syscall(pid, None)?; - let status = waitpid(pid, None)?; + if run_loop { + // Since you have to do 2 syscalls (start and end) you have to alternate the print value, + // because it could be equals except for the `rax` register. + let mut have_to_print = true; - match status { - // Break the loop if the process exists - WaitStatus::Exited(_pid, _) => { - break; - } - // Match the stopped value for a process - WaitStatus::Stopped(pid, signal) => { - match signal { - Signal::SIGTRAP => { - if have_to_print { - let reg = RegistersData::new(ptrace::getregs(pid)?); - - if let Some(ref mut f) = f { - writeln!(f, "{}", reg.output())?; - } - - lines.push(reg); + loop { + match trace_next(pid)? { + Some(reg) => { + have_to_print ^= true; + if have_to_print { + if let Some(ref mut f) = f { + writeln!(f, "{}", reg.output())?; + } + + if args.no_tui { + writeln!(io::stdout(), "{}", reg.output())?; } + + lines.push(reg); } - _ => {} - }; + } + None => { + break; + } } - _ => {} - }; + } } - Ok(lines) } + +/// Get the next step for a ptrace process +pub fn trace_next(pid: Pid) -> anyhow::Result<Option<RegistersData>> { + ptrace::syscall(pid, None)?; + let status = waitpid(pid, None).unwrap(); + + match status { + // Match the stopped value for a process + WaitStatus::Stopped(pid, signal) => { + match signal { + Signal::SIGTRAP => { + let reg = RegistersData::new(ptrace::getregs(pid)?); + return Ok(Some(reg)); + } + _ => {} + }; + } + _ => {} + }; + + Ok(None) +} + +/// Kill a process traced by ptrace +pub fn trace_kill(pid: Pid) -> anyhow::Result<()> { + let _ = ptrace::kill(pid); + Ok(()) +} @@ -1,4 +1,8 @@ -use crate::registers::RegistersData; +use crate::{ + cli::Args, + registers::RegistersData, + trace::{trace, trace_kill, trace_next}, +}; use crossterm::{ event::{self, Event, KeyCode}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, @@ -8,19 +12,95 @@ use nix::unistd::Pid; use ratatui::{prelude::*, widgets::*}; use std::io::{self, stdout}; -struct UI { +pub struct UI { height: usize, max_lines: usize, scroll: usize, + lines: Vec<RegistersData>, } impl UI { - fn new() -> UI { + pub fn new() -> UI { UI { height: 0, max_lines: 0, scroll: 0, + lines: vec![], + } + } + + pub fn add_line(&mut self, registers: RegistersData) { + self.lines.push(registers); + self.max_lines = self.lines.len() + 1; + } + + pub fn get_paragraph(&self, pid: Pid) -> Paragraph { + let lines: Vec<Line> = self.lines.iter().map(|x| x.output_ui()).collect(); + let paragraph = Paragraph::new(lines) + .block( + Block::default() + .border_style(Style::default().fg(Color::Yellow)) + .title(format!("[{pid}]")) + .title( + block::Title::from(format!( + "[lines {}-{}]", + self.scroll, + self.scroll + self.height + )) + .position(block::Position::Bottom) + .alignment(Alignment::Right), + ) + .borders(Borders::ALL), + ) + .scroll((self.scroll as u16, 0)); + + paragraph + } + + pub fn start(&mut self, pid: Pid, args: &Args) -> anyhow::Result<()> { + enable_raw_mode()?; + stdout().execute(EnterAlternateScreen)?; + let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; + + let mut have_to_print = true; + let mut have_to_trace = !args.command.is_some(); + let mut should_quit = false; + + let lines = trace(pid, &args, args.command.is_some())?; + if lines.len() > 1 { + for line in lines { + self.add_line(line); + } + } + while !should_quit { + if have_to_trace { + if let Some(reg) = trace_next(pid)? { + have_to_print ^= true; + if have_to_print { + self.add_line(reg); + } + } else { + have_to_trace = false; + } + } + + self.height = terminal.get_frame().size().height as usize; + terminal.draw(|frame| { + let size = frame.size(); + + frame.render_widget(self.get_paragraph(pid), size); + })?; + + should_quit = handle_events(self)?; } + + // FIXME: avoid this kill without Rust errors + let _ = trace_kill(pid); + + disable_raw_mode()?; + stdout().execute(LeaveAlternateScreen)?; + + Ok(()) } } @@ -55,48 +135,3 @@ fn handle_events(ui: &mut UI) -> io::Result<bool> { } Ok(false) } - -pub fn run_tui(pid: Pid, registers: &Vec<RegistersData>) -> anyhow::Result<()> { - enable_raw_mode()?; - stdout().execute(EnterAlternateScreen)?; - let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; - - let mut ui = UI::new(); - ui.max_lines = registers.len() + 1; - - let mut should_quit = false; - while !should_quit { - ui.height = terminal.get_frame().size().height as usize; - terminal.draw(move |frame| { - let size = frame.size(); - let lines: Vec<Line> = registers.iter().map(|x| x.output_ui()).collect(); - - frame.render_widget( - Paragraph::new(lines) - .block( - Block::default() - .border_style(Style::default().fg(Color::Yellow)) - .title(format!("[{pid}]")) - .title( - block::Title::from(format!( - "[lines {}-{}]", - ui.scroll, - ui.scroll + ui.height - )) - .position(block::Position::Bottom) - .alignment(Alignment::Right), - ) - .borders(Borders::ALL), - ) - .scroll((ui.scroll as u16, 0)), - size, - ); - })?; - should_quit = handle_events(&mut ui)?; - } - - disable_raw_mode()?; - stdout().execute(LeaveAlternateScreen)?; - - Ok(()) -} |