Skip to content
This repository has been archived by the owner on Jan 30, 2024. It is now read-only.

Commit

Permalink
Implement --no-reset mode for live attach to a running device.
Browse files Browse the repository at this point in the history
  • Loading branch information
whitequark committed Jun 21, 2023
1 parent f01365b commit a2af87a
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/backtrace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl Outcome {
Outcome::StackOverflow => log::error!("the program has overflowed its stack"),
Outcome::HardFault => log::error!("the program panicked"),
Outcome::Ok => log::info!("device halted without error"),
Outcome::CtrlC => log::info!("device halted by user"),
Outcome::CtrlC => log::info!("interrupted by user"),
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub struct Opts {
list_probes: bool,

/// Whether to measure the program's stack consumption.
#[arg(long)]
#[arg(long, conflicts_with = "no-reset")]
pub measure_stack: bool,

/// Skip writing the application binary to flash.
Expand All @@ -75,6 +75,10 @@ pub struct Opts {
#[arg(long, env = "PROBE_RUN_PROBE")]
pub probe: Option<String>,

/// Whether to reset the target when attaching and detaching. Implies `--no-flash`.
#[arg(long = "no-reset", default_value = "true", action = ArgAction::SetFalse)]
pub reset: bool,

/// Whether to shorten paths (e.g. to crates.io dependencies) in backtraces and defmt logs
#[arg(long)]
pub shorten_paths: bool,
Expand Down
9 changes: 0 additions & 9 deletions src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ impl<'file> Elf<'file> {
})
}

pub fn main_fn_address(&self) -> u32 {
self.symbols.main_fn_address
}

pub fn program_uses_heap(&self) -> bool {
self.symbols.program_uses_heap
}
Expand Down Expand Up @@ -176,14 +172,12 @@ fn extract_debug_frame<'file>(elf: &ObjectFile<'file>) -> anyhow::Result<DebugFr
}

struct Symbols {
main_fn_address: u32,
program_uses_heap: bool,
reset_fn_range: Range<u32>,
rtt_buffer_address: Option<u32>,
}

fn extract_symbols(elf: &ObjectFile, reset_fn_address: u32) -> anyhow::Result<Symbols> {
let mut main_fn_address = None;
let mut program_uses_heap = false;
let mut reset_symbols = Vec::new();
let mut rtt_buffer_address = None;
Expand All @@ -196,7 +190,6 @@ fn extract_symbols(elf: &ObjectFile, reset_fn_address: u32) -> anyhow::Result<Sy

let address = symbol.address().try_into().expect("expected 32-bit ELF");
match name {
"main" => main_fn_address = Some(cortexm::clear_thumb_bit(address)),
"_SEGGER_RTT" => rtt_buffer_address = Some(address),
"__rust_alloc" | "__rg_alloc" | "__rdl_alloc" | "malloc" if !program_uses_heap => {
log::debug!("symbol `{}` indicates heap is in use", name);
Expand All @@ -211,7 +204,6 @@ fn extract_symbols(elf: &ObjectFile, reset_fn_address: u32) -> anyhow::Result<Sy
}
}

let main_fn_address = main_fn_address.ok_or(anyhow!("`main` symbol not found"))?;
let reset_fn_range = {
if reset_symbols.len() == 1 {
let reset = reset_symbols.remove(0);
Expand All @@ -226,7 +218,6 @@ fn extract_symbols(elf: &ObjectFile, reset_fn_address: u32) -> anyhow::Result<Sy
};

Ok(Symbols {
main_fn_address,
program_uses_heap,
reset_fn_range,
rtt_buffer_address,
Expand Down
96 changes: 68 additions & 28 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,29 +49,42 @@ fn run_target_program(elf_path: &Path, chip_name: &str, opts: &cli::Opts) -> any
// connect to probe and flash firmware
let probe_target = lookup_probe_target(elf_path, chip_name, opts)?;
let mut sess = attach_to_probe(probe_target.clone(), opts)?;
flash(&mut sess, elf_path, opts)?;
if opts.reset {
flash(&mut sess, elf_path, opts)?;
}

// attack to core
// attach to core
let memory_map = sess.target().memory_map.clone();
let core = &mut sess.core(0)?;

// reset-halt the core; this is necessary for analyzing the vector table and
// painting the stack
core.reset_and_halt(TIMEOUT)?;
// reset if requested, and then halt the core; this is necessary for analyzing
// the vector table and painting the stack
if opts.reset {
core.reset_and_halt(TIMEOUT)?;
} else {
core.halt(TIMEOUT)?;
}

// gather information
let elf_bytes = fs::read(elf_path)?;
let elf = &Elf::parse(&elf_bytes, elf_path)?;
let target_info = TargetInfo::new(elf, memory_map, probe_target)?;

// install stack canary
let canary = Canary::install(core, &target_info, elf, opts.measure_stack)?;
if opts.measure_stack && canary.is_none() {
bail!("failed to set up stack measurement");
let canary;
if opts.reset {
// install stack canary
canary = Canary::install(core, &target_info, elf, opts.measure_stack)?;
if opts.measure_stack && canary.is_none() {
bail!("failed to set up stack measurement");
}
} else {
// cannot safely touch the stack of a running application
canary = None;
log::warn!("You are using `--no-reset` -- stack overflow checks will not be done")
}

// run program and print logs until there is an exception
start_program(core, elf)?;
attach_to_program(core, elf)?;
let current_dir = &env::current_dir()?;
let halted_due_to_signal = print_logs(core, current_dir, elf, &target_info.memory_map, opts)?; // blocks until exception
print_separator()?;
Expand All @@ -87,8 +100,13 @@ fn run_target_program(elf_path: &Path, chip_name: &str, opts: &cli::Opts) -> any
backtrace::Settings::new(canary_touched, current_dir, halted_due_to_signal, opts);
let outcome = backtrace::print(core, elf, &target_info, &mut backtrace_settings)?;

// reset the target
core.reset_and_halt(TIMEOUT)?;
if opts.reset {
// reset the target
core.reset_and_halt(TIMEOUT)?;
} else {
// remove our instrumentation and restart the target
detach_from_program(core, elf)?;
}

outcome.log();
Ok(outcome.into())
Expand Down Expand Up @@ -196,13 +214,14 @@ fn flashing_progress() -> flashing::FlashProgress {
})
}

fn start_program(core: &mut Core, elf: &Elf) -> anyhow::Result<()> {
log::debug!("starting device");
fn attach_to_program(core: &mut Core, elf: &Elf) -> anyhow::Result<()> {
log::debug!("attaching to program");

match (core.available_breakpoint_units()?, elf.rtt_buffer_address()) {
(0, Some(_)) => bail!("RTT not supported on device without HW breakpoints"),
(0, None) => log::warn!("device doesn't support HW breakpoints; HardFault will NOT make `probe-run` exit with an error code"),
(_, Some(rtt_buffer_address)) => set_rtt_to_blocking(core, elf.main_fn_address(), rtt_buffer_address)?,
(_, Some(rtt_buffer_address)) =>
set_rtt_blocking_mode(core, rtt_buffer_address, true)?,
(_, None) => {}
}

Expand All @@ -212,16 +231,33 @@ fn start_program(core: &mut Core, elf: &Elf) -> anyhow::Result<()> {
Ok(())
}

fn detach_from_program(core: &mut Core, elf: &Elf) -> anyhow::Result<()> {
log::debug!("detaching from program");

if let Some(rtt_buffer_address) = elf.rtt_buffer_address() {
set_rtt_blocking_mode(core, rtt_buffer_address, false)?;
}

core.clear_hw_breakpoint(cortexm::clear_thumb_bit(elf.vector_table.hard_fault).into())?;
core.run()?;

Ok(())
}

/// Set rtt to blocking mode
fn set_rtt_to_blocking(
fn set_rtt_blocking_mode(
core: &mut Core,
main_fn_address: u32,
rtt_buffer_address: u32,
is_blocking: bool,
) -> anyhow::Result<()> {
// set and wait for a hardware breakpoint at the beginning of `fn main()`
core.set_hw_breakpoint(main_fn_address.into())?;
core.run()?;
core.wait_for_core_halted(Duration::from_secs(5))?;
log::debug!(
"setting RTT mode to {}",
if is_blocking {
"blocking"
} else {
"nonblocking"
}
);

// calculate address of up-channel-flags inside the rtt control block
const OFFSET: u32 = 44;
Expand All @@ -232,14 +268,17 @@ fn set_rtt_to_blocking(
core.read_32(rtt_buffer_address.into(), channel_flags)?;
// modify flags to blocking
const MODE_MASK: u32 = 0b11;
const MODE_NON_BLOCKING_TRIM: u32 = 0b01;
const MODE_BLOCK_IF_FULL: u32 = 0b10;
let modified_channel_flags = (channel_flags[0] & !MODE_MASK) | MODE_BLOCK_IF_FULL;
let modified_channel_flags = (channel_flags[0] & !MODE_MASK)
| if is_blocking {
MODE_BLOCK_IF_FULL
} else {
MODE_NON_BLOCKING_TRIM
};
// write flags back
core.write_word_32(rtt_buffer_address.into(), modified_channel_flags)?;

// clear the breakpoint we set before
core.clear_hw_breakpoint(main_fn_address.into())?;

Ok(())
}

Expand All @@ -256,17 +295,18 @@ fn print_logs(
let mut logging_channel = if let Some(address) = elf.rtt_buffer_address() {
Some(setup_logging_channel(core, memory_map, address)?)
} else {
eprintln!("RTT logs not available; blocking until the device halts..");
eprintln!("RTT logs not available; blocking until the device halts...");
None
};

let use_defmt = logging_channel
.as_ref()
.map_or(false, |channel| channel.name() == Some("defmt"));

if use_defmt && opts.no_flash {
if use_defmt && (!opts.reset || opts.no_flash) {
log::warn!(
"You are using `--no-flash` and `defmt` logging -- this combination can lead to malformed defmt data!"
"You are using `{}` and `defmt` logging -- this combination can lead to malformed defmt data!",
if !opts.reset { "--no-reset" } else { "--no-flash" }
);
} else if use_defmt && elf.defmt_table.is_none() {
bail!("\"defmt\" RTT channel is in use, but the firmware binary contains no defmt data");
Expand Down

0 comments on commit a2af87a

Please sign in to comment.