probe_rs_cli_util/
rtt.rs

1use crate::*;
2use anyhow::{anyhow, Result};
3use defmt_decoder::DecodeError;
4use num_traits::Zero;
5use probe_rs::config::MemoryRegion;
6pub use probe_rs::rtt::ChannelMode;
7use probe_rs::rtt::{DownChannel, Rtt, ScanRegion, UpChannel};
8use probe_rs::Core;
9use serde::Deserialize;
10use std::collections::HashMap;
11use std::fs::File;
12use std::{
13    fmt,
14    fmt::Write,
15    fs,
16    io::{Read, Seek},
17    str::FromStr,
18};
19use time::{OffsetDateTime, UtcOffset};
20
21pub fn attach_to_rtt(
22    core: &mut Core,
23    memory_map: &[MemoryRegion],
24    elf_file: &Path,
25    rtt_config: &RttConfig,
26    timestamp_offset: UtcOffset,
27) -> Result<crate::rtt::RttActiveTarget, anyhow::Error> {
28    log::info!("Initializing RTT");
29    let rtt_header_address = if let Ok(mut file) = File::open(elf_file) {
30        if let Some(address) = RttActiveTarget::get_rtt_symbol(&mut file) {
31            ScanRegion::Exact(address as u32)
32        } else {
33            ScanRegion::Ram
34        }
35    } else {
36        ScanRegion::Ram
37    };
38
39    match Rtt::attach_region(core, memory_map, &rtt_header_address) {
40        Ok(rtt) => {
41            log::info!("RTT initialized.");
42            let app = RttActiveTarget::new(rtt, elf_file, rtt_config, timestamp_offset)?;
43            Ok(app)
44        }
45        Err(err) => Err(anyhow!("Error attempting to attach to RTT: {}", err)),
46    }
47}
48
49/// Used by serde to provide defaults for `RttConfig`
50fn default_channel_formats() -> Vec<RttChannelConfig> {
51    vec![]
52}
53
54/// Used by serde to provide defaults for `RttChannelConfig::show_location`
55fn default_include_location() -> bool {
56    // Setting this to true to allow compatibility with behaviour prior to when this option was introduced.
57    true
58}
59
60#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
61pub enum DataFormat {
62    #[default]
63    String,
64    BinaryLE,
65    Defmt,
66}
67impl FromStr for DataFormat {
68    type Err = String;
69
70    fn from_str(s: &str) -> Result<Self, Self::Err> {
71        let src = s.to_ascii_lowercase();
72        match &src.to_ascii_lowercase()[..] {
73            // A forgiving/case-insensitive match
74            "string" => Ok(Self::String),
75            "binaryle" => Ok(Self::BinaryLE),
76            "defmt" => Ok(Self::Defmt),
77            _ => Err(format!("{src} is not a valid format")),
78        }
79    }
80}
81
82/// The initial configuration for RTT (Real Time Transfer). This configuration is complimented with the additional information specified for each of the channels in `RttChannel`.
83#[derive(clap::Parser, Debug, Clone, Deserialize, Default)]
84pub struct RttConfig {
85    #[structopt(skip)]
86    #[serde(default, rename = "rttEnabled")]
87    pub enabled: bool,
88    /// Configure data_format and show_timestamps for select channels
89    #[structopt(skip)]
90    #[serde(default = "default_channel_formats", rename = "rttChannelFormats")]
91    pub channels: Vec<RttChannelConfig>,
92}
93
94/// The User specified configuration for each active RTT Channel. The configuration is passed via a DAP Client configuration (`launch.json`). If no configuration is specified, the defaults will be `Dataformat::String` and `show_timestamps=false`.
95#[derive(clap::Parser, Debug, Clone, serde::Deserialize, Default)]
96#[serde(rename_all = "camelCase")]
97pub struct RttChannelConfig {
98    pub channel_number: Option<usize>,
99    pub channel_name: Option<String>,
100    #[serde(default)]
101    pub data_format: DataFormat,
102    #[structopt(skip)]
103    #[serde(default)]
104    // Control the inclusion of timestamps for DataFormat::String.
105    pub show_timestamps: bool,
106    #[structopt(skip)]
107    #[serde(default = "default_include_location")]
108    // Control the inclusion of source location information for DataFormat::Defmt.
109    pub show_location: bool,
110}
111
112/// This is the primary interface through which RTT channel data is read and written. Every actual RTT channel has a configuration and buffer that is used for this purpose.
113#[derive(Debug)]
114pub struct RttActiveChannel {
115    pub up_channel: Option<UpChannel>,
116    pub down_channel: Option<DownChannel>,
117    pub channel_name: String,
118    pub data_format: DataFormat,
119    /// Data that will be written to the down_channel (host to target)
120    _input_data: String,
121    rtt_buffer: RttBuffer,
122    show_timestamps: bool,
123    show_location: bool,
124
125    /// UTC offset used for creating timestamps
126    ///
127    /// Getting the offset can fail in multi-threaded programs,
128    /// so it needs to be stored.
129    timestamp_offset: UtcOffset,
130}
131
132/// A fully configured RttActiveChannel. The configuration will always try to 'default' based on information read from the RTT control block in the binary. Where insufficient information is available, it will use the supplied configuration, with final hardcoded defaults where no other information was available.
133impl RttActiveChannel {
134    pub fn new(
135        up_channel: Option<UpChannel>,
136        down_channel: Option<DownChannel>,
137        channel_config: Option<RttChannelConfig>,
138        timestamp_offset: UtcOffset,
139    ) -> Self {
140        let full_config = match &channel_config {
141            Some(channel_config) => channel_config.clone(),
142            None => RttChannelConfig {
143                ..Default::default() // Will set intelligent defaults below ...
144            },
145        };
146        let buffer_size: usize = up_channel
147            .as_ref()
148            .map(|up| up.buffer_size())
149            .or_else(|| down_channel.as_ref().map(|down| down.buffer_size()))
150            .unwrap_or(1024); // If no explicit config is requested, assign a default
151        let defmt_enabled: bool = up_channel
152            .as_ref()
153            .map(|up| up.name() == Some("defmt"))
154            .or_else(|| {
155                down_channel
156                    .as_ref()
157                    .map(|down| down.name() == Some("defmt"))
158            })
159            .unwrap_or(false); // If no explicit config is requested, assign a default
160        let (data_format, show_location) = if defmt_enabled {
161            let show_location = if let Some(channel_config) = channel_config {
162                channel_config.show_location
163            } else {
164                true
165            };
166            (DataFormat::Defmt, show_location)
167        } else {
168            (full_config.data_format, false)
169        };
170        let name = up_channel
171            .as_ref()
172            .and_then(|up| up.name().map(Into::into))
173            .or_else(|| {
174                down_channel
175                    .as_ref()
176                    .and_then(|down| down.name().map(Into::into))
177            })
178            .or_else(|| full_config.clone().channel_name)
179            .unwrap_or_else(|| {
180                format!(
181                    "Unnamed {:?} RTT channel - {}",
182                    data_format,
183                    full_config.channel_number.unwrap_or(0)
184                )
185            });
186        Self {
187            up_channel,
188            down_channel,
189            channel_name: name,
190            data_format,
191            _input_data: String::new(),
192            rtt_buffer: RttBuffer::new(buffer_size),
193            show_timestamps: full_config.show_timestamps,
194            show_location,
195            timestamp_offset,
196        }
197    }
198
199    /// Returns the number of the `UpChannel`.
200    pub fn number(&self) -> Option<usize> {
201        self.up_channel.as_ref().map(|uc| uc.number())
202    }
203
204    /// Polls the RTT target for new data on the channel represented by `self`.
205    /// Processes all the new data into the channel internal buffer and returns the number of bytes that was read.
206    pub fn poll_rtt(&mut self, core: &mut Core) -> Option<usize> {
207        if let Some(channel) = self.up_channel.as_mut() {
208            // Retry loop, in case the probe is temporarily unavailable, e.g. user pressed the `reset` button.
209            for _loop_count in 0..10 {
210                match channel.read(core, self.rtt_buffer.0.as_mut()) {
211                    Ok(count) => {
212                        if count.is_zero() {
213                            return None;
214                        } else {
215                            return Some(count);
216                        }
217                    }
218                    Err(err) => {
219                        if matches!(err, probe_rs::rtt::Error::Probe(_)) {
220                            std::thread::sleep(std::time::Duration::from_millis(50));
221                        } else {
222                            log::error!("\nError reading from RTT: {}", err);
223                            return None;
224                        }
225                    }
226                }
227            }
228        }
229        None
230    }
231
232    /// Retrieves available data from the channel and if available, returns `Some(channel_number:String, formatted_data:String)`.
233    /// If no data is available, or we encounter a recoverable error, it returns `None` value fore `formatted_data`.
234    /// Non-recoverable errors are propagated to the caller.
235    pub fn get_rtt_data(
236        &mut self,
237        core: &mut Core,
238        defmt_state: Option<&(defmt_decoder::Table, Option<defmt_decoder::Locations>)>,
239    ) -> Result<Option<(String, String)>, anyhow::Error> {
240        self
241            .poll_rtt(core)
242            .map(|bytes_read| {
243                Ok((
244                    self.number().unwrap_or(0).to_string(), // If the Channel doesn't have a number, then send the output to channel 0
245                    {
246                        let mut formatted_data = String::new();
247                        match self.data_format {
248                            DataFormat::String => {
249                                let incoming = String::from_utf8_lossy(&self.rtt_buffer.0[..bytes_read]).to_string();
250                                for (_i, line) in incoming.split_terminator('\n').enumerate() {
251                                    if self.show_timestamps {
252                                        write!(formatted_data, "{} :", OffsetDateTime::now_utc().to_offset(self.timestamp_offset))
253                                            .map_or_else(|err| log::error!("Failed to format RTT data - {:?}", err), |r|r);
254                                    }
255                                    writeln!(formatted_data, "{line}").map_or_else(|err| log::error!("Failed to format RTT data - {:?}", err), |r|r);
256                                }
257                            }
258                            DataFormat::BinaryLE => {
259                                for element in &self.rtt_buffer.0[..bytes_read] {
260                                    // Width of 4 allows 0xFF to be printed.
261                                    write!(formatted_data, "{element:#04x}").map_or_else(|err| log::error!("Failed to format RTT data - {:?}", err), |r|r);
262                                }
263                            }
264                            DataFormat::Defmt => {
265                                match defmt_state {
266                                    Some((table, locs)) => {
267                                        let mut stream_decoder = table.new_stream_decoder();
268                                        stream_decoder.received(&self.rtt_buffer.0[..bytes_read]);
269                                        loop {
270                                            match stream_decoder.decode() {
271                                                Ok(frame) => {
272                                                    let loc = locs.as_ref().and_then(|locs| locs.get(&frame.index()) );
273                                                    writeln!(formatted_data, "{}", frame.display(false)).map_or_else(|err| log::error!("Failed to format RTT data - {:?}", err), |r|r);
274                                                    if self.show_location {
275                                                        if let Some(loc) = loc {
276                                                            let relpath = if let Ok(relpath) =
277                                                                loc.file.strip_prefix(&std::env::current_dir().unwrap())
278                                                            {
279                                                                relpath
280                                                            } else {
281                                                                // not relative; use full path
282                                                                &loc.file
283                                                            };
284                                                            writeln!(formatted_data,
285                                                                "└─ {}:{}",
286                                                                relpath.display(),
287                                                                loc.line
288                                                            ).map_or_else(|err| log::error!("Failed to format RTT data - {:?}", err), |r|r);
289                                                        } else {
290                                                            writeln!(formatted_data, "└─ <invalid location: defmt frame-index: {}>", frame.index()).map_or_else(|err| log::error!("Failed to format RTT data - {:?}", err), |r|r);
291                                                        }
292                                                    }
293                                                    continue;
294                                                },
295                                                Err(DecodeError::UnexpectedEof) => break,
296                                                Err(DecodeError::Malformed) => match table.encoding().can_recover() {
297                                                    // If recovery is impossible, break out of here and propagate the error.
298                                                    false => {
299                                                        return Err(anyhow!("Unrecoverable error while decoding Defmt data and some data may have been lost: {:?}", DecodeError::Malformed));
300                                                    },
301                                                    // If recovery is possible, skip the current frame and continue with new data.
302                                                    true => continue,
303                                                },
304
305                                            }
306                                        }
307                                    }
308                                    None => {
309                                        write!(formatted_data, "Running rtt in defmt mode but table or locations could not be loaded.")
310                                            .map_or_else(|err| log::error!("Failed to format RTT data - {:?}", err), |r|r);
311                                    }
312                                }
313                            }
314                        };
315                        formatted_data
316                    }
317                ))
318            }).transpose()
319    }
320
321    pub fn _push_rtt(&mut self, core: &mut Core) {
322        if let Some(down_channel) = self.down_channel.as_mut() {
323            self._input_data += "\n";
324            down_channel
325                .write(core, self._input_data.as_bytes())
326                .unwrap();
327            self._input_data.clear();
328        }
329    }
330}
331
332/// Once an active connection with the Target RTT control block has been established, we configure each of the active channels, and hold essential state information for successfull communication.
333#[derive(Debug)]
334pub struct RttActiveTarget {
335    pub active_channels: Vec<RttActiveChannel>,
336    pub defmt_state: Option<(defmt_decoder::Table, Option<defmt_decoder::Locations>)>,
337}
338
339impl RttActiveTarget {
340    /// RttActiveTarget collects references to all the `RttActiveChannel`s, for latter polling/pushing of data.
341    pub fn new(
342        mut rtt: probe_rs::rtt::Rtt,
343        elf_file: &Path,
344        rtt_config: &RttConfig,
345        timestamp_offset: UtcOffset,
346    ) -> Result<Self> {
347        let mut active_channels = Vec::new();
348        // For each channel configured in the RTT Control Block (`Rtt`), check if there are additional user configuration in a `RttChannelConfig`. If not, apply defaults.
349        let up_channels = rtt.up_channels().drain();
350        let down_channels = rtt.down_channels().drain();
351        for channel in up_channels {
352            let number = channel.number();
353            let channel_config = rtt_config
354                .channels
355                .clone()
356                .into_iter()
357                .find(|channel| channel.channel_number == Some(number));
358            active_channels.push(RttActiveChannel::new(
359                Some(channel),
360                None,
361                channel_config,
362                timestamp_offset,
363            ));
364        }
365
366        for channel in down_channels {
367            let number = channel.number();
368            let channel_config = rtt_config
369                .channels
370                .clone()
371                .into_iter()
372                .find(|channel| channel.channel_number == Some(number));
373            active_channels.push(RttActiveChannel::new(
374                None,
375                Some(channel),
376                channel_config,
377                timestamp_offset,
378            ));
379        }
380
381        // It doesn't make sense to pretend RTT is active, if there are no active channels
382        if active_channels.is_empty() {
383            return Err(anyhow!(
384                "RTT Initialized correctly, but there were no active channels configured"
385            ));
386        }
387
388        let defmt_enabled = active_channels
389            .iter()
390            .any(|elem| elem.data_format == DataFormat::Defmt);
391        let defmt_state = if defmt_enabled {
392            let elf = fs::read(elf_file).map_err(|err| {
393                anyhow!(
394                    "Error reading program binary while initalizing RTT: {}",
395                    err
396                )
397            })?;
398            if let Some(table) = defmt_decoder::Table::parse(&elf)? {
399                let locs = {
400                    let locs = table.get_locations(&elf)?;
401
402                    if !table.is_empty() && locs.is_empty() {
403                        log::warn!("Insufficient DWARF info; compile your program with `debug = 2` to enable location info.");
404                        None
405                    } else if table.indices().all(|idx| locs.contains_key(&(idx as u64))) {
406                        Some(locs)
407                    } else {
408                        log::warn!(
409                            "Location info is incomplete; it will be omitted from the output."
410                        );
411                        None
412                    }
413                };
414                Some((table, locs))
415            } else {
416                log::warn!("No `Table` definition in DWARF info; compile your program with `debug = 2` to enable location info.");
417                None
418            }
419        } else {
420            None
421        };
422
423        Ok(Self {
424            active_channels,
425            defmt_state,
426        })
427    }
428
429    pub fn get_rtt_symbol<T: Read + Seek>(file: &mut T) -> Option<u64> {
430        let mut buffer = Vec::new();
431        if file.read_to_end(&mut buffer).is_ok() {
432            if let Ok(binary) = goblin::elf::Elf::parse(buffer.as_slice()) {
433                for sym in &binary.syms {
434                    if let Some(name) = binary.strtab.get_at(sym.st_name) {
435                        if name == "_SEGGER_RTT" {
436                            return Some(sym.st_value);
437                        }
438                    }
439                }
440            }
441        }
442
443        log::warn!("No RTT header info was present in the ELF file. Does your firmware run RTT?");
444        None
445    }
446
447    /// Polls the RTT target on all channels and returns available data.
448    /// Errors on any channel will be ignored and the data (even if incomplete) from the other channels will be returned.
449    #[deprecated(
450        since = "0.14.0",
451        note = "This function is deprecated and will be removed in a future version. Please use `poll_rtt_fallible` instead."
452    )]
453    pub fn poll_rtt(&mut self, core: &mut Core) -> HashMap<String, String> {
454        let defmt_state = self.defmt_state.as_ref();
455        self.active_channels
456            .iter_mut()
457            .filter_map(|active_channel| {
458                active_channel
459                    .get_rtt_data(core, defmt_state)
460                    .unwrap_or_default()
461            })
462            .collect::<HashMap<_, _>>()
463    }
464
465    /// Polls the RTT target on all channels and returns available data.
466    /// An error on any channel will return an error instead of incomplete data.
467    pub fn poll_rtt_fallible(
468        &mut self,
469        core: &mut Core,
470    ) -> Result<HashMap<String, String>, anyhow::Error> {
471        let defmt_state = self.defmt_state.as_ref();
472        let mut data = HashMap::new();
473        for channel in self.active_channels.iter_mut() {
474            if let Some((channel, formatted_data)) = channel.get_rtt_data(core, defmt_state)? {
475                data.insert(channel, formatted_data);
476            }
477        }
478        Ok(data)
479    }
480
481    // pub fn push_rtt(&mut self) {
482    //     self.tabs[self.current_tab].push_rtt();
483    // }
484}
485
486struct RttBuffer(Vec<u8>);
487impl RttBuffer {
488    /// Initialize the buffer and ensure it has enough capacity to match the size of the RTT channel on the target at the time of instantiation. Doing this now prevents later performance impact if the buffer capacity has to be grown dynamically.
489    pub fn new(mut buffer_size: usize) -> RttBuffer {
490        let mut rtt_buffer = vec![0u8; 1];
491        while buffer_size > 0 {
492            buffer_size -= 1;
493            rtt_buffer.push(0u8);
494        }
495        RttBuffer(rtt_buffer)
496    }
497}
498impl fmt::Debug for RttBuffer {
499    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
500        self.0.fmt(f)
501    }
502}