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