Skip to main content

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}