Skip to main content

probe_rs/
rtt.rs

1//! Host side implementation of the RTT (Real-Time Transfer) I/O protocol over probe-rs
2//!
3//! RTT implements input and output to/from a microcontroller using in-memory ring buffers and
4//! memory polling. This enables debug logging from the microcontroller with minimal delays and no
5//! blocking, making it usable even in real-time applications where e.g. semihosting delays cannot
6//! be tolerated.
7//!
8//! This crate enables you to read and write via RTT channels. It's also used as a building-block
9//! for probe-rs debugging tools.
10//!
11//! ## Example
12//!
13//! ```no_run
14//! use probe_rs::probe::list::Lister;
15//! use probe_rs::Permissions;
16//! use probe_rs::rtt::Rtt;
17//!
18//! // First obtain a probe-rs session (see probe-rs documentation for details)
19//! let lister = Lister::new();
20//!
21//! let probes = lister.list_all();
22//!
23//! let probe = probes[0].open()?;
24//! let mut session = probe.attach("somechip", Permissions::default())?;
25//! // Select a core.
26//! let mut core = session.core(0)?;
27//!
28//! // Attach to RTT
29//! let mut rtt = Rtt::attach(&mut core)?;
30//!
31//! // Read from a channel
32//! if let Some(input) = rtt.up_channel(0) {
33//!     let mut buf = [0u8; 1024];
34//!     let count = input.read(&mut core, &mut buf[..])?;
35//!
36//!     println!("Read data: {:?}", &buf[..count]);
37//! }
38//!
39//! // Write to a channel
40//! if let Some(output) = rtt.down_channel(0) {
41//!     output.write(&mut core, b"Hello, computer!\n")?;
42//! }
43//!
44//! # Ok::<(), Box<dyn std::error::Error>>(())
45//! ```
46
47mod channel;
48pub use channel::*;
49#[cfg(feature = "object")]
50use object::{Object as _, ObjectSymbol as _};
51
52use crate::Session;
53#[cfg(feature = "object")]
54use crate::meta::ElfMetadata;
55use crate::{Core, MemoryInterface, config::MemoryRegion};
56use std::ops::Range;
57use std::thread;
58use std::time::Duration;
59use std::time::Instant;
60use zerocopy::{FromBytes, IntoBytes};
61
62/// Extract the RTT control block from a raw file, usually an ELF file.
63#[cfg(feature = "object")]
64pub fn find_rtt_control_block_in_raw_file(raw_file: &[u8]) -> Result<Option<u64>, object::Error> {
65    let obj = object::File::parse(raw_file)?;
66    Ok(find_rtt_control_block_in_file(&obj))
67}
68
69/// Extract the RTT control block and metadata from a raw file, usually an ELF file.
70#[cfg(feature = "object")]
71pub fn find_rtt_control_block_and_metadata_in_raw_file(
72    raw_file: &[u8],
73) -> Result<(Option<u64>, ElfMetadata), object::Error> {
74    let obj = object::File::parse(raw_file)?;
75    Ok((
76        find_rtt_control_block_in_file(&obj),
77        ElfMetadata::from_object(&obj)?,
78    ))
79}
80
81/// Extract the RTT control block from a parsed [object::File] file, usually a parsed ELF file.
82#[cfg(feature = "object")]
83pub fn find_rtt_control_block_in_file(file: &object::File) -> Option<u64> {
84    for symbol in file.symbols() {
85        // Get the symbol name
86        if let Ok(name) = symbol.name()
87            && name == "_SEGGER_RTT"
88        {
89            // Ensure it is a defined symbol (not undefined). Symbols with a section index are
90            // defined in the binary.
91            if symbol.section_index().is_some() {
92                return Some(symbol.address());
93            }
94        }
95    }
96    None
97}
98
99/// The RTT interface.
100///
101/// Use [`Rtt::attach`] or [`Rtt::attach_region`] to attach to a probe-rs [`Core`] and detect the
102///     channels, as they were configured on the target. The timing of when this is called is really
103///     important, or else unexpected results can be expected.
104///
105/// ## Examples of how timing between host and target effects the results
106///
107/// 1. **Scenario: Ideal configuration**: The host RTT interface is created **AFTER** the target
108///    program has successfully executing the RTT initialization, by calling an api such as
109///    [`rtt_target::rtt_init_print!()`](https://docs.rs/rtt-target/0.5.0/rtt_target/macro.rtt_init_print.html).
110///
111///    At this point, both the RTT Control Block and the RTT Channel configurations are present in
112///    the target memory, and this RTT interface can be expected to work as expected.
113///
114/// 2. **Scenario: Failure to detect RTT Control Block**: The target has been configured correctly,
115///    **BUT** the host creates this interface **BEFORE** the target program has initialized RTT.
116///
117///    This most commonly occurs when the target halts processing before initializing RTT. For
118///    example, this could happen ...
119///       * During debugging, if the user sets a breakpoint in the code before the RTT
120///         initialization.
121///       * After flashing, if the user has configured `probe-rs` to `reset_after_flashing` AND
122///         `halt_after_reset`. On most targets, this will result in the target halting with
123///         reason `Exception` and will delay the subsequent RTT initialization.
124///       * If RTT initialization on the target is delayed because of time consuming processing or
125///         excessive interrupt handling. This can usually be prevented by moving the RTT
126///         initialization code to the very beginning of the target program logic.
127///
128///     The result of such a timing issue is that `probe-rs` will fail to initialize RTT with an
129///    [`Error::ControlBlockNotFound`]
130///
131/// 3. **Scenario: Incorrect Channel names and incorrect Channel buffer sizes**: This scenario
132///    usually occurs when two conditions coincide. Firstly, the same timing mismatch as described
133///    in point #2 above, and secondly, the target memory has NOT been cleared since a previous
134///    version of the binary program has been flashed to the target.
135///
136///    What happens here is that the RTT Control Block is validated by reading a previously
137///    initialized RTT ID from the target memory. The next step in the logic is then to read the
138///    Channel configuration from the RTT Control block which is usually contains unreliable data
139///    at this point. The symptoms will appear as:
140///       * RTT Channel names are incorrect and/or contain unprintable characters.
141///       * RTT Channel names are correct, but no data, or corrupted data, will be reported from
142///         RTT, because the buffer sizes are incorrect.
143#[derive(Debug)]
144pub struct Rtt {
145    /// The location of the control block in target memory.
146    ptr: u64,
147
148    /// The detected up (target to host) channels.
149    pub up_channels: Vec<UpChannel>,
150
151    /// The detected down (host to target) channels.
152    pub down_channels: Vec<DownChannel>,
153}
154
155#[repr(C)]
156#[derive(FromBytes)]
157struct RttControlBlockHeaderInner {
158    id: [u8; 16],
159    max_up_channels: u32,
160    max_down_channels: u32,
161}
162
163enum RttControlBlockHeader {
164    Header32(RttControlBlockHeaderInner),
165    Header64(RttControlBlockHeaderInner),
166}
167
168impl RttControlBlockHeader {
169    pub fn try_from_header(is_64_bit: bool, mem: &[u8]) -> Option<Self> {
170        if is_64_bit {
171            RttControlBlockHeaderInner::read_from_prefix(mem)
172                .map(|(header, _)| Self::Header64(header))
173                .ok()
174        } else {
175            RttControlBlockHeaderInner::read_from_prefix(mem)
176                .map(|(header, _)| Self::Header32(header))
177                .ok()
178        }
179    }
180
181    pub const fn minimal_header_size() -> usize {
182        std::mem::size_of::<RttControlBlockHeaderInner>()
183    }
184
185    pub const fn header_size(&self) -> usize {
186        std::mem::size_of::<RttControlBlockHeaderInner>()
187    }
188
189    pub fn id(&self) -> [u8; 16] {
190        match self {
191            RttControlBlockHeader::Header32(x) => x.id,
192            RttControlBlockHeader::Header64(x) => x.id,
193        }
194    }
195
196    pub fn max_up_channels(&self) -> usize {
197        match self {
198            RttControlBlockHeader::Header32(x) => x.max_up_channels as usize,
199            RttControlBlockHeader::Header64(x) => x.max_up_channels as usize,
200        }
201    }
202
203    pub fn max_down_channels(&self) -> usize {
204        match self {
205            RttControlBlockHeader::Header32(x) => x.max_down_channels as usize,
206            RttControlBlockHeader::Header64(x) => x.max_down_channels as usize,
207        }
208    }
209
210    pub fn channel_buffer_size(&self) -> usize {
211        match self {
212            RttControlBlockHeader::Header32(_x) => RttChannelBufferInner::<u32>::size(),
213            RttControlBlockHeader::Header64(_x) => RttChannelBufferInner::<u64>::size(),
214        }
215    }
216
217    pub fn total_rtt_buffer_size(&self) -> usize {
218        let total_number_of_channels = self.max_up_channels() + self.max_down_channels();
219        let channel_size = self.channel_buffer_size();
220
221        self.header_size() + channel_size * total_number_of_channels
222    }
223
224    pub fn parse_channel_buffers(&self, mem: &[u8]) -> Result<Vec<RttChannelBuffer>, Error> {
225        let buffers = match self {
226            RttControlBlockHeader::Header32(_) => {
227                <[RttChannelBufferInner<u32>]>::ref_from_bytes(mem)
228                    .map_err(|_| Error::ControlBlockNotFound)?
229                    .iter()
230                    .cloned()
231                    .map(RttChannelBuffer::from)
232                    .collect::<Vec<RttChannelBuffer>>()
233            }
234            RttControlBlockHeader::Header64(_) => {
235                <[RttChannelBufferInner<u64>]>::ref_from_bytes(mem)
236                    .map_err(|_| Error::ControlBlockNotFound)?
237                    .iter()
238                    .cloned()
239                    .map(RttChannelBuffer::from)
240                    .collect::<Vec<RttChannelBuffer>>()
241            }
242        };
243
244        Ok(buffers)
245    }
246}
247
248// Rtt must follow this data layout when reading/writing memory in order to be compatible with the
249// official RTT implementation.
250//
251// struct ControlBlock {
252//     char id[16]; // Used to find/validate the control block.
253//     // Maximum number of up (target to host) channels in following array
254//     unsigned int max_up_channels;
255//     // Maximum number of down (host to target) channels in following array.
256//     unsigned int max_down_channels;
257//     RttChannel up_channels[max_up_channels]; // Array of up (target to host) channels.
258//     RttChannel down_channels[max_down_channels]; // array of down (host to target) channels.
259// }
260impl Rtt {
261    /// The magic string expected to be found at the beginning of the RTT control block.
262    pub const RTT_ID: [u8; 16] = *b"SEGGER RTT\0\0\0\0\0\0";
263
264    /// Tries to attach to an RTT control block at the specified memory address.
265    pub fn attach_at(
266        core: &mut Core,
267        // Pointer from which to scan
268        ptr: u64,
269    ) -> Result<Rtt, Error> {
270        let is_64_bit = core.is_64_bit();
271
272        let mut mem = [0u32; RttControlBlockHeader::minimal_header_size() / 4];
273        // Read the magic value first as unordered data, and read the subsequent pointers
274        // as ordered u32 values.
275        core.read(ptr, &mut mem.as_mut_bytes()[0..Self::RTT_ID.len()])?;
276        core.read_32(
277            ptr + Self::RTT_ID.len() as u64,
278            &mut mem[Self::RTT_ID.len() / 4..],
279        )?;
280
281        let rtt_header = RttControlBlockHeader::try_from_header(is_64_bit, mem.as_bytes())
282            .ok_or(Error::ControlBlockNotFound)?;
283
284        // Validate that the control block starts with the ID bytes
285        let rtt_id = rtt_header.id();
286        if rtt_id != Self::RTT_ID {
287            tracing::trace!(
288                "Expected control block to start with RTT ID: {:?}\n. Got instead: {:?}",
289                String::from_utf8_lossy(&Self::RTT_ID),
290                String::from_utf8_lossy(&rtt_id)
291            );
292            return Err(Error::ControlBlockNotFound);
293        }
294
295        let max_up_channels = rtt_header.max_up_channels();
296        let max_down_channels = rtt_header.max_down_channels();
297
298        // *Very* conservative sanity check, most people only use a handful of RTT channels
299        if max_up_channels > 255 || max_down_channels > 255 {
300            return Err(Error::ControlBlockCorrupted(format!(
301                "Unexpected array sizes at {ptr:#010x}: max_up_channels={max_up_channels} max_down_channels={max_down_channels}"
302            )));
303        }
304
305        // Read the rest of the control block
306        let channel_buffer_len = rtt_header.total_rtt_buffer_size() - rtt_header.header_size();
307        let mut mem = vec![0; channel_buffer_len / 4];
308        core.read_32(ptr + rtt_header.header_size() as u64, &mut mem)?;
309
310        let mut up_channels = Vec::new();
311        let mut down_channels = Vec::new();
312
313        let channel_buffer_size = rtt_header.channel_buffer_size();
314
315        let up_channels_start = 0;
316        let up_channels_len = max_up_channels * channel_buffer_size;
317        let up_channels_raw_buffer = &mem.as_bytes()[up_channels_start..][..up_channels_len];
318        let up_channels_buffer = rtt_header.parse_channel_buffers(up_channels_raw_buffer)?;
319
320        let down_channels_start = up_channels_start + up_channels_len;
321        let down_channels_len = max_down_channels * channel_buffer_size;
322        let down_channels_raw_buffer = &mem.as_bytes()[down_channels_start..][..down_channels_len];
323        let down_channels_buffer = rtt_header.parse_channel_buffers(down_channels_raw_buffer)?;
324
325        let mut offset = ptr + rtt_header.header_size() as u64 + up_channels_start as u64;
326        for (channel_index, buffer) in up_channels_buffer.into_iter().enumerate() {
327            let buffer_size = buffer.size() as u64;
328
329            if let Some(chan) = Channel::from(core, channel_index, offset, buffer)? {
330                up_channels.push(UpChannel(chan));
331            } else {
332                tracing::warn!("Buffer for up channel {channel_index} not initialized");
333            }
334            offset += buffer_size;
335        }
336
337        let mut offset = ptr + rtt_header.header_size() as u64 + down_channels_start as u64;
338        for (channel_index, buffer) in down_channels_buffer.into_iter().enumerate() {
339            let buffer_size = buffer.size() as u64;
340
341            if let Some(chan) = Channel::from(core, channel_index, offset, buffer)? {
342                down_channels.push(DownChannel(chan));
343            } else {
344                tracing::warn!("Buffer for down channel {channel_index} not initialized");
345            }
346            offset += buffer_size;
347        }
348
349        Ok(Rtt {
350            ptr,
351            up_channels,
352            down_channels,
353        })
354    }
355
356    /// Attempts to detect an RTT control block in the specified RAM region(s) and returns an
357    /// instance if a valid control block was found.
358    pub fn attach_region(core: &mut Core, region: &ScanRegion) -> Result<Rtt, Error> {
359        let ptr = Self::find_control_block(core, region)?;
360        Self::attach_at(core, ptr)
361    }
362
363    /// Attempts to detect an RTT control block anywhere in the target RAM and returns an instance
364    /// if a valid control block was found.
365    pub fn attach(core: &mut Core) -> Result<Rtt, Error> {
366        Self::attach_region(core, &ScanRegion::default())
367    }
368
369    /// Attempts to detect an RTT control block in the specified RAM region(s) and returns an
370    /// address if a valid control block location was found.
371    pub fn find_control_block(core: &mut Core, region: &ScanRegion) -> Result<u64, Error> {
372        let ranges = match region.clone() {
373            ScanRegion::Exact(addr) => {
374                tracing::debug!("Scanning at exact address: {:#010x}", addr);
375
376                return Ok(addr);
377            }
378            ScanRegion::Ram => {
379                tracing::debug!("Scanning whole RAM");
380
381                core.memory_regions()
382                    .filter_map(MemoryRegion::as_ram_region)
383                    .filter(|r| !r.is_alias)
384                    .map(|r| r.range.clone())
385                    .collect()
386            }
387            ScanRegion::Ranges(regions) if regions.is_empty() => {
388                // We have no regions to scan so we cannot initialize RTT.
389                tracing::debug!(
390                    "ELF file has no RTT block symbol, and this target does not support automatic scanning"
391                );
392                return Err(Error::NoControlBlockLocation);
393            }
394            ScanRegion::Ranges(regions) => {
395                tracing::debug!("Scanning regions: {:#010x?}", region);
396                regions
397            }
398        };
399
400        let mut instances = ranges
401            .into_iter()
402            .filter_map(|range| {
403                let range_len = range.end.checked_sub(range.start)?;
404                let Ok(range_len) = usize::try_from(range_len) else {
405                    // FIXME: This is not ideal because it means that we
406                    // won't consider a >4GiB region if probe-rs is running
407                    // on a 32-bit host, but it would be relatively unusual
408                    // to use a 32-bit host to debug a 64-bit target.
409                    tracing::warn!("Region too long ({} bytes), ignoring", range_len);
410                    return None;
411                };
412
413                let mut mem = vec![0; range_len];
414                core.read(range.start, &mut mem).ok()?;
415
416                let offset = mem
417                    .windows(Self::RTT_ID.len())
418                    .position(|w| w == Self::RTT_ID)?;
419
420                let target_ptr = range.start + offset as u64;
421
422                Some(target_ptr)
423            })
424            .collect::<Vec<_>>();
425
426        match instances.len() {
427            0 => Err(Error::ControlBlockNotFound),
428            1 => Ok(instances.remove(0)),
429            _ => Err(Error::MultipleControlBlocksFound(instances)),
430        }
431    }
432
433    /// Clear a potentially stale RTT control block at the given scan region.
434    ///
435    /// For `Exact` addresses, zeros the header directly. For `Ranges` / `Ram`,
436    /// scans for the magic bytes and clears if found. Does nothing if no control
437    /// block is found.
438    ///
439    /// This should be called while the core is halted, before a reset, to prevent
440    /// attaching to stale data from a previous session.
441    pub fn clear_control_block(core: &mut Core, region: &ScanRegion) -> Result<(), Error> {
442        match Self::find_control_block(core, region) {
443            Ok(address) => {
444                tracing::debug!("Clearing RTT control block at {:#010x}", address);
445                let clear_size = if matches!(region, ScanRegion::Exact(_)) {
446                    // Full header: 16 bytes magic + channel count fields
447                    if core.is_64_bit() {
448                        16 + 2 * 8
449                    } else {
450                        16 + 2 * 4
451                    }
452                } else {
453                    // Just the magic identifier (we haven't validated the full block)
454                    Self::RTT_ID.len()
455                };
456                let zeros = vec![0u8; clear_size];
457                core.write_8(address, &zeros)?;
458                Ok(())
459            }
460            Err(Error::ControlBlockNotFound | Error::NoControlBlockLocation) => {
461                tracing::debug!("No RTT control block found to clear");
462                Ok(())
463            }
464            Err(e) => Err(e),
465        }
466    }
467
468    /// Returns the memory address of the control block in target memory.
469    pub fn ptr(&self) -> u64 {
470        self.ptr
471    }
472
473    /// Returns a reference to the detected up channels.
474    pub fn up_channels(&mut self) -> &mut [UpChannel] {
475        &mut self.up_channels
476    }
477
478    /// Returns a reference to the detected down channels.
479    pub fn down_channels(&mut self) -> &mut [DownChannel] {
480        &mut self.down_channels
481    }
482
483    /// Returns a particular up channel.
484    pub fn up_channel(&mut self, channel: usize) -> Option<&mut UpChannel> {
485        self.up_channels.get_mut(channel)
486    }
487
488    /// Returns a particular down channel.
489    pub fn down_channel(&mut self, channel: usize) -> Option<&mut DownChannel> {
490        self.down_channels.get_mut(channel)
491    }
492
493    /// Returns the size of the RTT control block.
494    pub fn control_block_size() -> usize {
495        RttControlBlockHeader::minimal_header_size()
496    }
497}
498
499/// Used to specify which memory regions to scan for the RTT control block.
500#[derive(Clone, Debug, Default)]
501pub enum ScanRegion {
502    /// Scans all RAM regions known to probe-rs. This is the default and should always work, however
503    /// if your device has a lot of RAM, scanning all of it is slow.
504    #[default]
505    Ram,
506
507    /// Limit scanning to the memory addresses covered by all of the given ranges. It is up to the
508    /// user to ensure that reading from this range will not read from undefined memory.
509    Ranges(Vec<Range<u64>>),
510
511    /// Tries to find the control block starting at this exact address. It is up to the user to
512    /// ensure that reading the necessary bytes after the pointer will no read from undefined
513    /// memory.
514    Exact(u64),
515}
516
517impl ScanRegion {
518    /// Creates a new `ScanRegion` that scans the given memory range.
519    ///
520    /// The memory range should be in a single memory block of the target.
521    pub fn range(range: Range<u64>) -> Self {
522        Self::Ranges(vec![range])
523    }
524}
525
526/// Error type for RTT operations.
527#[derive(thiserror::Error, Debug, docsplay::Display)]
528pub enum Error {
529    /// There is no control block location given. This usually means RTT is not present in the
530    /// firmware.
531    NoControlBlockLocation,
532
533    /// RTT control block not found in target memory.
534    /// - Make sure RTT is initialized on the target, AND that there are NO target breakpoints before RTT initialization.
535    /// - For VSCode and probe-rs-debugger users, using `halt_after_reset:true` in your `launch.json` file will prevent RTT
536    ///   initialization from happening on time.
537    /// - Depending on the target, sleep modes can interfere with RTT.
538    ControlBlockNotFound,
539
540    /// Multiple control blocks found in target memory: {display_list(_0)}.
541    MultipleControlBlocksFound(Vec<u64>),
542
543    /// The control block has been corrupted: {0}
544    ControlBlockCorrupted(String),
545
546    /// Attempted an RTT operation against a Core number that is different from the Core number against which RTT was initialized. Expected {0}, found {1}
547    IncorrectCoreSpecified(usize, usize),
548
549    /// Error communicating with the probe.
550    Probe(#[from] crate::Error),
551
552    /// Unexpected error while reading {0} from target memory. Please report this as a bug.
553    MemoryRead(String),
554
555    /// Some uncategorized error occurred.
556    Other(#[from] anyhow::Error),
557
558    /// The read pointer changed unexpectedly.
559    ReadPointerChanged,
560
561    /// Channel {0} does not exist.
562    MissingChannel(usize),
563}
564
565fn display_list(list: &[u64]) -> String {
566    list.iter()
567        .map(|ptr| format!("{ptr:#010x}"))
568        .collect::<Vec<_>>()
569        .join(", ")
570}
571
572fn try_attach_to_rtt_inner(
573    mut try_attach_once: impl FnMut() -> Result<Rtt, Error>,
574    timeout: Duration,
575) -> Result<Rtt, Error> {
576    let t = Instant::now();
577    let mut attempt = 1;
578    loop {
579        tracing::debug!("Initializing RTT (attempt {attempt})...");
580
581        match try_attach_once() {
582            err @ Err(Error::NoControlBlockLocation) => return err,
583            Err(_) if t.elapsed() < timeout => {
584                attempt += 1;
585                tracing::debug!("Failed to initialize RTT. Retrying until timeout.");
586                thread::sleep(Duration::from_millis(50));
587            }
588            other => return other,
589        }
590    }
591}
592
593/// Try to attach to RTT, with the given timeout.
594pub fn try_attach_to_rtt(
595    core: &mut Core<'_>,
596    timeout: Duration,
597    rtt_region: &ScanRegion,
598) -> Result<Rtt, Error> {
599    try_attach_to_rtt_inner(|| Rtt::attach_region(core, rtt_region), timeout)
600}
601
602/// Try to attach to RTT, with the given timeout.
603pub fn try_attach_to_rtt_shared(
604    session: &parking_lot::FairMutex<Session>,
605    core_id: usize,
606    timeout: Duration,
607    rtt_region: &ScanRegion,
608) -> Result<Rtt, Error> {
609    try_attach_to_rtt_inner(
610        || {
611            let mut session_handle = session.lock();
612            let mut core = session_handle.core(core_id)?;
613            Rtt::attach_region(&mut core, rtt_region)
614        },
615        timeout,
616    )
617}
618
619#[cfg(test)]
620mod test {
621    use super::*;
622
623    #[test]
624    fn test_how_control_block_list_looks() {
625        let error = Error::MultipleControlBlocksFound(vec![0x2000, 0x3000]);
626        assert_eq!(
627            error.to_string(),
628            "Multiple control blocks found in target memory: 0x00002000, 0x00003000."
629        );
630    }
631}