1use nix::{
2 sys::{
3 ptrace,
4 signal::Signal,
5 wait::{waitpid, WaitStatus},
6 },
7 unistd::Pid,
8};
9
10use crate::{
11 asm,
12 diag::Result,
13 hwaccess::Registers,
14 print::{self, PrintFn},
15 process,
16 progress::{self, Execution, Mode, ProgressFn, State},
17 trace::terminated,
18};
19
20pub struct Tracer {
25 path: String,
26 parser: asm::Parser,
27}
28
29impl Tracer {
30 pub fn new(path: &str) -> Result<Self> {
44 Ok(Self {
45 path: path.to_string(),
46 parser: asm::Parser::new()?,
47 })
48 }
49
50 #[must_use]
51 pub fn path(&self) -> &str {
57 &self.path
58 }
59}
60
61fn init_tracer(process: &process::Info, state: &mut State, pid: Pid) -> Result<()> {
62 state
63 .breakpoint_mgr()
64 .set_breakpoint(*process.entry()?, true, false, None)?;
65 ptrace::cont(pid, None)?;
66
67 Ok(())
68}
69
70fn handle_mode(
71 process: &process::Info,
72 state: &mut State,
73 pid: Pid,
74) -> Result<bool> {
75 match state.mode() {
76 Mode::Continue => {
77 state.set_mode(Mode::StepInto);
78 ptrace::cont(pid, None)?;
79 println!("Continuing");
80 Ok(true)
81 }
82 Mode::StepOverInProgress => {
83 state.set_mode(Mode::StepInto);
84 skip_func(process, state, pid)?;
85 Ok(true)
86 }
87 _ => Ok(false),
88 }
89}
90
91fn handle_step_over(instr: &asm::Instruction, state: &mut State) -> bool {
92 let mut ret = false;
93
94 if let Mode::StepOver = state.mode() {
95 if instr.is_call() {
96 state.set_execution(Execution::Run);
97 state.set_mode(Mode::StepOverInProgress);
98 ret = true;
99 } else {
100 state.set_mode(Mode::StepInto);
101 }
102 }
103
104 ret
105}
106
107fn handle_sigtrap(
108 context: &Tracer,
109 process: &process::Info,
110 state: &mut State,
111 pid: Pid,
112 last_instr: &mut Option<asm::Instruction>,
113 startup_complete: &mut bool,
114 print: &mut impl PrintFn,
115) -> Result<bool> {
116 let mut regs = Registers::read(pid)?;
117 if let Some(bp) = state.breakpoint_mgr().handle_breakpoint(&mut regs)? {
118 let addr = bp.address();
119 if state
120 .breakpoint_mgr()
121 .set_breakpoint(addr, false, true, bp.id())
122 .is_err()
123 {
124 eprintln!("Failed to set breakpoint at address {addr:#x}");
125 }
126 }
127
128 let rip = regs.rip();
129 let mut ret = false;
130
131 if let Some(opcode) = process.get_opcode_from_addr(rip)? {
132 let instr = context.parser.get_instruction_from(opcode, rip)?;
133 state.set_printed(print(&instr, state.layout())?);
134 state.set_prev_rip(rip);
135 *last_instr = Some(instr);
136 } else if let Some(instr) = last_instr.as_ref() {
137 if instr.is_call() {
140 if *startup_complete {
143 skip_func(process, state, pid)?;
144 ret = true;
145 } else {
146 *startup_complete = true;
147 }
148 }
149
150 if !ret {
151 *last_instr = None;
152 }
153 }
154
155 if !ret {
156 state.breakpoint_mgr().save_breakpoint(rip)?;
157 ptrace::step(pid, None)?;
158 state.set_execution(Execution::Run);
159 }
160
161 Ok(ret)
162}
163
164fn do_progress_ui(
165 instr: &asm::Instruction,
166 state: &mut State,
167 print: &mut impl PrintFn,
168 progress: &mut impl ProgressFn,
169) -> Result<()> {
170 progress(state)?;
171
172 if let Some(layout) = state.take_requested_layout() {
173 state.set_layout(layout);
174 if let Some(line) = print(instr, state.layout())? {
175 state.set_printed(Some(line));
176 }
177 }
178
179 Ok(())
180}
181
182fn skip_func(process: &process::Info, state: &mut State, pid: Pid) -> Result<()> {
183 let regs = Registers::read(pid)?;
184 let ret_addr =
185 u64::try_from(ptrace::read(pid, regs.rsp() as ptrace::AddressType)?)?;
186
187 if process.is_addr_in_section(ret_addr, ".text") {
188 state
189 .breakpoint_mgr()
190 .set_breakpoint(ret_addr, true, false, None)?;
191 ptrace::cont(pid, None)?;
192 state.set_execution(Execution::Run);
193 }
194
195 Ok(())
196}
197
198pub fn trace_with(
227 context: &Tracer,
228 process: &process::Info,
229 state: State,
230 mut print: impl PrintFn,
231 mut progress: impl ProgressFn,
232) -> Result<i32> {
233 let pid = process.pid();
234 let mut state = state;
235
236 let mut ret = 0;
237 let mut startup_complete = false;
238 let mut last_instr: Option<asm::Instruction> = None;
239
240 init_tracer(process, &mut state, pid)?;
241
242 loop {
243 if let Execution::Run = state.execution() {
244 let status = waitpid(pid, None)?;
245 state.breakpoint_mgr().restore_breakpoint()?;
246
247 if handle_mode(process, &mut state, pid)? {
248 continue;
249 }
250
251 if let Some(code) = terminated(status) {
252 ret = code;
253 break;
254 }
255
256 match status {
257 WaitStatus::Stopped(_, Signal::SIGTRAP)
258 if handle_sigtrap(
259 context,
260 process,
261 &mut state,
262 pid,
263 &mut last_instr,
264 &mut startup_complete,
265 &mut print,
266 )? =>
267 {
268 continue;
269 }
270 WaitStatus::Stopped(_, Signal::SIGTRAP) => {}
271 WaitStatus::Stopped(_, signal) => {
272 ptrace::cont(pid, signal)?;
273 state.set_execution(Execution::Run);
274 }
275 _ => {}
276 }
277 }
278
279 let Some(instr) = last_instr.as_ref() else {
280 continue;
281 };
282
283 if handle_step_over(instr, &mut state) {
284 continue;
285 }
286
287 if state.printed().is_some() {
288 do_progress_ui(instr, &mut state, &mut print, &mut progress)?;
289 }
290
291 if let Execution::Exit = state.execution() {
292 break;
293 }
294 }
295
296 Ok(ret)
297}
298
299pub fn trace_with_default_print(
320 context: &Tracer,
321 process: &process::Info,
322) -> Result<i32> {
323 let state = State::new(process.pid(), None);
324 trace_with(context, process, state, print::default, progress::default)
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 use nix::unistd::Pid;
332
333 use crate::{asm::Parser, print::Layout};
334
335 #[test]
336 fn test_tracer_new() {
337 let path = "/path/to/file";
338 let tracer = Tracer::new(path).expect("Failed to create Tracer instance");
339 assert_eq!(tracer.path(), path);
340 }
341
342 #[test]
343 fn test_handle_step_over_call_and_noncall() {
344 let parser = Parser::new().expect("parser");
345 let opcode_call: [u8; 5] = [0xe8, 0x05, 0x00, 0x00, 0x00];
346 let call_inst = parser.get_instruction_from(&opcode_call, 0x1000).unwrap();
347
348 let opcode_nop: [u8; 1] = [0x90];
349 let nop_inst = parser.get_instruction_from(&opcode_nop, 0x2000).unwrap();
350
351 let mut state = progress::State::new(Pid::from_raw(1), None);
352 state.set_mode(progress::Mode::StepOver);
353
354 let ret = handle_step_over(&call_inst, &mut state);
355 assert!(ret);
356 assert!(matches!(state.execution(), progress::Execution::Run));
357 assert!(matches!(state.mode(), progress::Mode::StepOverInProgress));
358
359 state.set_mode(progress::Mode::StepOver);
360 let ret = handle_step_over(&nop_inst, &mut state);
361 assert!(!ret);
362 assert!(matches!(state.mode(), progress::Mode::StepInto));
363 }
364
365 #[test]
366 fn test_do_progress_ui_applies_requested_layout_and_records_print() {
367 let parser = Parser::new().expect("parser");
368 let opcode_call: [u8; 5] = [0xe8, 0x05, 0x00, 0x00, 0x00];
369 let inst = parser.get_instruction_from(&opcode_call, 0x1000).unwrap();
370
371 let mut state = progress::State::new(Pid::from_raw(1), None);
372 state.set_requested_layout(Layout::Source);
373
374 let mut progress_called = false;
375 let mut print = |_: &asm::Instruction,
376 _layout: &print::Layout|
377 -> crate::diag::Result<Option<String>> {
378 Ok(Some("printed line".to_string()))
379 };
380
381 let mut progress_fn = |_s: &mut State| -> crate::diag::Result<()> {
382 progress_called = true;
383 Ok(())
384 };
385
386 do_progress_ui(&inst, &mut state, &mut print, &mut progress_fn)
387 .expect("do_progress_ui failed");
388
389 assert!(progress_called);
390 assert!(state.printed().is_some());
391 assert_eq!(state.printed().unwrap(), "printed line");
392 assert!(matches!(state.layout(), print::Layout::Source));
393 }
394}