mod channel;
pub use channel::*;
use crate::Session;
use crate::{config::MemoryRegion, Core, MemoryInterface};
use std::ops::Range;
use std::thread;
use std::time::Instant;
use std::{borrow::Cow, time::Duration};
use zerocopy::FromBytes;
#[derive(Debug)]
pub struct Rtt {
ptr: u64,
pub up_channels: Vec<UpChannel>,
pub down_channels: Vec<DownChannel>,
}
#[repr(C)]
#[derive(FromBytes)]
struct RttControlBlockHeaderInner<T> {
id: [u8; 16],
max_up_channels: T,
max_down_channels: T,
}
impl From<RttControlBlockHeaderInner<u32>> for RttControlBlockHeaderInner<u64> {
fn from(value: RttControlBlockHeaderInner<u32>) -> Self {
Self {
id: value.id,
max_up_channels: u64::from(value.max_up_channels),
max_down_channels: u64::from(value.max_down_channels),
}
}
}
enum RttControlBlockHeader {
Header32(RttControlBlockHeaderInner<u32>),
Header64(RttControlBlockHeaderInner<u64>),
}
impl RttControlBlockHeader {
pub fn try_from_header(is_64_bit: bool, mem: &[u8]) -> Option<Self> {
if is_64_bit {
RttControlBlockHeaderInner::<u64>::read_from_prefix(mem)
.map(|(header, _)| Self::Header64(header))
.ok()
} else {
RttControlBlockHeaderInner::<u32>::read_from_prefix(mem)
.map(|(header, _)| Self::Header32(header))
.ok()
}
}
pub fn minimal_header_size(is_64_bit: bool) -> usize {
if is_64_bit {
std::mem::size_of::<RttControlBlockHeaderInner<u64>>()
} else {
std::mem::size_of::<RttControlBlockHeaderInner<u32>>()
}
}
pub fn header_size(&self) -> usize {
Self::minimal_header_size(matches!(self, Self::Header64(_)))
}
pub fn id(&self) -> [u8; 16] {
match self {
RttControlBlockHeader::Header32(x) => x.id,
RttControlBlockHeader::Header64(x) => x.id,
}
}
pub fn max_up_channels(&self) -> usize {
match self {
RttControlBlockHeader::Header32(x) => x.max_up_channels as usize,
RttControlBlockHeader::Header64(x) => x.max_up_channels as usize,
}
}
pub fn max_down_channels(&self) -> usize {
match self {
RttControlBlockHeader::Header32(x) => x.max_down_channels as usize,
RttControlBlockHeader::Header64(x) => x.max_down_channels as usize,
}
}
pub fn channel_buffer_size(&self) -> usize {
match self {
RttControlBlockHeader::Header32(_x) => RttChannelBufferInner::<u32>::size(),
RttControlBlockHeader::Header64(_x) => RttChannelBufferInner::<u64>::size(),
}
}
pub fn total_rtt_buffer_size(&self) -> usize {
let total_number_of_channels = self.max_up_channels() + self.max_down_channels();
let channel_size = self.channel_buffer_size();
self.header_size() + channel_size * total_number_of_channels
}
pub fn parse_channel_buffers(&self, mem: &[u8]) -> Result<Vec<RttChannelBuffer>, Error> {
let buffers = match self {
RttControlBlockHeader::Header32(_) => {
<[RttChannelBufferInner<u32>]>::ref_from_bytes(mem)
.map_err(|_| Error::ControlBlockNotFound)?
.iter()
.cloned()
.map(RttChannelBuffer::from)
.collect::<Vec<RttChannelBuffer>>()
}
RttControlBlockHeader::Header64(_) => {
<[RttChannelBufferInner<u64>]>::ref_from_bytes(mem)
.map_err(|_| Error::ControlBlockNotFound)?
.iter()
.cloned()
.map(RttChannelBuffer::from)
.collect::<Vec<RttChannelBuffer>>()
}
};
Ok(buffers)
}
}
impl Rtt {
const RTT_ID: [u8; 16] = *b"SEGGER RTT\0\0\0\0\0\0";
fn from(
core: &mut Core,
ptr: u64,
mem_in: Option<&[u8]>,
) -> Result<Option<Rtt>, Error> {
let is_64_bit = core.is_64_bit();
let mut mem = match mem_in {
Some(mem) => Cow::Borrowed(mem),
None => {
let new_length = RttControlBlockHeader::minimal_header_size(is_64_bit);
let mut mem = vec![0; new_length];
core.read(ptr, &mut mem)?;
Cow::Owned(mem)
}
};
let rtt_header = RttControlBlockHeader::try_from_header(is_64_bit, &mem)
.ok_or(Error::ControlBlockNotFound)?;
let rtt_id = rtt_header.id();
if rtt_id != Self::RTT_ID {
tracing::trace!(
"Expected control block to start with RTT ID: {:?}\n. Got instead: {:?}",
String::from_utf8_lossy(&Self::RTT_ID),
String::from_utf8_lossy(&rtt_id)
);
return Err(Error::ControlBlockNotFound);
}
let max_up_channels = rtt_header.max_up_channels();
let max_down_channels = rtt_header.max_down_channels();
if max_up_channels > 255 || max_down_channels > 255 {
return Err(Error::ControlBlockCorrupted(format!(
"Unexpected array sizes at {ptr:#010x}: max_up_channels={max_up_channels} max_down_channels={max_down_channels}"
)));
}
let cb_len = rtt_header.total_rtt_buffer_size();
if let Cow::Owned(mem) = &mut mem {
mem.resize(cb_len, 0);
core.read(
ptr + rtt_header.header_size() as u64,
&mut mem[rtt_header.header_size()..cb_len],
)?;
}
if mem.len() < cb_len {
tracing::debug!("Control block doesn't fit in scanned memory region.");
return Ok(None);
}
let mut up_channels = Vec::new();
let mut down_channels = Vec::new();
let channel_buffer_size = rtt_header.channel_buffer_size();
let up_channels_start = rtt_header.header_size();
let up_channels_len = max_up_channels * channel_buffer_size;
let up_channels_raw_buffer = &mem[up_channels_start..][..up_channels_len];
let up_channels_buffer = rtt_header.parse_channel_buffers(up_channels_raw_buffer)?;
let down_channels_start = up_channels_start + up_channels_len;
let down_channels_len = max_down_channels * channel_buffer_size;
let down_channels_raw_buffer = &mem[down_channels_start..][..down_channels_len];
let down_channels_buffer = rtt_header.parse_channel_buffers(down_channels_raw_buffer)?;
let mut offset = up_channels_start as u64;
for (i, b) in up_channels_buffer.into_iter().enumerate() {
let buffer_size = b.size() as u64;
if let Some(chan) = Channel::from(core, i, ptr + offset, b)? {
up_channels.push(UpChannel(chan));
} else {
tracing::warn!("Buffer for up channel {i} not initialized");
}
offset += buffer_size;
}
for (i, b) in down_channels_buffer.into_iter().enumerate() {
let buffer_size = b.size() as u64;
if let Some(chan) = Channel::from(core, i, ptr + offset, b)? {
down_channels.push(DownChannel(chan));
} else {
tracing::warn!("Buffer for down channel {i} not initialized");
}
offset += buffer_size;
}
Ok(Some(Rtt {
ptr,
up_channels,
down_channels,
}))
}
pub fn attach(core: &mut Core) -> Result<Rtt, Error> {
Self::attach_region(core, &ScanRegion::default())
}
pub fn attach_region(core: &mut Core, region: &ScanRegion) -> Result<Rtt, Error> {
let ranges = match region.clone() {
ScanRegion::Exact(addr) => {
tracing::debug!("Scanning at exact address: {:#010x}", addr);
return Rtt::from(core, addr, None)?.ok_or(Error::ControlBlockNotFound);
}
ScanRegion::Ram => {
tracing::debug!("Scanning whole RAM");
core.memory_regions()
.filter_map(MemoryRegion::as_ram_region)
.map(|r| r.range.clone())
.collect()
}
ScanRegion::Ranges(regions) if regions.is_empty() => {
tracing::debug!("ELF file has no RTT block symbol, and this target does not support automatic scanning");
return Err(Error::NoControlBlockLocation);
}
ScanRegion::Ranges(regions) => {
tracing::debug!("Scanning regions: {:#010x?}", region);
regions
}
};
let mut instances = ranges
.into_iter()
.filter_map(|range| {
let range_len = range.end.checked_sub(range.start)?;
let Ok(range_len) = usize::try_from(range_len) else {
tracing::warn!("Region too long ({} bytes), ignoring", range_len);
return None;
};
let mut mem = vec![0; range_len];
core.read(range.start, &mut mem).ok()?;
let offset = mem
.windows(Self::RTT_ID.len())
.position(|w| w == Self::RTT_ID)?;
let target_ptr = range.start + offset as u64;
Rtt::from(core, target_ptr, Some(&mem[offset..])).transpose()
})
.collect::<Result<Vec<_>, _>>()?;
match instances.len() {
0 => Err(Error::ControlBlockNotFound),
1 => Ok(instances.remove(0)),
_ => Err(Error::MultipleControlBlocksFound(instances)),
}
}
pub fn ptr(&self) -> u64 {
self.ptr
}
pub fn up_channels(&mut self) -> &mut [UpChannel] {
&mut self.up_channels
}
pub fn down_channels(&mut self) -> &mut [DownChannel] {
&mut self.down_channels
}
pub fn up_channel(&mut self, channel: usize) -> Option<&mut UpChannel> {
self.up_channels.get_mut(channel)
}
pub fn down_channel(&mut self, channel: usize) -> Option<&mut DownChannel> {
self.down_channels.get_mut(channel)
}
pub fn control_block_size(core: &Core) -> usize {
let is_64_bit = core.is_64_bit();
RttControlBlockHeader::minimal_header_size(is_64_bit)
}
}
#[derive(Clone, Debug, Default)]
pub enum ScanRegion {
#[default]
Ram,
Ranges(Vec<Range<u64>>),
Exact(u64),
}
impl ScanRegion {
pub fn range(range: Range<u64>) -> Self {
Self::Ranges(vec![range])
}
}
#[derive(thiserror::Error, Debug, docsplay::Display)]
pub enum Error {
NoControlBlockLocation,
ControlBlockNotFound,
MultipleControlBlocksFound(Vec<Rtt>),
ControlBlockCorrupted(String),
IncorrectCoreSpecified(usize, usize),
Probe(#[from] crate::Error),
MemoryRead(String),
Other(#[from] anyhow::Error),
ReadPointerChanged,
MissingChannel(usize),
}
fn display_list(list: &[Rtt]) -> String {
list.iter()
.map(|rtt| format!("{:#010x}", rtt.ptr))
.collect::<Vec<_>>()
.join(", ")
}
fn try_attach_to_rtt_inner(
mut try_attach_once: impl FnMut() -> Result<Rtt, Error>,
timeout: Duration,
) -> Result<Rtt, Error> {
let t = Instant::now();
let mut attempt = 1;
loop {
tracing::debug!("Initializing RTT (attempt {attempt})...");
match try_attach_once() {
err @ Err(Error::NoControlBlockLocation) => return err,
Err(_) if t.elapsed() < timeout => {
attempt += 1;
tracing::debug!("Failed to initialize RTT. Retrying until timeout.");
thread::sleep(Duration::from_millis(50));
}
other => return other,
}
}
}
pub fn try_attach_to_rtt(
core: &mut Core<'_>,
timeout: Duration,
rtt_region: &ScanRegion,
) -> Result<Rtt, Error> {
try_attach_to_rtt_inner(|| Rtt::attach_region(core, rtt_region), timeout)
}
pub fn try_attach_to_rtt_shared(
session: &parking_lot::FairMutex<Session>,
core_id: usize,
timeout: Duration,
rtt_region: &ScanRegion,
) -> Result<Rtt, Error> {
try_attach_to_rtt_inner(
|| {
let mut session_handle = session.lock();
let mut core = session_handle.core(core_id)?;
Rtt::attach_region(&mut core, rtt_region)
},
timeout,
)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_how_control_block_list_looks() {
fn rtt(ptr: u32) -> Rtt {
Rtt {
ptr: ptr.into(),
up_channels: Vec::new(),
down_channels: Vec::new(),
}
}
let error = Error::MultipleControlBlocksFound(vec![rtt(0x2000), rtt(0x3000)]);
assert_eq!(
error.to_string(),
"Multiple control blocks found in target memory: 0x00002000, 0x00003000."
);
}
}