diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index 1a1ee2c..b3b20ec 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -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"), } } } diff --git a/src/cli.rs b/src/cli.rs index dadece1..71d358b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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. @@ -75,6 +75,10 @@ pub struct Opts { #[arg(long, env = "PROBE_RUN_PROBE")] pub probe: Option, + /// 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, diff --git a/src/elf.rs b/src/elf.rs index 595236f..91bae7a 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -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 } @@ -176,14 +172,12 @@ fn extract_debug_frame<'file>(elf: &ObjectFile<'file>) -> anyhow::Result, rtt_buffer_address: Option, } fn extract_symbols(elf: &ObjectFile, reset_fn_address: u32) -> anyhow::Result { - let mut main_fn_address = None; let mut program_uses_heap = false; let mut reset_symbols = Vec::new(); let mut rtt_buffer_address = None; @@ -196,7 +190,6 @@ fn extract_symbols(elf: &ObjectFile, reset_fn_address: u32) -> anyhow::Result 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); @@ -211,7 +204,6 @@ fn extract_symbols(elf: &ObjectFile, reset_fn_address: u32) -> anyhow::Result anyhow::Result 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()?; @@ -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()) @@ -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) => {} } @@ -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; @@ -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(()) } @@ -256,7 +295,7 @@ 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 }; @@ -264,9 +303,10 @@ fn print_logs( .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");