use nix::{
sys::{
ptrace,
signal::Signal,
wait::{waitpid, WaitStatus},
},
unistd::Pid,
};
use crate::{
asm,
diag::Result,
hwaccess::Registers,
print::{self, PrintFn},
process,
progress::{self, Execution, Mode, ProgressFn, State},
trace::terminated,
};
pub struct Tracer {
path: String,
parser: asm::Parser,
}
impl Tracer {
pub fn new(path: &str) -> Result<Self> {
Ok(Self {
path: path.to_string(),
parser: asm::Parser::new()?,
})
}
#[must_use]
pub fn path(&self) -> &str {
&self.path
}
}
fn init_tracer(process: &process::Info, state: &mut State, pid: Pid) -> Result<()> {
state
.breakpoint_mgr()
.set_breakpoint(*process.entry()?, true, false, None)?;
ptrace::cont(pid, None)?;
Ok(())
}
fn handle_mode(
process: &process::Info,
state: &mut State,
pid: Pid,
) -> Result<bool> {
match state.mode() {
Mode::Continue => {
state.set_mode(Mode::StepInto);
ptrace::cont(pid, None)?;
println!("Continuing");
Ok(true)
}
Mode::StepOverInProgress => {
state.set_mode(Mode::StepInto);
skip_func(process, state, pid)?;
Ok(true)
}
_ => Ok(false),
}
}
fn handle_step_over(instr: &asm::Instruction, state: &mut State) -> bool {
let mut ret = false;
if let Mode::StepOver = state.mode() {
if instr.is_call() {
state.set_execution(Execution::Run);
state.set_mode(Mode::StepOverInProgress);
ret = true;
} else {
state.set_mode(Mode::StepInto);
}
}
ret
}
fn handle_sigtrap(
context: &Tracer,
process: &process::Info,
state: &mut State,
pid: Pid,
last_instr: &mut Option<asm::Instruction>,
startup_complete: &mut bool,
print: &mut impl PrintFn,
) -> Result<bool> {
let mut regs = Registers::read(pid)?;
if let Some(bp) = state.breakpoint_mgr().handle_breakpoint(&mut regs)? {
let addr = bp.address();
if state
.breakpoint_mgr()
.set_breakpoint(addr, false, true, bp.id())
.is_err()
{
eprintln!("Failed to set breakpoint at address {addr:#x}");
}
}
let rip = regs.rip();
let mut ret = false;
if let Some(opcode) = process.get_opcode_from_addr(rip)? {
let instr = context.parser.get_instruction_from(opcode, rip)?;
state.set_printed(print(&instr, state.layout())?);
state.set_prev_rip(rip);
*last_instr = Some(instr);
} else if let Some(instr) = last_instr.as_ref() {
if instr.is_call() {
if *startup_complete {
skip_func(process, state, pid)?;
ret = true;
} else {
*startup_complete = true;
}
}
if !ret {
*last_instr = None;
}
}
if !ret {
state.breakpoint_mgr().save_breakpoint(rip)?;
ptrace::step(pid, None)?;
state.set_execution(Execution::Run);
}
Ok(ret)
}
fn do_progress_ui(
instr: &asm::Instruction,
state: &mut State,
print: &mut impl PrintFn,
progress: &mut impl ProgressFn,
) -> Result<()> {
progress(state)?;
if let Some(layout) = state.take_requested_layout() {
state.set_layout(layout);
if let Some(line) = print(instr, state.layout())? {
state.set_printed(Some(line));
}
}
Ok(())
}
fn skip_func(process: &process::Info, state: &mut State, pid: Pid) -> Result<()> {
let regs = Registers::read(pid)?;
let ret_addr =
u64::try_from(ptrace::read(pid, regs.rsp() as ptrace::AddressType)?)?;
if process.is_addr_in_section(ret_addr, ".text") {
state
.breakpoint_mgr()
.set_breakpoint(ret_addr, true, false, None)?;
ptrace::cont(pid, None)?;
state.set_execution(Execution::Run);
}
Ok(())
}
pub fn trace_with(
context: &Tracer,
process: &process::Info,
state: State,
mut print: impl PrintFn,
mut progress: impl ProgressFn,
) -> Result<i32> {
let pid = process.pid();
let mut state = state;
let mut ret = 0;
let mut startup_complete = false;
let mut last_instr: Option<asm::Instruction> = None;
init_tracer(process, &mut state, pid)?;
loop {
if let Execution::Run = state.execution() {
let status = waitpid(pid, None)?;
state.breakpoint_mgr().restore_breakpoint()?;
if handle_mode(process, &mut state, pid)? {
continue;
}
if let Some(code) = terminated(status) {
ret = code;
break;
}
match status {
WaitStatus::Stopped(_, Signal::SIGTRAP)
if handle_sigtrap(
context,
process,
&mut state,
pid,
&mut last_instr,
&mut startup_complete,
&mut print,
)? =>
{
continue;
}
WaitStatus::Stopped(_, Signal::SIGTRAP) => {}
WaitStatus::Stopped(_, signal) => {
ptrace::cont(pid, signal)?;
state.set_execution(Execution::Run);
}
_ => {}
}
}
let Some(instr) = last_instr.as_ref() else {
continue;
};
if handle_step_over(instr, &mut state) {
continue;
}
if state.printed().is_some() {
do_progress_ui(instr, &mut state, &mut print, &mut progress)?;
}
if let Execution::Exit = state.execution() {
break;
}
}
Ok(ret)
}
pub fn trace_with_default_print(
context: &Tracer,
process: &process::Info,
) -> Result<i32> {
let state = State::new(process.pid(), None);
trace_with(context, process, state, print::default, progress::default)
}
#[cfg(test)]
mod tests {
use super::*;
use nix::unistd::Pid;
use crate::{asm::Parser, print::Layout};
#[test]
fn test_tracer_new() {
let path = "/path/to/file";
let tracer = Tracer::new(path).expect("Failed to create Tracer instance");
assert_eq!(tracer.path(), path);
}
#[test]
fn test_handle_step_over_call_and_noncall() {
let parser = Parser::new().expect("parser");
let opcode_call: [u8; 5] = [0xe8, 0x05, 0x00, 0x00, 0x00];
let call_inst = parser.get_instruction_from(&opcode_call, 0x1000).unwrap();
let opcode_nop: [u8; 1] = [0x90];
let nop_inst = parser.get_instruction_from(&opcode_nop, 0x2000).unwrap();
let mut state = progress::State::new(Pid::from_raw(1), None);
state.set_mode(progress::Mode::StepOver);
let ret = handle_step_over(&call_inst, &mut state);
assert!(ret);
assert!(matches!(state.execution(), progress::Execution::Run));
assert!(matches!(state.mode(), progress::Mode::StepOverInProgress));
state.set_mode(progress::Mode::StepOver);
let ret = handle_step_over(&nop_inst, &mut state);
assert!(!ret);
assert!(matches!(state.mode(), progress::Mode::StepInto));
}
#[test]
fn test_do_progress_ui_applies_requested_layout_and_records_print() {
let parser = Parser::new().expect("parser");
let opcode_call: [u8; 5] = [0xe8, 0x05, 0x00, 0x00, 0x00];
let inst = parser.get_instruction_from(&opcode_call, 0x1000).unwrap();
let mut state = progress::State::new(Pid::from_raw(1), None);
state.set_requested_layout(Layout::Source);
let mut progress_called = false;
let mut print = |_: &asm::Instruction,
_layout: &print::Layout|
-> crate::diag::Result<Option<String>> {
Ok(Some("printed line".to_string()))
};
let mut progress_fn = |_s: &mut State| -> crate::diag::Result<()> {
progress_called = true;
Ok(())
};
do_progress_ui(&inst, &mut state, &mut print, &mut progress_fn)
.expect("do_progress_ui failed");
assert!(progress_called);
assert!(state.printed().is_some());
assert_eq!(state.printed().unwrap(), "printed line");
assert!(matches!(state.layout(), print::Layout::Source));
}
}