Skip to content

Commit

Permalink
Add support for setup-on-breakpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
jonlamb-gh committed Apr 15, 2024
1 parent 5e8d9f2 commit c0d44c9
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 18 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,18 @@ reflector configuration file, e.g. `[plugins.ingest.collectors.defmt-rtt.metadat
See the [RTT timing section](https://docs.rs/probe-rs-rtt/0.14.2/probe_rs_rtt/struct.Rtt.html#examples-of-how-timing-between-host-and-target-effects-the-results) for more information.
- `control-block-address` — Use the provided RTT control block address instead of scanning the target memory for it.
- `up-channel` — The RTT up (target to host) channel number to poll on. The default value is 0.
- `setup-on-breakpoint` — Set a breakpoint on the address of the given symbol used to signal
when to enable RTT BlockIfFull channel mode and start reading.
Can be an absolute address or symbol name.
- `thumb` — Assume thumb mode when resolving symbols from the ELF file for breakpoint addresses.
- `probe-selector` — Select a specific probe instead of opening the first available one.
- `chip` — The target chip to attach to (e.g. `STM32F407VE`).
- `protocol` — Protocol used to connect to chip. Possible options: [`swd`, `jtag`]. The default value is `swd`.
- `speed` — The protocol speed in kHz. The default value is 4000.
- `core` — The selected core to target. The default value is 0.
- `reset` — Reset the target on startup.
- `attach-under-reset` — Attach to the chip under hard-reset.
This asserts the reset pin via the probe, plays the protocol init routines and deasserts the pin.
- `chip-description-path` — Provides custom target descriptions based on CMSIS Pack files.
See the [probe-rs target extraction](https://probe.rs/docs/knowledge-base/cmsis-packs/#target-extraction) section for
more information.
Expand Down
149 changes: 131 additions & 18 deletions src/bin/rtt_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@ use modality_defmt_plugin::{
use probe_rs::{
config::MemoryRegion,
probe::{list::Lister, DebugProbeSelector, WireProtocol},
rtt::{Rtt, ScanRegion, UpChannel},
Core, Permissions, Session, VectorCatchCondition,
rtt::{ChannelMode, Rtt, ScanRegion, UpChannel},
Core, CoreStatus, HaltReason, Permissions, RegisterValue, Session, VectorCatchCondition,
};
use std::{
fs, io,
path::PathBuf,
sync::{Arc, Mutex},
time::{Duration, Instant},
};
use thiserror::Error;
use tracing::{debug, error};
use tracing::{debug, error, warn};

/// Collect defmt data from an on-device RTT buffer
#[derive(Parser, Debug, Clone)]
Expand Down Expand Up @@ -52,6 +51,26 @@ struct Opts {
#[clap(long, name = "up-channel", help_heading = "COLLECTOR CONFIGURATION")]
pub up_channel: Option<usize>,

/// Set a breakpoint on the address of the given symbol used to signal
/// when to enable RTT BlockIfFull channel mode and start reading.
///
/// Can be an absolute address or symbol name.
#[arg(
long,
name = "setup-on-breakpoint",
help_heading = "COLLECTOR CONFIGURATION"
)]
pub setup_on_breakpoint: Option<String>,

/// Assume thumb mode when resolving symbols from the ELF file
/// for breakpoint addresses.
#[arg(
long,
requires = "setup-on-breakpoint",
help_heading = "COLLECTOR CONFIGURATION"
)]
pub thumb: bool,

/// Select a specific probe instead of opening the first available one.
///
/// Use '--probe VID:PID' or '--probe VID:PID:Serial' if you have more than one probe with the same VID:PID.
Expand Down Expand Up @@ -104,6 +123,7 @@ struct Opts {

/// The ELF file containing the defmt table and location information.
#[clap(
long,
name = "elf-file",
verbatim_doc_comment,
help_heading = "DEFMT CONFIGURATION"
Expand Down Expand Up @@ -169,6 +189,12 @@ async fn do_main() -> Result<(), Box<dyn std::error::Error>> {
if let Some(up_channel) = opts.up_channel {
defmt_cfg.plugin.rtt_collector.up_channel = up_channel;
}
if let Some(setup_on_breakpoint) = &opts.setup_on_breakpoint {
defmt_cfg.plugin.rtt_collector.setup_on_breakpoint = Some(setup_on_breakpoint.clone());
}
if opts.thumb {
defmt_cfg.plugin.rtt_collector.thumb = true;
}
if let Some(ps) = &opts.probe_selector {
defmt_cfg.plugin.rtt_collector.probe_selector = Some(ps.clone().into());
}
Expand Down Expand Up @@ -268,6 +294,41 @@ async fn do_main() -> Result<(), Box<dyn std::error::Error>> {
core.disable_vector_catch(VectorCatchCondition::All)?;
core.clear_all_hw_breakpoints()?;

if let Some(bp_sym_or_addr) = &defmt_cfg.plugin.rtt_collector.setup_on_breakpoint {
let num_bp = core.available_breakpoint_units()?;

let bp_addr = if let Some(bp_addr) = bp_sym_or_addr
.parse::<u64>()
.ok()
.or(u64::from_str_radix(bp_sym_or_addr.trim_start_matches("0x"), 16).ok())
{
bp_addr
} else {
let mut file = fs::File::open(
defmt_cfg
.plugin
.elf_file
.as_ref()
.ok_or(modality_defmt_plugin::Error::MissingElfFile)?,
)?;
let bp_addr = get_symbol(&mut file, bp_sym_or_addr)
.ok_or_else(|| Error::ElfSymbol(bp_sym_or_addr.to_owned()))?;
if opts.thumb {
bp_addr & !1
} else {
bp_addr
}
};

debug!(
available_breakpoints = num_bp,
symbol_or_addr = bp_sym_or_addr,
addr = format_args!("0x{:X}", bp_addr),
"Setting breakpoint to do RTT channel setup"
);
core.set_hw_breakpoint(bp_addr)?;
}

let mut rtt = match defmt_cfg.plugin.rtt_collector.attach_timeout {
Some(to) if !to.0.is_zero() => {
attach_retry_loop(&mut core, &memory_map, &rtt_scan_region, to.0)?
Expand All @@ -278,11 +339,6 @@ async fn do_main() -> Result<(), Box<dyn std::error::Error>> {
}
};

if defmt_cfg.plugin.rtt_collector.reset {
debug!("Run core");
core.run()?;
}

let up_channel = rtt
.up_channels()
.take(defmt_cfg.plugin.rtt_collector.up_channel)
Expand All @@ -291,19 +347,61 @@ async fn do_main() -> Result<(), Box<dyn std::error::Error>> {
let up_channel_name = up_channel.name().unwrap_or("NA");
debug!(channel = up_channel.number(), name = up_channel_name, mode = ?up_channel_mode, buffer_size = up_channel.buffer_size(), "Opened up channel");

// TODO - add blocking/non-blocking mode control
if defmt_cfg.plugin.rtt_collector.reset || defmt_cfg.plugin.rtt_collector.attach_under_reset {
let sp_reg = core.stack_pointer();
let sp: RegisterValue = core.read_core_reg(sp_reg.id())?;
let pc_reg = core.program_counter();
let pc: RegisterValue = core.read_core_reg(pc_reg.id())?;
debug!(pc = %pc, sp = %sp, "Run core");
core.run()?;
}

if defmt_cfg.plugin.rtt_collector.setup_on_breakpoint.is_some() {
debug!("Waiting for breakpoint");
'bp_loop: loop {
if intr.is_set() {
break;
}

match core.status()? {
CoreStatus::Running => (),
CoreStatus::Halted(halt_reason) => match halt_reason {
HaltReason::Breakpoint(_) => break 'bp_loop,
_ => {
warn!(reason = ?halt_reason, "Unexpected halt reason");
break 'bp_loop;
}
},
state => {
warn!(state = ?state, "Core is in an unexpected state");
break 'bp_loop;
}
}

std::thread::sleep(Duration::from_millis(100));
}

let mode = ChannelMode::BlockIfFull;
debug!(mode = ?mode, "Set channel mode");
up_channel.set_mode(&mut core, mode)?;

debug!("Run core after breakpoint setup");
core.run()?;
}

// Only hold onto the Core when we need to lock the debug probe driver (before each read/write)
std::mem::drop(core);

let session = Arc::new(Mutex::new(session));
let up_channel = Arc::new(up_channel);
let session_clone = session.clone();
let up_channel_clone = up_channel.clone();
let defmt_cfg_clone = defmt_cfg.clone();
let mut join_handle: tokio::task::JoinHandle<Result<(), Error>> = tokio::spawn(async move {
let mut stream = DefmtRttReader::new(
intr.clone(),
session_clone,
up_channel,
up_channel_clone,
defmt_cfg_clone.plugin.rtt_collector.core,
);
defmt_reader::run(&mut stream, defmt_cfg_clone, intr).await?;
Expand All @@ -329,18 +427,30 @@ async fn do_main() -> Result<(), Box<dyn std::error::Error>> {
}
};

// TODO set channel mode to non-blocking on exit
let mut session = match session.lock() {
Ok(s) => s,
// Reader thread is either shutdown or aborted
Err(s) => s.into_inner(),
};
let mut core = session.core(defmt_cfg.plugin.rtt_collector.core)?;
let mode = ChannelMode::NoBlockTrim;
debug!(mode = ?mode, "Set channel mode");
up_channel.set_mode(&mut core, mode)?;

Ok(())
}

fn get_rtt_symbol<T: io::Read + io::Seek>(file: &mut T) -> Option<u64> {
get_symbol(file, "_SEGGER_RTT")
}

fn get_symbol<T: io::Read + io::Seek>(file: &mut T, symbol: &str) -> Option<u64> {
let mut buffer = Vec::new();
if file.read_to_end(&mut buffer).is_ok() {
if let Ok(binary) = goblin::elf::Elf::parse(buffer.as_slice()) {
for sym in &binary.syms {
if let Some(name) = binary.strtab.get_at(sym.st_name) {
if name == "_SEGGER_RTT" {
if name == symbol {
return Some(sym.st_value);
}
}
Expand Down Expand Up @@ -377,7 +487,7 @@ fn attach_retry_loop(
Ok(Rtt::attach(core, memory_map)?)
}

#[derive(Debug, Error)]
#[derive(Debug, thiserror::Error)]
enum Error {
#[error("No probes available")]
NoProbesAvailable,
Expand All @@ -390,6 +500,9 @@ enum Error {
#[error("The RTT up channel ({0}) is invalid")]
UpChannelInvalid(usize),

#[error("Could not locate the address of symbol '{0}' in the ELF file")]
ElfSymbol(String),

#[error("Encountered an error with the probe. {0}")]
ProbeRs(#[from] probe_rs::Error),

Expand All @@ -403,21 +516,21 @@ enum Error {
struct DefmtRttReader {
interruptor: Interruptor,
session: Arc<Mutex<Session>>,
ch: UpChannel,
channel: Arc<UpChannel>,
core_index: usize,
}

impl DefmtRttReader {
pub fn new(
interruptor: Interruptor,
session: Arc<Mutex<Session>>,
ch: UpChannel,
channel: Arc<UpChannel>,
core_index: usize,
) -> Self {
Self {
interruptor,
session,
ch,
channel,
core_index,
}
}
Expand All @@ -431,7 +544,7 @@ impl io::Read for DefmtRttReader {
let mut core = session
.core(self.core_index)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
self.ch
self.channel
.read(&mut core, buf)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?
};
Expand Down

0 comments on commit c0d44c9

Please sign in to comment.