lldb/
thread.rs

1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6
7use crate::{
8    lldb_tid_t, sys, RunMode, SBError, SBEvent, SBFileSpec, SBFrame, SBProcess, SBQueue, SBStream,
9    SBValue, StopReason,
10};
11use std::ffi::{CStr, CString};
12use std::fmt;
13use std::os::raw::c_char;
14use std::ptr;
15
16/// A thread of execution.
17///
18/// `SBThread`s can be referred to by their ID, which maps to the system
19/// specific thread identifier, or by `IndexID`.  The ID may or may not
20/// be unique depending on whether the system reuses its thread identifiers.
21/// The `IndexID` is a monotonically increasing identifier that will always
22/// uniquely reference a particular thread, and when that thread goes
23/// away it will not be reused.
24///
25/// # Thread State
26///
27/// ...
28///
29/// # Execution Control
30///
31/// ...
32///
33/// # Frames
34///
35/// The thread contains [stack frames]. These can be iterated
36/// over with [`SBThread::frames()`]:
37///
38/// ```no_run
39/// # use lldb::{SBFrame, SBThread};
40/// # fn look_at_frames(thread: SBThread) {
41/// // Iterate over the frames...
42/// for frame in thread.frames() {
43///     println!("Hello {:?}!", frame);
44/// }
45/// // Or collect them into a vector!
46/// let frames = thread.frames().collect::<Vec<SBFrame>>();
47/// # }
48/// ```
49///
50/// Some functions operate on the 'currently selected frame'. This can
51/// retrieved via [`SBThread::selected_frame()`] and set via
52/// [`SBThread::set_selected_frame()`].
53///
54///
55/// # Events
56///
57/// ...
58///
59/// [stack frames]: SBFrame
60pub struct SBThread {
61    /// The underlying raw `SBThreadRef`.
62    pub raw: sys::SBThreadRef,
63}
64
65impl SBThread {
66    /// Construct a new `SBThread`.
67    pub(crate) fn wrap(raw: sys::SBThreadRef) -> SBThread {
68        SBThread { raw }
69    }
70
71    /// Construct a new `Some(SBThread)` or `None`.
72    pub(crate) fn maybe_wrap(raw: sys::SBThreadRef) -> Option<SBThread> {
73        if unsafe { sys::SBThreadIsValid(raw) } {
74            Some(SBThread { raw })
75        } else {
76            None
77        }
78    }
79
80    /// Check whether or not this is a valid `SBThread` value.
81    pub fn is_valid(&self) -> bool {
82        unsafe { sys::SBThreadIsValid(self.raw) }
83    }
84
85    #[allow(missing_docs)]
86    pub fn broadcaster_class_name() -> &'static str {
87        unsafe {
88            match CStr::from_ptr(sys::SBThreadGetBroadcasterClassName()).to_str() {
89                Ok(s) => s,
90                _ => panic!("Invalid string?"),
91            }
92        }
93    }
94
95    /// Get the stop reason for this thread.
96    pub fn stop_reason(&self) -> StopReason {
97        unsafe { sys::SBThreadGetStopReason(self.raw) }
98    }
99
100    /// The return value from the last stop if we just stopped due
101    /// to stepping out of a function
102    pub fn stop_return_value(&self) -> Option<SBValue> {
103        SBValue::maybe_wrap(unsafe { sys::SBThreadGetStopReturnValue(self.raw) })
104    }
105
106    /// Returns a unique thread identifier for the current `SBThread`
107    /// that will remain constant throughout the thread's lifetime in
108    /// this process and will not be reused by another thread during this
109    /// process lifetime.  On macOS systems, this is a system-wide
110    /// unique thread identifier; this identifier is also used by
111    /// other tools like sample which helps to associate data from
112    /// those tools with lldb.  See related [`SBThread::index_id`].
113    pub fn thread_id(&self) -> lldb_tid_t {
114        unsafe { sys::SBThreadGetThreadID(self.raw) }
115    }
116
117    /// Return the index number for this `SBThread`.  The index
118    /// number is the same thing that a user gives as an argument
119    /// to `thread select` in the command line lldb.
120    ///
121    /// These numbers start at `1` (for the first thread lldb sees
122    /// in a debug session) and increments up throughout the process
123    /// lifetime.  An index number will not be reused for a different
124    /// thread later in a process - thread 1 will always be associated
125    /// with the same thread.  See related [`SBThread::thread_id`].
126    pub fn index_id(&self) -> u32 {
127        unsafe { sys::SBThreadGetIndexID(self.raw) }
128    }
129
130    /// The name associated with the thread, if any.
131    pub fn name(&self) -> Option<&str> {
132        unsafe { self.check_null_ptr(sys::SBThreadGetName(self.raw)) }
133    }
134
135    /// Return the queue associated with this thread, if any.
136    ///
137    /// If this `SBThread` is actually a history thread, then there may be
138    /// a queue ID and name available, but not a full [`SBQueue`] as the
139    /// individual attributes may have been saved, but without enough
140    /// information to reconstitute the entire `SBQueue` at that time.
141    pub fn queue(&self) -> Option<SBQueue> {
142        SBQueue::maybe_wrap(unsafe { sys::SBThreadGetQueue(self.raw) })
143    }
144
145    /// Return the queue name associated with this thread, if any.
146    ///
147    /// For example, this would report a `libdispatch` (Grand Central Dispatch)
148    /// queue name.
149    pub fn queue_name(&self) -> Option<&str> {
150        unsafe { self.check_null_ptr(sys::SBThreadGetQueueName(self.raw)) }
151    }
152
153    /// Return the `dispatch_queue_id` for this thread, if any.
154    ///
155    /// For example, this would report a `libdispatch` (Grand Central Dispatch)
156    /// queue ID.
157    pub fn queue_id(&self) -> u64 {
158        unsafe { sys::SBThreadGetQueueID(self.raw) }
159    }
160
161    /// Set the user resume state for this thread to suspend.
162    ///
163    /// LLDB currently supports process centric debugging which means when any
164    /// thread in a process stops, all other threads are stopped. The `suspend`
165    /// call here tells our process to suspend a thread and not let it run when
166    /// the other threads in a process are allowed to run. So when
167    /// [`SBProcess::continue_execution()`] is called, any threads that
168    /// aren't suspended will be allowed to run. If any of the `SBThread`
169    /// functions for stepping are called (`step_over`, `step_into`,
170    /// `step_out`, `step_instruction`, `run_to_address`), the thread will
171    /// not be allowed to run and these functions will simply return.
172    pub fn suspend(&self) -> Result<(), SBError> {
173        let error: SBError = SBError::default();
174        unsafe { sys::SBThreadSuspend(self.raw, error.raw) };
175        error.into_result()
176    }
177
178    /// Set the user resume state for this to allow it to run again.
179    ///
180    /// See the discussion on [`SBThread::suspend()`] for further details.
181    pub fn resume(&self) -> Result<(), SBError> {
182        let error: SBError = SBError::default();
183        unsafe { sys::SBThreadResume(self.raw, error.raw) };
184        error.into_result()
185    }
186
187    /// Is this thread set to the suspended user resume state?
188    ///
189    /// See the discussion on [`SBThread::suspend()`] for further details.
190    pub fn is_suspended(&self) -> bool {
191        unsafe { sys::SBThreadIsSuspended(self.raw) }
192    }
193
194    /// Is this thread stopped?
195    pub fn is_stopped(&self) -> bool {
196        unsafe { sys::SBThreadIsStopped(self.raw) }
197    }
198
199    /// Get an iterator over the [frames] known to this thread instance.
200    ///
201    /// [frames]: SBFrame
202    pub fn frames(&self) -> SBThreadFrameIter {
203        SBThreadFrameIter {
204            thread: self,
205            idx: 0,
206        }
207    }
208
209    /// Get the currently selected frame for this thread.
210    pub fn selected_frame(&self) -> SBFrame {
211        SBFrame::wrap(unsafe { sys::SBThreadGetSelectedFrame(self.raw) })
212    }
213
214    /// Set the currently selected frame for this thread. This takes a frame index.
215    pub fn set_selected_frame(&self, frame_index: u32) -> Option<SBFrame> {
216        SBFrame::maybe_wrap(unsafe { sys::SBThreadSetSelectedFrame(self.raw, frame_index) })
217    }
218
219    /// Get the process in which this thread is running.
220    pub fn process(&self) -> SBProcess {
221        SBProcess::wrap(unsafe { sys::SBThreadGetProcess(self.raw) })
222    }
223
224    #[allow(missing_docs)]
225    pub fn step_over(&self, stop_other_threads: RunMode) -> Result<(), SBError> {
226        let error = SBError::default();
227        unsafe { sys::SBThreadStepOver(self.raw, stop_other_threads, error.raw) }
228        if error.is_success() {
229            Ok(())
230        } else {
231            Err(error)
232        }
233    }
234
235    #[allow(missing_docs)]
236    pub fn step_into(&self, stop_other_threads: RunMode) {
237        unsafe {
238            sys::SBThreadStepInto(self.raw, stop_other_threads);
239        }
240    }
241
242    #[allow(missing_docs)]
243    pub fn step_into_until(
244        &self,
245        target_name: Option<&str>,
246        end_line: u32,
247        stop_other_threads: RunMode,
248    ) -> Result<(), SBError> {
249        let error = SBError::default();
250        let target_name =
251            target_name.map(|n| CString::new(n).expect("Invalid target_name supplied."));
252        unsafe {
253            sys::SBThreadStepInto3(
254                self.raw,
255                target_name.map(|s| s.as_ptr()).unwrap_or_else(ptr::null),
256                end_line,
257                error.raw,
258                stop_other_threads,
259            );
260        }
261        if error.is_success() {
262            Ok(())
263        } else {
264            Err(error)
265        }
266    }
267
268    #[allow(missing_docs)]
269    pub fn step_out(&self) -> Result<(), SBError> {
270        let error = SBError::default();
271        unsafe { sys::SBThreadStepOut(self.raw, error.raw) }
272        if error.is_success() {
273            Ok(())
274        } else {
275            Err(error)
276        }
277    }
278
279    /// Step out of the specified frame.
280    pub fn step_out_of_frame(&self, frame: &SBFrame) -> Result<(), SBError> {
281        let error = SBError::default();
282        unsafe { sys::SBThreadStepOutOfFrame(self.raw, frame.raw, error.raw) }
283        if error.is_success() {
284            Ok(())
285        } else {
286            Err(error)
287        }
288    }
289
290    #[allow(missing_docs)]
291    pub fn step_instruction(&self, step_over: bool) -> Result<(), SBError> {
292        let error = SBError::default();
293        unsafe { sys::SBThreadStepInstruction(self.raw, step_over, error.raw) }
294        if error.is_success() {
295            Ok(())
296        } else {
297            Err(error)
298        }
299    }
300
301    #[allow(missing_docs)]
302    pub fn step_over_until(
303        &self,
304        frame: &SBFrame,
305        file_spec: &SBFileSpec,
306        line: u32,
307    ) -> Result<(), SBError> {
308        SBError::wrap(unsafe {
309            sys::SBThreadStepOverUntil(self.raw, frame.raw, file_spec.raw, line)
310        })
311        .into_result()
312    }
313
314    /// If the given event is a thread event, return it as an
315    /// `SBThreadEvent`. Otherwise, return `None`.
316    pub fn event_as_thread_event(event: &SBEvent) -> Option<SBThreadEvent> {
317        if unsafe { sys::SBThreadEventIsThreadEvent(event.raw) } {
318            Some(SBThreadEvent::new(event))
319        } else {
320            None
321        }
322    }
323
324    unsafe fn check_null_ptr(&self, ptr: *const c_char) -> Option<&str> {
325        if !ptr.is_null() {
326            match CStr::from_ptr(ptr).to_str() {
327                Ok(s) => Some(s),
328                _ => panic!("Invalid string?"),
329            }
330        } else {
331            None
332        }
333    }
334}
335
336/// Iterate over the [frames] in a [thread].
337///
338/// [frames]: SBFrame
339/// [thread]: SBThread
340pub struct SBThreadFrameIter<'d> {
341    thread: &'d SBThread,
342    idx: usize,
343}
344
345impl Iterator for SBThreadFrameIter<'_> {
346    type Item = SBFrame;
347
348    fn next(&mut self) -> Option<SBFrame> {
349        if self.idx < unsafe { sys::SBThreadGetNumFrames(self.thread.raw) as usize } {
350            let r = Some(SBFrame::wrap(unsafe {
351                sys::SBThreadGetFrameAtIndex(self.thread.raw, self.idx as u32)
352            }));
353            self.idx += 1;
354            r
355        } else {
356            None
357        }
358    }
359
360    fn size_hint(&self) -> (usize, Option<usize>) {
361        let sz = unsafe { sys::SBThreadGetNumFrames(self.thread.raw) } as usize;
362        (sz - self.idx, Some(sz))
363    }
364}
365
366impl ExactSizeIterator for SBThreadFrameIter<'_> {}
367
368impl Clone for SBThread {
369    fn clone(&self) -> SBThread {
370        SBThread {
371            raw: unsafe { sys::CloneSBThread(self.raw) },
372        }
373    }
374}
375
376impl fmt::Debug for SBThread {
377    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
378        let stream = SBStream::new();
379        unsafe { sys::SBThreadGetDescription(self.raw, stream.raw) };
380        write!(fmt, "SBThread {{ {} }}", stream.data())
381    }
382}
383
384impl Drop for SBThread {
385    fn drop(&mut self) {
386        unsafe { sys::DisposeSBThread(self.raw) };
387    }
388}
389
390unsafe impl Send for SBThread {}
391unsafe impl Sync for SBThread {}
392
393/// A thread event.
394pub struct SBThreadEvent<'e> {
395    event: &'e SBEvent,
396}
397
398impl<'e> SBThreadEvent<'e> {
399    /// Construct a new `SBThreadEvent`.
400    pub fn new(event: &'e SBEvent) -> Self {
401        SBThreadEvent { event }
402    }
403
404    /// Get the thread from this thread event.
405    pub fn thread(&self) -> SBThread {
406        SBThread::wrap(unsafe { sys::SBThreadGetThreadFromEvent(self.event.raw) })
407    }
408
409    /// Get the frame from this thread event.
410    pub fn frame(&self) -> Option<SBFrame> {
411        SBFrame::maybe_wrap(unsafe { sys::SBThreadGetStackFrameFromEvent(self.event.raw) })
412    }
413
414    #[allow(missing_docs)]
415    pub const BROADCAST_BIT_STACK_CHANGED: u32 = (1 << 0);
416    #[allow(missing_docs)]
417    pub const BROADCAST_BIT_THREAD_SUSPENDED: u32 = (1 << 1);
418    #[allow(missing_docs)]
419    pub const BROADCAST_BIT_THREAD_RESUMED: u32 = (1 << 2);
420    #[allow(missing_docs)]
421    pub const BROADCAST_BIT_SELECTED_FRAME_CHANGED: u32 = (1 << 3);
422    #[allow(missing_docs)]
423    pub const BROADCAST_BIT_THREAD_SELECTED: u32 = (1 << 4);
424}
425
426#[cfg(feature = "graphql")]
427#[juniper::graphql_object]
428impl SBThread {
429    // TODO(bm): This should be u64
430    fn thread_id(&self) -> i32 {
431        self.thread_id() as i32
432    }
433
434    // TODO(bm) This should be u32
435    fn index_id() -> i32 {
436        self.index_id() as i32
437    }
438
439    fn frames() -> Vec<SBFrame> {
440        self.frames().collect()
441    }
442
443    fn selected_frame() -> SBFrame {
444        self.selected_frame()
445    }
446
447    fn process() -> SBProcess {
448        self.process()
449    }
450}