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 continue;
268 }
269 }
270 WaitStatus::Stopped(_, signal) => {
271 ptrace::cont(pid, signal)?;
272 state.set_execution(Execution::Run);
273 }
274 _ => {}
275 }
276 }
277
278 let Some(instr) = last_instr.as_ref() else {
279 continue;
280 };
281
282 if handle_step_over(instr, &mut state) {
283 continue;
284 }
285
286 if state.printed().is_some() {
287 do_progress_ui(instr, &mut state, &mut print, &mut progress)?;
288 }
289
290 if let Execution::Exit = state.execution() {
291 break;
292 }
293 }
294
295 Ok(ret)
296}
297
298pub fn trace_with_default_print(
319 context: &Tracer,
320 process: &process::Info,
321) -> Result<i32> {
322 let state = State::new(process.pid(), None);
323 trace_with(context, process, state, print::default, progress::default)
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 use nix::unistd::Pid;
331
332 use crate::{asm::Parser, print::Layout};
333
334 #[test]
335 fn test_tracer_new() {
336 let path = "/path/to/file";
337 let tracer = Tracer::new(path).expect("Failed to create Tracer instance");
338 assert_eq!(tracer.path(), path);
339 }
340
341 #[test]
342 fn test_handle_step_over_call_and_noncall() {
343 let parser = Parser::new().expect("parser");
344 let opcode_call: [u8; 5] = [0xe8, 0x05, 0x00, 0x00, 0x00];
345 let call_inst = parser.get_instruction_from(&opcode_call, 0x1000).unwrap();
346
347 let opcode_nop: [u8; 1] = [0x90];
348 let nop_inst = parser.get_instruction_from(&opcode_nop, 0x2000).unwrap();
349
350 let mut state = progress::State::new(Pid::from_raw(1), None);
351 state.set_mode(progress::Mode::StepOver);
352
353 let ret = handle_step_over(&call_inst, &mut state);
354 assert!(ret);
355 assert!(matches!(state.execution(), progress::Execution::Run));
356 assert!(matches!(state.mode(), progress::Mode::StepOverInProgress));
357
358 state.set_mode(progress::Mode::StepOver);
359 let ret = handle_step_over(&nop_inst, &mut state);
360 assert!(!ret);
361 assert!(matches!(state.mode(), progress::Mode::StepInto));
362 }
363
364 #[test]
365 fn test_do_progress_ui_applies_requested_layout_and_records_print() {
366 let parser = Parser::new().expect("parser");
367 let opcode_call: [u8; 5] = [0xe8, 0x05, 0x00, 0x00, 0x00];
368 let inst = parser.get_instruction_from(&opcode_call, 0x1000).unwrap();
369
370 let mut state = progress::State::new(Pid::from_raw(1), None);
371 state.set_requested_layout(Layout::Source);
372
373 let mut progress_called = false;
374 let mut print = |_: &asm::Instruction,
375 _layout: &print::Layout|
376 -> crate::diag::Result<Option<String>> {
377 Ok(Some("printed line".to_string()))
378 };
379
380 let mut progress_fn = |_s: &mut State| -> crate::diag::Result<()> {
381 progress_called = true;
382 Ok(())
383 };
384
385 do_progress_ui(&inst, &mut state, &mut print, &mut progress_fn)
386 .expect("do_progress_ui failed");
387
388 assert!(progress_called);
389 assert!(state.printed().is_some());
390 assert_eq!(state.printed().unwrap(), "printed line");
391 assert!(matches!(state.layout(), print::Layout::Source));
392 }
393}