sys_rs/progress.rs
1use nix::unistd::Pid;
2
3use crate::{
4 breakpoint,
5 debug::{Dwarf, LineInfo},
6 diag::Result,
7 print::Layout,
8};
9
10/// Execution state for the tracer loop.
11pub enum Execution {
12 /// The tracer should stop and exit.
13 Exit,
14 /// The tracer should continue running.
15 Run,
16 /// The tracer should skip waiting for the child and process UI.
17 Skip,
18}
19
20/// Tracing mode used to control stepping behavior.
21pub enum Mode {
22 /// Continue running until the next breakpoint.
23 Continue,
24 /// Step into the next instruction.
25 StepInto,
26 /// Step over the next instruction.
27 StepOver,
28 /// Internal state used while a step-over is in progress.
29 StepOverInProgress,
30}
31
32/// Runtime state shared with progress callbacks and handlers.
33pub struct State<'a> {
34 pid: Pid,
35 dwarf: Option<&'a Dwarf<'a>>,
36 breakpoint_mgr: breakpoint::Manager,
37 layout: Layout,
38 requested_layout: Option<Layout>,
39 execution: Execution,
40 mode: Mode,
41 prev_rip: Option<u64>,
42 printed: Option<String>,
43}
44
45impl<'a> State<'a> {
46 #[must_use]
47 /// Create a new runtime `State` for the given `pid`.
48 ///
49 /// # Arguments
50 ///
51 /// * `pid` - The PID of the traced process.
52 /// * `dwarf` - Optional DWARF info used for source lookups.
53 ///
54 /// # Returns
55 ///
56 /// A freshly-initialized `State` ready for tracing.
57 pub fn new(pid: Pid, dwarf: Option<&'a Dwarf<'a>>) -> Self {
58 Self {
59 pid,
60 dwarf,
61 breakpoint_mgr: breakpoint::Manager::new(pid),
62 layout: Layout::from(dwarf.is_some()),
63 requested_layout: None,
64 execution: Execution::Run,
65 mode: Mode::StepInto,
66 prev_rip: None,
67 printed: None,
68 }
69 }
70
71 #[must_use]
72 /// Return the PID associated with this state.
73 ///
74 /// # Returns
75 ///
76 /// The `Pid` belonging to the traced process.
77 pub fn pid(&self) -> Pid {
78 self.pid
79 }
80
81 /// Resolve an address to DWARF line information when available.
82 ///
83 /// # Arguments
84 ///
85 /// * `addr` - The target runtime address to resolve.
86 ///
87 /// # Returns
88 ///
89 /// `Ok(Some(LineInfo))` when DWARF had a mapping, `Ok(None)` when no
90 /// mapping is available.
91 ///
92 /// # Errors
93 ///
94 /// Returns `Err` if a DWARF lookup failure occurs during address resolution.
95 pub fn addr2line(&self, addr: u64) -> Result<Option<LineInfo>> {
96 self.dwarf.map_or(Ok(None), |dwarf| dwarf.addr2line(addr))
97 }
98
99 #[must_use]
100 /// Return the initial layout computed from available DWARF.
101 ///
102 /// # Returns
103 ///
104 /// The initial `Layout` chosen based on whether DWARF is available.
105 pub fn initial_layout(&self) -> Layout {
106 Layout::from(self.dwarf.is_some())
107 }
108
109 #[must_use]
110 /// Return the currently active layout.
111 ///
112 /// # Returns
113 ///
114 /// A reference to the current `Layout`.
115 pub fn layout(&self) -> &Layout {
116 &self.layout
117 }
118
119 /// Set the active layout.
120 ///
121 /// # Arguments
122 ///
123 /// * `layout` - The new `Layout` to activate.
124 pub fn set_layout(&mut self, layout: Layout) {
125 self.layout = layout;
126 }
127
128 /// Request a new layout which will be applied by the tracer loop.
129 ///
130 /// # Arguments
131 ///
132 /// * `layout` - The requested `Layout` which will be applied asynchronously by the tracer loop.
133 pub fn set_requested_layout(&mut self, layout: Layout) {
134 self.requested_layout = Some(layout);
135 }
136
137 #[must_use]
138 /// Take and return any requested layout.
139 ///
140 /// # Returns
141 ///
142 /// `Some(Layout)` when a layout was requested, otherwise `None`.
143 pub fn take_requested_layout(&mut self) -> Option<Layout> {
144 self.requested_layout.take()
145 }
146
147 /// Mutable access to the breakpoint manager owned by the state.
148 ///
149 /// # Returns
150 ///
151 /// A mutable reference to the internal `breakpoint::Manager`.
152 pub fn breakpoint_mgr(&mut self) -> &mut breakpoint::Manager {
153 &mut self.breakpoint_mgr
154 }
155
156 /// Print the currently registered breakpoints to stdout.
157 ///
158 /// This function writes the breakpoint manager's display representation to stdout.
159 pub fn print_breakpoints(&self) {
160 println!("{}", self.breakpoint_mgr);
161 }
162
163 #[must_use]
164 /// Return the previous RIP (if set).
165 ///
166 /// # Returns
167 ///
168 /// The previous `RIP` value if one has been recorded.
169 pub fn prev_rip(&self) -> Option<u64> {
170 self.prev_rip
171 }
172
173 /// Set the previous RIP value.
174 ///
175 /// # Arguments
176 ///
177 /// * `rip` - The RIP value to record as previous.
178 pub fn set_prev_rip(&mut self, rip: u64) {
179 self.prev_rip = Some(rip);
180 }
181
182 #[must_use]
183 /// Return the last printed text (if any).
184 ///
185 /// # Returns
186 ///
187 /// An optional reference to the last printed string.
188 pub fn printed(&self) -> Option<&String> {
189 self.printed.as_ref()
190 }
191
192 /// Set the last printed text.
193 ///
194 /// # Arguments
195 ///
196 /// * `printed` - Optional string to record as the last printed text.
197 pub fn set_printed(&mut self, printed: Option<String>) {
198 self.printed = printed;
199 }
200
201 #[must_use]
202 /// Return the current execution state.
203 ///
204 /// # Returns
205 ///
206 /// A reference to the `Execution` enum representing the current execution state.
207 pub fn execution(&self) -> &Execution {
208 &self.execution
209 }
210
211 /// Update the execution state.
212 ///
213 /// # Arguments
214 ///
215 /// * `execution` - The new `Execution` state to apply.
216 pub fn set_execution(&mut self, execution: Execution) {
217 self.execution = execution;
218 }
219
220 #[must_use]
221 /// Return the current tracing mode.
222 ///
223 /// # Returns
224 ///
225 /// A reference to the current `Mode` used for tracing.
226 pub fn mode(&self) -> &Mode {
227 &self.mode
228 }
229
230 /// Set the tracing mode.
231 ///
232 /// # Arguments
233 ///
234 /// * `mode` - The new `Mode` to set for tracing.
235 pub fn set_mode(&mut self, mode: Mode) {
236 self.mode = mode;
237 }
238}
239
240/// Progress callback signature used by the tracer loop.
241///
242/// The callback is invoked by the tracer loop to allow periodic UI updates
243/// or other bookkeeping. Implementations receive a mutable reference to the
244/// current `State` and may return an error to abort tracing.
245///
246/// # Arguments
247///
248/// * `&mut State` - Mutable reference to the tracer `State`.
249///
250/// # Returns
251///
252/// A `Result<()>` where `Ok(())` continues normal execution and `Err` aborts.
253pub trait ProgressFn = FnMut(&mut State) -> Result<()>;
254
255/// Default no-op progress function.
256///
257/// # Returns
258///
259/// Always returns `Ok(())`.
260///
261/// # Errors
262///
263/// This function never returns an error; it always returns `Ok(())`.
264pub fn default(_: &mut State) -> Result<()> {
265 Ok(())
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 use nix::unistd::Pid;
273
274 #[test]
275 fn test_state_initial_layout_and_pid() {
276 let pid = Pid::from_raw(1234);
277 let state: State = State::new(pid, None);
278 assert_eq!(state.pid(), pid);
279 assert_eq!(state.initial_layout(), *state.layout());
280 }
281
282 #[test]
283 fn test_requested_layout_roundtrip() {
284 let pid = Pid::from_raw(1);
285 let mut state = State::new(pid, None);
286 let orig = state.initial_layout();
287 state.set_requested_layout(orig);
288 let taken = state.take_requested_layout();
289 assert!(taken.is_some());
290 assert_eq!(taken.unwrap(), state.initial_layout());
291 }
292}