Repository: probe-rs/probe-rs
Pull request #1411 (2023-01-22 00:25:10)

Make flash-algorithm development easy


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e3161de..6c2bcdbe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,14 +16,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
   Check if the flash algorithm supports RTT, and if it does, store the RTT control block
   address in the target YAML file.
-  
+
+- probe-rs: Read RTT during flashing procedures if the algorithm supports RTT.
+
+  This enables better debugging for flash-algorithms and should encourage development of said algorithms.
+
 - Add support for FT4232HL probe.
 
 - probe-rs-cli: Add `--log-file` option to specify where the log file should be placed.
-  
+
+- target-gen: Add a command which enables the easy development and debugging of a flash algorithm.
+
+  `target-gen test` is a new command to automatically upload, run, print RTT messages and test
+  a flash algorithm. Have a look at the [template](https://github.com/probe-rs/flash-algorithm-template)
+  to create a new flash algorithm.
+
 ### Changed
 
 - cmsisdap: Increased read timeout from 100ms to 1000ms.
+- rtt: Moved RTT to the probe-rs library instead of having it in its own library. (#1411)
 
 ### Fixed
 
@@ -31,7 +42,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - probe-rs-cli: Reduce RTT polling frequency in run command to avoid USB instability issues.
 
-
 ## [0.14.2]
 
 Released 2023-01-18
diff --git a/Cargo.lock b/Cargo.lock
index ec91f4ca..5bfffe29 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -348,7 +348,6 @@ dependencies = [
  "log",
  "probe-rs",
  "probe-rs-cli-util",
- "probe-rs-rtt",
  "sanitize-filename",
  "serde",
  "serde_json",
@@ -1144,7 +1143,7 @@ checksum = "572564d6cba7d09775202c8e7eebc4d534d5ae36578ab402fb21e182a0ac9505"
 dependencies = [
  "log",
  "plain",
- "scroll 0.11.0",
+ "scroll",
 ]
 
 [[package]]
@@ -1560,6 +1559,12 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "kmp"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "131bddab59a64213cc1e9f0c5be010d7eb485951358ab7e52017888dec482028"
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -2086,6 +2091,7 @@ dependencies = [
  "itm",
  "jaylink",
  "jep106",
+ "kmp",
  "libftdi1-sys",
  "num-traits",
  "object",
@@ -2094,7 +2100,7 @@ dependencies = [
  "probe-rs-target",
  "rand",
  "rusb",
- "scroll 0.11.0",
+ "scroll",
  "serde",
  "serde_json",
  "serde_yaml",
@@ -2128,7 +2134,7 @@ dependencies = [
  "ron",
  "rustyline",
  "sanitize-filename",
- "scroll 0.11.0",
+ "scroll",
  "serde",
  "termtree",
  "thiserror",
@@ -2157,7 +2163,6 @@ dependencies = [
  "num-traits",
  "once_cell",
  "probe-rs",
- "probe-rs-rtt",
  "sentry",
  "serde",
  "simplelog",
@@ -2184,7 +2189,7 @@ dependencies = [
  "probe-rs-cli-util",
  "sanitize-filename",
  "schemafy",
- "scroll 0.11.0",
+ "scroll",
  "serde",
  "serde_json",
  "svd-parser",
@@ -2194,17 +2199,6 @@ dependencies = [
  "tracing-subscriber",
 ]
 
-[[package]]
-name = "probe-rs-rtt"
-version = "0.14.2"
-dependencies = [
- "probe-rs",
- "scroll 0.10.2",
- "serde",
- "thiserror",
- "tracing",
-]
-
 [[package]]
 name = "probe-rs-target"
 version = "0.14.2"
@@ -2488,7 +2482,6 @@ dependencies = [
  "clap",
  "pretty_env_logger",
  "probe-rs",
- "probe-rs-rtt",
 ]
 
 [[package]]
@@ -2657,12 +2650,6 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
-[[package]]
-name = "scroll"
-version = "0.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec"
-
 [[package]]
 name = "scroll"
 version = "0.11.0"
@@ -3109,20 +3096,24 @@ dependencies = [
  "assert_cmd",
  "assert_fs",
  "base64 0.21.0",
+ "cargo_metadata",
  "clap",
  "cmsis-pack",
+ "colored",
  "futures",
  "goblin",
+ "indicatif",
  "log",
  "predicates",
  "probe-rs",
  "probe-rs-target",
  "reqwest",
- "scroll 0.11.0",
+ "scroll",
  "serde",
  "serde_yaml",
  "tokio",
  "tracing-subscriber",
+ "xshell",
  "zip",
 ]
 
@@ -3345,9 +3336,9 @@ dependencies = [
 
 [[package]]
 name = "toml_edit"
-version = "0.18.0"
+version = "0.18.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "729bfd096e40da9c001f778f5cdecbd2957929a24e10e5883d9392220a751581"
+checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
 dependencies = [
  "indexmap",
  "nom8",
diff --git a/Cargo.toml b/Cargo.toml
index 1f3e70ac..e6d839b2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,7 +19,6 @@ members = [
     "probe-rs",
     "probe-rs-target",
     "probe-rs-cli-util",
-    "rtt",
     "rtthost",
     "smoke-tester",
     "xtask",
diff --git a/README.md b/README.md
index 4acee244..76febd6c 100644
--- a/README.md
+++ b/README.md
@@ -151,6 +151,11 @@ See [the vcpkg crate documentation](https://docs.rs/vcpkg/) for more information
 Target files are generated using [target-gen](https://github.com/probe-rs/probe-rs/tree/master/target-gen) from CMSIS packs provided [here](https://developer.arm.com/tools-and-software/embedded/cmsis/cmsis-search).
 Generated files are then placed in `probe-rs/targets` for inclusion in the probe-rs project.
 
+### Writing new flash algorithms
+
+If there is no CMSIS-Pack with a flash algorithm available, it is necessary to write a target definition and a flash algorithm by oneself.
+You can use our [template](https://github.com/probe-rs/flash-algorithm-template) for writing an algorithm. Please follow the instructions in the `README.md` in that repo.
+
 ## Acknowledgements
 
 In early stages of this library, we profited invaluably from the pyOCD code to understand how flashing works. Also it's always a good reference to cross check how ARM specific things work. So, a big thank you to the team behind pyOCD!
diff --git a/cargo-embed/Cargo.toml b/cargo-embed/Cargo.toml
index 53f5b2f1..ffd1fe05 100644
--- a/cargo-embed/Cargo.toml
+++ b/cargo-embed/Cargo.toml
@@ -23,7 +23,6 @@ gdb-server = { workspace = true }
 probe-rs-cli-util = { workspace = true, default-features = false, features = [
     "anyhow",
 ] }
-probe-rs-rtt = { workspace = true }
 
 git-version = "0.3.5"
 env_logger = "0.10.0"
diff --git a/cargo-embed/src/config/mod.rs b/cargo-embed/src/config/mod.rs
index 0c30c2c6..af49406e 100644
--- a/cargo-embed/src/config/mod.rs
+++ b/cargo-embed/src/config/mod.rs
@@ -4,8 +4,8 @@ use figment::{
     providers::{Format, Json, Toml, Yaml},
     Figment,
 };
+use probe_rs::rtt::ChannelMode;
 use probe_rs::WireProtocol;
-use probe_rs_rtt::ChannelMode;
 use serde::{Deserialize, Serialize};
 use std::path::PathBuf;
 
diff --git a/cargo-embed/src/main.rs b/cargo-embed/src/main.rs
index c96a3360..74694e25 100644
--- a/cargo-embed/src/main.rs
+++ b/cargo-embed/src/main.rs
@@ -34,7 +34,7 @@ use probe_rs_cli_util::{
     logging::{self, Metadata},
 };
 
-use probe_rs_rtt::{Rtt, ScanRegion};
+use probe_rs::rtt::{Rtt, ScanRegion};
 
 use probe_rs_gdb_server::GdbInstanceConfiguration;
 
@@ -404,12 +404,13 @@ fn main_try(metadata: Arc<Mutex<Metadata>>) -> Result<()> {
                             fp.finish()
                         };
                     }
+                    DiagnosticMessage { .. } => (),
                 }
             });
 
             let mut options = DownloadOptions::new();
 
-            options.progress = Some(&progress);
+            options.progress = Some(progress);
             options.keep_unwritten_bytes = config.flashing.restore_unwritten_bytes;
             options.do_chip_erase = config.flashing.do_chip_erase;
 
diff --git a/cargo-embed/src/rttui/app.rs b/cargo-embed/src/rttui/app.rs
index 9f7483fc..843d1fa8 100644
--- a/cargo-embed/src/rttui/app.rs
+++ b/cargo-embed/src/rttui/app.rs
@@ -4,8 +4,8 @@ use crossterm::{
     execute,
     terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
 };
+use probe_rs::rtt::RttChannel;
 use probe_rs::Core;
-use probe_rs_rtt::RttChannel;
 use std::{fmt::write, path::PathBuf, sync::mpsc::RecvTimeoutError};
 use std::{
     io::{Read, Seek, Write},
@@ -49,7 +49,7 @@ fn pull_channel<C: RttChannel>(channels: &mut Vec<C>, n: usize) -> Option<C> {
 
 impl App {
     pub fn new(
-        mut rtt: probe_rs_rtt::Rtt,
+        mut rtt: probe_rs::rtt::Rtt,
         config: &crate::config::Config,
         logname: String,
     ) -> Result<Self> {
diff --git a/cargo-embed/src/rttui/channel.rs b/cargo-embed/src/rttui/channel.rs
index fac80a70..415de774 100644
--- a/cargo-embed/src/rttui/channel.rs
+++ b/cargo-embed/src/rttui/channel.rs
@@ -1,7 +1,7 @@
 use std::fmt;
 
+use probe_rs::rtt::{ChannelMode, DownChannel, UpChannel};
 use probe_rs::Core;
-use probe_rs_rtt::{ChannelMode, DownChannel, UpChannel};
 use time::{macros::format_description, OffsetDateTime};
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
diff --git a/cli/src/main.rs b/cli/src/main.rs
index 86006b8d..03bbc2fe 100644
--- a/cli/src/main.rs
+++ b/cli/src/main.rs
@@ -489,7 +489,7 @@ fn download_program_fast(
 fn erase(common: &ProbeOptions) -> Result<()> {
     let mut session = common.simple_attach()?;
 
-    erase_all(&mut session)?;
+    erase_all(&mut session, None)?;
 
     Ok(())
 }
diff --git a/debugger/src/debugger/debug_entry.rs b/debugger/src/debugger/debug_entry.rs
index 1e84c51c..84fede02 100644
--- a/debugger/src/debugger/debug_entry.rs
+++ b/debugger/src/debugger/debug_entry.rs
@@ -711,12 +711,15 @@ impl Debugger {
                                             )
                                             .ok();
                                     }
+                                    probe_rs::flashing::ProgressEvent::DiagnosticMessage {
+                                        ..
+                                    } => (),
                                 }
                             })
                         } else {
                             FlashProgress::new(|_event| {})
                         };
-                        download_options.progress = Some(&flash_progress);
+                        download_options.progress = Some(flash_progress);
                         download_file_with_options(
                             &mut session_data.session,
                             path_to_elf,
diff --git a/probe-rs-cli-util/Cargo.toml b/probe-rs-cli-util/Cargo.toml
index e33c054f..5affece1 100644
--- a/probe-rs-cli-util/Cargo.toml
+++ b/probe-rs-cli-util/Cargo.toml
@@ -19,7 +19,6 @@ license.workspace = true
 default = ["anyhow"]
 
 [dependencies]
-probe-rs-rtt = { workspace = true }
 probe-rs = { workspace = true }
 
 thiserror = "1.0"
diff --git a/probe-rs-cli-util/src/flash.rs b/probe-rs-cli-util/src/flash.rs
index f05ccfcc..dbfced93 100644
--- a/probe-rs-cli-util/src/flash.rs
+++ b/probe-rs-cli-util/src/flash.rs
@@ -135,10 +135,11 @@ pub fn run_flash_download(
                         fp.finish()
                     };
                 }
+                DiagnosticMessage { .. } => (),
             }
         });
 
-        download_option.progress = Some(&progress);
+        download_option.progress = Some(progress);
 
         loader.commit(session, download_option).map_err(|error| {
             OperationError::FlashingFailed {
diff --git a/probe-rs-cli-util/src/rtt.rs b/probe-rs-cli-util/src/rtt.rs
index 57b74df8..417d45fa 100644
--- a/probe-rs-cli-util/src/rtt.rs
+++ b/probe-rs-cli-util/src/rtt.rs
@@ -3,9 +3,9 @@ use anyhow::{anyhow, Result};
 use defmt_decoder::DecodeError;
 use num_traits::Zero;
 use probe_rs::config::MemoryRegion;
+pub use probe_rs::rtt::ChannelMode;
+use probe_rs::rtt::{DownChannel, Rtt, ScanRegion, UpChannel};
 use probe_rs::Core;
-pub use probe_rs_rtt::ChannelMode;
-use probe_rs_rtt::{DownChannel, Rtt, ScanRegion, UpChannel};
 use serde::Deserialize;
 use std::collections::HashMap;
 use std::fs::File;
@@ -211,7 +211,7 @@ impl RttActiveChannel {
                         }
                     }
                     Err(err) => {
-                        if matches!(err, probe_rs_rtt::Error::Probe(_)) {
+                        if matches!(err, probe_rs::rtt::Error::Probe(_)) {
                             std::thread::sleep(std::time::Duration::from_millis(50));
                         } else {
                             log::error!("\nError reading from RTT: {}", err);
@@ -334,7 +334,7 @@ pub struct RttActiveTarget {
 impl RttActiveTarget {
     /// RttActiveTarget collects references to all the `RttActiveChannel`s, for latter polling/pushing of data.
     pub fn new(
-        mut rtt: probe_rs_rtt::Rtt,
+        mut rtt: probe_rs::rtt::Rtt,
         elf_file: &Path,
         rtt_config: &RttConfig,
     ) -> Result<Self> {
diff --git a/probe-rs/Cargo.toml b/probe-rs/Cargo.toml
index 31e94304..04312f12 100644
--- a/probe-rs/Cargo.toml
+++ b/probe-rs/Cargo.toml
@@ -16,7 +16,9 @@ keywords = ["embedded"]
 license.workspace = true
 
 [features]
-default = ["builtin-targets"]
+default = ["builtin-targets", "rtt"]
+
+rtt = ["kmp"]
 
 vendored-libusb = ["rusb/vendored"]
 
@@ -44,6 +46,7 @@ hidapi = { version = "2.0.2", default-features = false, features = [
 ihex = "3.0.0"
 jaylink = "0.3.0"
 jep106 = "0.2.6"
+kmp = { version = "0.1", optional = true }
 once_cell = "1.7.2"
 num-traits = "0.2.11"
 object = { version = "0.30.0", default-features = false, features = [
diff --git a/probe-rs/src/flashing/download.rs b/probe-rs/src/flashing/download.rs
index 58f8eef5..2cfbaf23 100644
--- a/probe-rs/src/flashing/download.rs
+++ b/probe-rs/src/flashing/download.rs
@@ -92,9 +92,9 @@ pub enum FileDownloadError {
 /// ```
 #[derive(Default)]
 #[non_exhaustive]
-pub struct DownloadOptions<'progress> {
+pub struct DownloadOptions {
     /// An optional progress reporter which is used if this argument is set to `Some(...)`.
-    pub progress: Option<&'progress FlashProgress>,
+    pub progress: Option<FlashProgress>,
     /// If `keep_unwritten_bytes` is `true`, erased portions of the flash that are not overwritten by the ELF data
     /// are restored afterwards, such that the old contents are untouched.
     ///
@@ -117,7 +117,7 @@ pub struct DownloadOptions<'progress> {
     pub disable_double_buffering: bool,
 }
 
-impl<'progress> DownloadOptions<'progress> {
+impl DownloadOptions {
     /// DownloadOptions with default values.
     pub fn new() -> Self {
         Self::default()
@@ -146,7 +146,7 @@ pub fn download_file_with_options<P: AsRef<Path>>(
     session: &mut Session,
     path: P,
     format: Format,
-    options: DownloadOptions<'_>,
+    options: DownloadOptions,
 ) -> Result<(), FileDownloadError> {
     let mut file = match File::open(path.as_ref()) {
         Ok(file) => file,
diff --git a/probe-rs/src/flashing/erase.rs b/probe-rs/src/flashing/erase.rs
index 00418f7a..5973a728 100644
--- a/probe-rs/src/flashing/erase.rs
+++ b/probe-rs/src/flashing/erase.rs
@@ -5,8 +5,13 @@ use probe_rs_target::{MemoryRange, MemoryRegion, NvmRegion};
 use crate::flashing::{flasher::Flasher, FlashError, FlashLoader};
 use crate::Session;
 
+use super::FlashProgress;
+
 /// Mass-erase all nonvolatile memory.
-pub fn erase_all(session: &mut Session) -> Result<(), FlashError> {
+///
+/// The optional progress will only be used to emit RTT messages.
+/// No actual indication for the state of the erase all operation will be given.
+pub fn erase_all(session: &mut Session, progress: Option<FlashProgress>) -> Result<(), FlashError> {
     tracing::debug!("Erasing all...");
 
     let mut algos: HashMap<(String, String), Vec<NvmRegion>> = HashMap::new();
@@ -45,7 +50,7 @@ pub fn erase_all(session: &mut Session) -> Result<(), FlashError> {
         let algo = algo.unwrap().clone();
 
         let core_index = session.target().core_index_by_name(&core_name).unwrap();
-        let mut flasher = Flasher::new(session, core_index, &algo)?;
+        let mut flasher = Flasher::new(session, core_index, &algo, progress.clone())?;
 
         if flasher.is_chip_erase_supported() {
             tracing::debug!("     -- chip erase supported, doing it.");
@@ -82,3 +87,82 @@ pub fn erase_all(session: &mut Session) -> Result<(), FlashError> {
 
     Ok(())
 }
+
+/// Erases `sectors` sectors starting from `start_sector` from flash.
+pub fn erase_sectors(
+    session: &mut Session,
+    progress: Option<FlashProgress>,
+    start_sector: usize,
+    sectors: usize,
+) -> Result<(), FlashError> {
+    tracing::debug!(
+        "Erasing sectors {start_sector} trough {}",
+        start_sector + sectors
+    );
+
+    let mut algos: HashMap<(String, String), Vec<NvmRegion>> = HashMap::new();
+    tracing::debug!("Regions:");
+    for region in &session.target().memory_map {
+        if let MemoryRegion::Nvm(region) = region {
+            tracing::debug!(
+                "    region: {:08x}-{:08x} ({} bytes)",
+                region.range.start,
+                region.range.end,
+                region.range.end - region.range.start
+            );
+
+            let algo = FlashLoader::get_flash_algorithm_for_region(region, session.target())?;
+
+            // Get the first core that can access the region
+            let core_name = region
+                .cores
+                .first()
+                .ok_or_else(|| FlashError::NoNvmCoreAccess(region.clone()))?;
+
+            let entry = algos
+                .entry((algo.name.clone(), core_name.clone()))
+                .or_default();
+            entry.push(region.clone());
+
+            tracing::debug!("     -- using algorithm: {}", algo.name);
+        }
+    }
+
+    for ((algo_name, core_name), regions) in algos {
+        tracing::debug!("Erasing with algorithm: {}", algo_name);
+
+        // This can't fail, algo_name comes from the target.
+        let algo = session.target().flash_algorithm_by_name(&algo_name);
+        let algo = algo.unwrap().clone();
+
+        let core_index = session.target().core_index_by_name(&core_name).unwrap();
+        let mut flasher = Flasher::new(session, core_index, &algo, progress.clone())?;
+
+        let sectors = flasher
+            .flash_algorithm()
+            .iter_sectors()
+            .skip(start_sector)
+            .take(sectors)
+            .filter(|info| {
+                let range = info.base_address..info.base_address + info.size;
+                regions.iter().any(|r| r.range.contains_range(&range))
+            })
+            .collect::<Vec<_>>();
+
+        flasher.run_erase(|active| {
+            for info in sectors {
+                tracing::debug!(
+                    "    sector: {:08x}-{:08x} ({} bytes)",
+                    info.base_address,
+                    info.base_address + info.size,
+                    info.size
+                );
+
+                active.erase_sector(info.base_address)?;
+            }
+            Ok(())
+        })?;
+    }
+
+    Ok(())
+}
diff --git a/probe-rs/src/flashing/flasher.rs b/probe-rs/src/flashing/flasher.rs
index 979a1944..29fe0082 100644
--- a/probe-rs/src/flashing/flasher.rs
+++ b/probe-rs/src/flashing/flasher.rs
@@ -7,6 +7,7 @@ use super::{
 use crate::config::NvmRegion;
 use crate::memory::MemoryInterface;
 use crate::{core::RegisterFile, session::Session, Core, InstructionSet};
+use std::time::Instant;
 use std::{fmt::Debug, time::Duration};
 
 pub(super) trait Operation {
@@ -52,6 +53,7 @@ pub(super) struct Flasher<'session> {
     session: &'session mut Session,
     core_index: usize,
     flash_algorithm: FlashAlgorithm,
+    progress: FlashProgress,
 }
 
 impl<'session> Flasher<'session> {
@@ -59,6 +61,7 @@ impl<'session> Flasher<'session> {
         session: &'session mut Session,
         core_index: usize,
         raw_flash_algorithm: &RawFlashAlgorithm,
+        progress: Option<FlashProgress>,
     ) -> Result<Self, FlashError> {
         let target = session.target();
 
@@ -87,6 +90,7 @@ impl<'session> Flasher<'session> {
             session,
             core_index,
             flash_algorithm,
+            progress: progress.unwrap_or(FlashProgress::new(|_| {})),
         };
 
         this.load()?;
@@ -163,6 +167,8 @@ impl<'session> Flasher<'session> {
         &mut self,
         clock: Option<u32>,
     ) -> Result<ActiveFlasher<'_, O>, FlashError> {
+        #[cfg(feature = "rtt")]
+        let memory_map = self.session.target().memory_map.clone();
         // Attach to memory and core.
         let core = self
             .session
@@ -172,6 +178,11 @@ impl<'session> Flasher<'session> {
         tracing::debug!("Preparing Flasher for operation {}", O::operation_name());
         let mut flasher = ActiveFlasher::<O> {
             core,
+            #[cfg(feature = "rtt")]
+            rtt: None,
+            #[cfg(feature = "rtt")]
+            memory_map,
+            progress: self.progress.clone(),
             flash_algorithm: self.flash_algorithm.clone(),
             _operation: core::marker::PhantomData,
         };
@@ -245,7 +256,6 @@ impl<'session> Flasher<'session> {
         restore_unwritten_bytes: bool,
         enable_double_buffering: bool,
         skip_erasing: bool,
-        progress: &FlashProgress,
     ) -> Result<(), FlashError> {
         tracing::debug!("Starting program procedure.");
         // Convert the list of flash operations into flash sectors and pages.
@@ -254,8 +264,7 @@ impl<'session> Flasher<'session> {
             &self.flash_algorithm,
             restore_unwritten_bytes,
         )?;
-
-        progress.initialized(flash_layout.clone());
+        self.progress.initialized(flash_layout.clone());
 
         tracing::debug!("Double Buffering enabled: {:?}", enable_double_buffering);
         tracing::debug!(
@@ -264,7 +273,7 @@ impl<'session> Flasher<'session> {
         );
 
         // Read all fill areas from the flash.
-        progress.started_filling();
+        self.progress.started_filling();
 
         if restore_unwritten_bytes {
             let fills = flash_layout.fills().to_vec();
@@ -275,28 +284,28 @@ impl<'session> Flasher<'session> {
 
                 // If we encounter an error, catch it, gracefully report the failure and return the error.
                 if result.is_err() {
-                    progress.failed_filling();
+                    self.progress.failed_filling();
                     return result;
                 } else {
-                    progress.page_filled(fill.size(), t.elapsed());
+                    self.progress.page_filled(fill.size(), t.elapsed());
                 }
             }
         }
 
         // We successfully finished filling.
-        progress.finished_filling();
+        self.progress.finished_filling();
 
         // Skip erase if necessary
         if !skip_erasing {
             // Erase all necessary sectors
-            self.sector_erase(&flash_layout, progress)?;
+            self.sector_erase(&flash_layout)?;
         }
 
         // Flash all necessary pages.
         if self.double_buffering_supported() && enable_double_buffering {
-            self.program_double_buffer(&flash_layout, progress)?;
+            self.program_double_buffer(&flash_layout)?;
         } else {
-            self.program_simple(&flash_layout, progress)?;
+            self.program_simple(&flash_layout)?;
         };
 
         Ok(())
@@ -323,12 +332,8 @@ impl<'session> Flasher<'session> {
     }
 
     /// Programs the pages given in `flash_layout` into the flash.
-    fn program_simple(
-        &mut self,
-        flash_layout: &FlashLayout,
-        progress: &FlashProgress,
-    ) -> Result<(), FlashError> {
-        progress.started_programming();
+    fn program_simple(&mut self, flash_layout: &FlashLayout) -> Result<(), FlashError> {
+        self.progress.started_programming();
 
         let mut t = std::time::Instant::now();
         let result = self.run_program(|active| {
@@ -339,28 +344,25 @@ impl<'session> Flasher<'session> {
                         page_address: page.address(),
                         source: Box::new(error),
                     })?;
-                progress.page_programmed(page.size(), t.elapsed());
+                active.progress.page_programmed(page.size(), t.elapsed());
+
                 t = std::time::Instant::now();
             }
             Ok(())
         });
 
         if result.is_ok() {
-            progress.finished_programming();
+            self.progress.finished_programming();
         } else {
-            progress.failed_programming();
+            self.progress.failed_programming();
         }
 
         result
     }
 
     /// Perform an erase of all sectors given in `flash_layout`.
-    fn sector_erase(
-        &mut self,
-        flash_layout: &FlashLayout,
-        progress: &FlashProgress,
-    ) -> Result<(), FlashError> {
-        progress.started_erasing();
+    fn sector_erase(&mut self, flash_layout: &FlashLayout) -> Result<(), FlashError> {
+        self.progress.started_erasing();
 
         let mut t = std::time::Instant::now();
         let result = self.run_erase(|active| {
@@ -371,17 +373,17 @@ impl<'session> Flasher<'session> {
                         sector_address: sector.address(),
                         source: Box::new(e),
                     })?;
+                active.progress.sector_erased(sector.size(), t.elapsed());
 
-                progress.sector_erased(sector.size(), t.elapsed());
                 t = std::time::Instant::now();
             }
             Ok(())
         });
 
         if result.is_ok() {
-            progress.finished_erasing();
+            self.progress.finished_erasing();
         } else {
-            progress.failed_erasing();
+            self.progress.failed_erasing();
         }
 
         result
@@ -396,14 +398,9 @@ impl<'session> Flasher<'session> {
     ///
     /// This is only possible if the RAM is large enough to
     /// fit at least two page buffers. See [Flasher::double_buffering_supported].
-    fn program_double_buffer(
-        &mut self,
-        flash_layout: &FlashLayout,
-        progress: &FlashProgress,
-    ) -> Result<(), FlashError> {
+    fn program_double_buffer(&mut self, flash_layout: &FlashLayout) -> Result<(), FlashError> {
         let mut current_buf = 0;
-
-        progress.started_programming();
+        self.progress.started_programming();
 
         let mut t = std::time::Instant::now();
         let result = self.run_program(|active| {
@@ -423,7 +420,8 @@ impl<'session> Flasher<'session> {
                         })?;
 
                 last_page_address = page.address();
-                progress.page_programmed(page.size(), t.elapsed());
+                active.progress.page_programmed(page.size(), t.elapsed());
+
                 t = std::time::Instant::now();
                 if result != 0 {
                     return Err(FlashError::RoutineCallFailed {
@@ -461,9 +459,10 @@ impl<'session> Flasher<'session> {
         });
 
         if result.is_ok() {
-            progress.finished_programming();
+            self.progress.finished_programming();
         } else {
-            progress.failed_programming();
+            self.progress.failed_programming();
+
             result?;
         }
 
@@ -499,6 +498,11 @@ fn into_reg(val: u64) -> Result<u32, FlashError> {
 
 pub(super) struct ActiveFlasher<'probe, O: Operation> {
     core: Core<'probe>,
+    #[cfg(feature = "rtt")]
+    rtt: Option<crate::rtt::Rtt>,
+    #[cfg(feature = "rtt")]
+    memory_map: Vec<MemoryRegion>,
+    progress: FlashProgress,
     flash_algorithm: FlashAlgorithm,
     _operation: core::marker::PhantomData<O>,
 }
@@ -644,6 +648,30 @@ impl<'probe, O: Operation> ActiveFlasher<'probe, O> {
         // Resume target operation.
         self.core.run()?;
 
+        #[cfg(feature = "rtt")]
+        if let Some(rtt_address) = self.flash_algorithm.rtt_control_block {
+            let now = std::time::Instant::now();
+            while self.rtt.is_none() {
+                std::thread::sleep(Duration::from_millis(1));
+                let rtt = match crate::rtt::Rtt::attach_region(
+                    &mut self.core,
+                    &self.memory_map,
+                    &crate::rtt::ScanRegion::Exact(rtt_address as u32),
+                ) {
+                    Ok(rtt) => Some(rtt),
+                    Err(error) => {
+                        tracing::error!("RTT could not be initialized: {error}");
+                        None
+                    }
+                };
+                self.rtt = rtt;
+
+                if self.rtt.is_some() || now.elapsed() > std::time::Duration::from_secs(1) {
+                    break;
+                }
+            }
+        }
+
         Ok(())
     }
 
@@ -652,11 +680,54 @@ impl<'probe, O: Operation> ActiveFlasher<'probe, O> {
         tracing::debug!("Waiting for routine call completion.");
         let regs = self.core.registers();
 
-        self.core.wait_for_core_halted(timeout)?;
+        // Wait until halted state is active again.
+        let start = Instant::now();
+
+        let mut timeout_ocurred = true;
+        while start.elapsed() < timeout {
+            if self.core.core_halted()? {
+                timeout_ocurred = false;
+                // Once the core is halted we know for sure all RTT data is written
+                // so we can read all of it.
+                #[cfg(feature = "rtt")]
+                self.read_rtt()?;
+                break;
+            }
+
+            // Periodically read RTT.
+            #[cfg(feature = "rtt")]
+            self.read_rtt()?;
+
+            std::thread::sleep(Duration::from_millis(1));
+        }
+
+        if timeout_ocurred {
+            return Err(FlashError::Core(crate::Error::Timeout));
+        }
 
         let r: u32 = self.core.read_core_reg(regs.result_register(0).id)?;
         Ok(r)
     }
+
+    #[cfg(feature = "rtt")]
+    fn read_rtt(&mut self) -> Result<(), FlashError> {
+        if let Some(rtt) = &mut self.rtt {
+            for channel in rtt.up_channels().iter() {
+                let mut buffer = vec![0; channel.buffer_size()];
+                match channel.read(&mut self.core, &mut buffer) {
+                    Ok(read) if read > 0 => {
+                        let message = String::from_utf8_lossy(&buffer[..read]).to_string();
+                        let channel = channel.name().unwrap_or("unnamed");
+                        tracing::debug!("RTT({channel}): {message}");
+                        self.progress.message(message);
+                    }
+                    Ok(_) => (),
+                    Err(error) => tracing::debug!("Reading RTT failed: {error}"),
+                };
+            }
+        }
+        Ok(())
+    }
 }
 
 impl<'probe> ActiveFlasher<'probe, Erase> {
diff --git a/probe-rs/src/flashing/loader.rs b/probe-rs/src/flashing/loader.rs
index ea91ca6b..94fdd6d5 100644
--- a/probe-rs/src/flashing/loader.rs
+++ b/probe-rs/src/flashing/loader.rs
@@ -8,8 +8,7 @@ use std::ops::Range;
 
 use super::builder::FlashBuilder;
 use super::{
-    extract_from_elf, BinOptions, DownloadOptions, FileDownloadError, FlashError, FlashProgress,
-    Flasher,
+    extract_from_elf, BinOptions, DownloadOptions, FileDownloadError, FlashError, Flasher,
 };
 use crate::memory::MemoryInterface;
 use crate::session::Session;
@@ -197,7 +196,7 @@ impl FlashLoader {
     pub fn commit(
         &self,
         session: &mut Session,
-        options: DownloadOptions<'_>,
+        options: DownloadOptions,
     ) -> Result<(), FlashError> {
         tracing::debug!("committing FlashLoader!");
 
@@ -302,7 +301,7 @@ impl FlashLoader {
                 .iter()
                 .position(|c| c.name == core_name)
                 .unwrap();
-            let mut flasher = Flasher::new(session, core, &algo)?;
+            let mut flasher = Flasher::new(session, core, &algo, options.progress.clone())?;
 
             let mut do_chip_erase = options.do_chip_erase;
 
@@ -317,7 +316,7 @@ impl FlashLoader {
                 tracing::debug!("    Doing chip erase...");
                 flasher.run_erase_all()?;
 
-                if let Some(progress) = options.progress {
+                if let Some(progress) = &options.progress {
                     progress.finished_erasing();
                 }
             }
@@ -343,7 +342,6 @@ impl FlashLoader {
                     options.keep_unwritten_bytes,
                     do_use_double_buffering,
                     options.skip_erase || do_chip_erase,
-                    options.progress.unwrap_or(&FlashProgress::new(|_| {})),
                 )?;
             }
         }
diff --git a/probe-rs/src/flashing/progress.rs b/probe-rs/src/flashing/progress.rs
index 477bc104..0eddf67d 100644
--- a/probe-rs/src/flashing/progress.rs
+++ b/probe-rs/src/flashing/progress.rs
@@ -1,5 +1,5 @@
 use super::FlashLayout;
-use std::time::Duration;
+use std::{sync::Arc, time::Duration};
 
 /// A structure to manage the flashing procedure progress reporting.
 ///
@@ -14,15 +14,16 @@ use std::time::Duration;
 /// // Print events
 /// let progress = FlashProgress::new(|event| println!("Event: {:#?}", event));
 /// ```
+#[derive(Clone)]
 pub struct FlashProgress {
-    handler: Box<dyn Fn(ProgressEvent)>,
+    handler: Arc<dyn Fn(ProgressEvent)>,
 }
 
 impl FlashProgress {
     /// Create a new `FlashProgress` structure with a given `handler` to be called on events.
     pub fn new(handler: impl Fn(ProgressEvent) + 'static) -> Self {
         Self {
-            handler: Box::new(handler),
+            handler: Arc::new(handler),
         }
     }
 
@@ -95,6 +96,11 @@ impl FlashProgress {
     pub(super) fn finished_filling(&self) {
         self.emit(ProgressEvent::FinishedFilling);
     }
+
+    #[cfg(feature = "rtt")]
+    pub(super) fn message(&self, message: String) {
+        self.emit(ProgressEvent::DiagnosticMessage { message });
+    }
 }
 
 /// Possible events during the flashing process.
@@ -164,4 +170,9 @@ pub enum ProgressEvent {
     FailedProgramming,
     /// Programming of the flash has finished successfully.
     FinishedProgramming,
+    /// a message was received from the algo.
+    DiagnosticMessage {
+        /// The message that was emitted.
+        message: String,
+    },
 }
diff --git a/probe-rs/src/lib.rs b/probe-rs/src/lib.rs
index afe14ade..a9a44e14 100644
--- a/probe-rs/src/lib.rs
+++ b/probe-rs/src/lib.rs
@@ -82,6 +82,9 @@ mod memory;
 #[warn(missing_docs)]
 mod probe;
 #[warn(missing_docs)]
+#[cfg(feature = "rtt")]
+pub mod rtt;
+#[warn(missing_docs)]
 mod session;
 
 pub use crate::config::{CoreType, InstructionSet, Target};
diff --git a/rtt/src/rtt.rs b/probe-rs/src/rtt.rs
similarity index 72%
rename from rtt/src/rtt.rs
rename to probe-rs/src/rtt.rs
index decc9783..dc5856c4 100644
--- a/rtt/src/rtt.rs
+++ b/probe-rs/src/rtt.rs
@@ -1,6 +1,53 @@
-use crate::channel::*;
-use crate::{Channels, Error};
-use probe_rs::{config::MemoryRegion, Core, MemoryInterface};
+//! Host side implementation of the RTT (Real-Time Transfer) I/O protocol over probe-rs
+//!
+//! RTT implements input and output to/from a microcontroller using in-memory ring buffers and
+//! memory polling. This enables debug logging from the microcontroller with minimal delays and no
+//! blocking, making it usable even in real-time applications where e.g. semihosting delays cannot
+//! be tolerated.
+//!
+//! This crate enables you to read and write via RTT channels. It's also used as a building-block
+//! for probe-rs debugging tools.
+//!
+//! ## Example
+//!
+//! ```no_run
+//! use std::sync::{Arc, Mutex};
+//! use probe_rs::{Probe, Permissions};
+//! use probe_rs::rtt::Rtt;
+//!
+//! // First obtain a probe-rs session (see probe-rs documentation for details)
+//! let probe = Probe::list_all()[0].open()?;
+//! let mut session = probe.attach("somechip", Permissions::default())?;
+//! let memory_map = session.target().memory_map.clone();
+//! // Select a core.
+//! let mut core = session.core(0)?;
+//!
+//! // Attach to RTT
+//! let mut rtt = Rtt::attach(&mut core, &memory_map)?;
+//!
+//! // Read from a channel
+//! if let Some(input) = rtt.up_channels().take(0) {
+//!     let mut buf = [0u8; 1024];
+//!     let count = input.read(&mut core, &mut buf[..])?;
+//!
+//!     println!("Read data: {:?}", &buf[..count]);
+//! }
+//!
+//! // Write to a channel
+//! if let Some(output) = rtt.down_channels().take(0) {
+//!     output.write(&mut core, b"Hello, computer!\n")?;
+//! }
+//!
+//! # Ok::<(), Box<dyn std::error::Error>>(())
+//! ```
+
+mod channel;
+pub use channel::*;
+
+pub mod channels;
+pub use channels::Channels;
+
+use crate::{config::MemoryRegion, Core, MemoryInterface};
 use scroll::{Pread, LE};
 use std::borrow::Cow;
 use std::collections::BTreeMap;
@@ -204,34 +251,30 @@ impl Rtt {
             }
         };
 
-        let mut mem: Vec<u8> = Vec::new();
-        let mut instances: Vec<Rtt> = Vec::new();
-
-        for range in ranges.iter() {
-            if range.len() < Self::MIN_SIZE {
-                continue;
-            }
+        let mut instances = ranges
+            .into_iter()
+            .filter_map(|range| {
+                if range.len() < Self::MIN_SIZE {
+                    return None;
+                }
 
-            mem.resize(range.len(), 0);
-            {
-                core.read(range.start.into(), mem.as_mut())?;
-            }
+                let mut mem = vec![0; range.len()];
+                {
+                    core.read(range.start.into(), mem.as_mut()).ok()?;
+                }
 
-            for offset in 0..(mem.len() - Self::MIN_SIZE) {
-                if let Ok(Some(rtt)) = Rtt::from(
-                    core,
-                    memory_map,
-                    range.start + offset as u32,
-                    Some(&mem[offset..]),
-                ) {
-                    instances.push(rtt);
-
-                    if instances.len() >= 5 {
-                        break;
-                    }
+                match kmp::kmp_find(&Self::RTT_ID, mem.as_slice()) {
+                    Some(offset) => Rtt::from(
+                        core,
+                        memory_map,
+                        range.start + offset as u32,
+                        Some(&mem[offset..]),
+                    )
+                    .transpose(),
+                    None => None,
                 }
-            }
-        }
+            })
+            .collect::<Result<Vec<_>, _>>()?;
 
         if instances.is_empty() {
             return Err(Error::ControlBlockNotFound);
@@ -284,3 +327,37 @@ impl Default for ScanRegion {
         ScanRegion::Ram
     }
 }
+
+/// Error type for RTT operations.
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    /// RTT control block not found in target memory. Make sure RTT is initialized on the target.
+    #[error(
+        "RTT control block not found in target memory.\n\
+        - Make sure RTT is initialized on the target, AND that there are NO target breakpoints before RTT initalization.\n\
+        - For VSCode and probe-rs-debugger users, using `halt_after_reset:true` in your `launch.json` file will prevent RTT \n\
+        \tinitialization from happening on time.\n\
+        - Depending on the target, sleep modes can interfere with RTT."
+    )]
+    ControlBlockNotFound,
+
+    /// Multiple control blocks found in target memory. The data contains the control block addresses (up to 5).
+    #[error("Multiple control blocks found in target memory.")]
+    MultipleControlBlocksFound(Vec<u32>),
+
+    /// The control block has been corrupted. The data contains a detailed error.
+    #[error("Control block corrupted: {0}")]
+    ControlBlockCorrupted(String),
+
+    /// Attempted an RTT read/write operation against a Core number that is different from the Core number against which RTT was initialized
+    #[error("Incorrect Core number specified for this operation. Expected {0}, and found {1}")]
+    IncorrectCoreSpecified(usize, usize),
+
+    /// Wraps errors propagated up from probe-rs.
+    #[error("Error communicating with probe: {0}")]
+    Probe(#[from] crate::Error),
+
+    /// Wraps errors propagated up from reading memory on the target.
+    #[error("Unexpected error while reading {0} from target memory. Please report this as a bug.")]
+    MemoryRead(String),
+}
diff --git a/rtt/src/channel.rs b/probe-rs/src/rtt/channel.rs
similarity index 99%
rename from rtt/src/channel.rs
rename to probe-rs/src/rtt/channel.rs
index 91200861..b985db36 100644
--- a/rtt/src/channel.rs
+++ b/probe-rs/src/rtt/channel.rs
@@ -1,5 +1,5 @@
-use crate::Error;
-use probe_rs::{config::MemoryRegion, Core, MemoryInterface};
+use crate::rtt::Error;
+use crate::{config::MemoryRegion, Core, MemoryInterface};
 use scroll::{Pread, LE};
 use std::cmp::min;
 
diff --git a/rtt/src/channels.rs b/probe-rs/src/rtt/channels.rs
similarity index 98%
rename from rtt/src/channels.rs
rename to probe-rs/src/rtt/channels.rs
index 02bcbc7a..24ec443a 100644
--- a/rtt/src/channels.rs
+++ b/probe-rs/src/rtt/channels.rs
@@ -1,6 +1,6 @@
 //! List of RTT channels.
 
-use crate::RttChannel;
+use crate::rtt::RttChannel;
 use std::collections::{btree_map, BTreeMap};
 use std::mem;
 
diff --git a/rtt/Cargo.toml b/rtt/Cargo.toml
deleted file mode 100644
index 8004b8e0..00000000
--- a/rtt/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "probe-rs-rtt"
-description = "Host side implementation of the RTT (Real-Time Transfer) I/O protocol over probe-rs"
-version.workspace = true
-edition.workspace = true
-readme = "README.md"
-keywords = ["embedded", "debugging", "rtt"]
-license = "MIT"
-authors = ["Matti Virkkunen <mvirkkunen@gmail.com>"]
-repository.workspace = true
-
-[dependencies]
-tracing = { version = "0.1.37", features = ["log"] }
-probe-rs = { version = "0.14.2", path = "../probe-rs" }
-scroll = "0.10.1"
-serde = { version = "1", features = ["derive"] }
-thiserror = "1.0.11"
diff --git a/rtt/README.md b/rtt/README.md
deleted file mode 100644
index c66396a3..00000000
--- a/rtt/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# probe-rs-rtt
-
-[![crates.io](https://img.shields.io/crates/v/probe-rs-rtt)](https://crates.io/crates/probe-rs-rtt) [![documentation](https://docs.rs/probe-rs-rtt/badge.svg)](https://docs.rs/probe-rs-rtt)
-
-Host side implementation of the RTT (Real-Time Transfer) I/O protocol over probe-rs.
-
-## [Documentation](https://docs.rs/probe-rs-rtt)
-
-RTT implements input and output to/from a microcontroller using in-memory ring buffers and memory polling. This enables debug logging from the microcontroller with minimal delays and no blocking, making it usable even in real-time applications where e.g. semihosting delays cannot be tolerated.
-
-This crate enables you to read and write via RTT channels. It's also used as a building-block for probe-rs debugging tools.
diff --git a/rtt/src/lib.rs b/rtt/src/lib.rs
deleted file mode 100644
index 1bc5a606..00000000
--- a/rtt/src/lib.rs
+++ /dev/null
@@ -1,87 +0,0 @@
-//! Host side implementation of the RTT (Real-Time Transfer) I/O protocol over probe-rs
-//!
-//! RTT implements input and output to/from a microcontroller using in-memory ring buffers and
-//! memory polling. This enables debug logging from the microcontroller with minimal delays and no
-//! blocking, making it usable even in real-time applications where e.g. semihosting delays cannot
-//! be tolerated.
-//!
-//! This crate enables you to read and write via RTT channels. It's also used as a building-block
-//! for probe-rs debugging tools.
-//!
-//! ## Example
-//!
-//! ```no_run
-//! use std::sync::{Arc, Mutex};
-//! use probe_rs::{Probe, Permissions};
-//! use probe_rs_rtt::Rtt;
-//!
-//! // First obtain a probe-rs session (see probe-rs documentation for details)
-//! let probe = Probe::list_all()[0].open()?;
-//! let mut session = probe.attach("somechip", Permissions::default())?;
-//! let memory_map = session.target().memory_map.clone();
-//! // Select a core.
-//! let mut core = session.core(0)?;
-//!
-//! // Attach to RTT
-//! let mut rtt = Rtt::attach(&mut core, &memory_map)?;
-//!
-//! // Read from a channel
-//! if let Some(input) = rtt.up_channels().take(0) {
-//!     let mut buf = [0u8; 1024];
-//!     let count = input.read(&mut core, &mut buf[..])?;
-//!
-//!     println!("Read data: {:?}", &buf[..count]);
-//! }
-//!
-//! // Write to a channel
-//! if let Some(output) = rtt.down_channels().take(0) {
-//!     output.write(&mut core, b"Hello, computer!\n")?;
-//! }
-//!
-//! # Ok::<(), Box<dyn std::error::Error>>(())
-//! ```
-
-use thiserror::Error;
-
-mod channel;
-pub use channel::*;
-
-pub mod channels;
-pub use channels::Channels;
-
-mod rtt;
-pub use rtt::*;
-
-/// Error type for RTT operations.
-#[derive(Error, Debug)]
-pub enum Error {
-    /// RTT control block not found in target memory. Make sure RTT is initialized on the target.
-    #[error(
-        "RTT control block not found in target memory.\n\
-        - Make sure RTT is initialized on the target, AND that there are NO target breakpoints before RTT initalization.\n\
-        - For VSCode and probe-rs-debugger users, using `halt_after_reset:true` in your `launch.json` file will prevent RTT \n\
-        \tinitialization from happening on time.\n\
-        - Depending on the target, sleep modes can interfere with RTT."
-    )]
-    ControlBlockNotFound,
-
-    /// Multiple control blocks found in target memory. The data contains the control block addresses (up to 5).
-    #[error("Multiple control blocks found in target memory.")]
-    MultipleControlBlocksFound(Vec<u32>),
-
-    /// The control block has been corrupted. The data contains a detailed error.
-    #[error("Control block corrupted: {0}")]
-    ControlBlockCorrupted(String),
-
-    /// Attempted an RTT read/write operation against a Core number that is different from the Core number against which RTT was initialized
-    #[error("Incorrect Core number specified for this operation. Expected {0}, and found {1}")]
-    IncorrectCoreSpecified(usize, usize),
-
-    /// Wraps errors propagated up from probe-rs.
-    #[error("Error communicating with probe: {0}")]
-    Probe(#[from] probe_rs::Error),
-
-    /// Wraps errors propagated up from reading memory on the target.
-    #[error("Unexpected error while reading {0} from target memory. Please report this as a bug.")]
-    MemoryRead(String),
-}
diff --git a/rtthost/Cargo.toml b/rtthost/Cargo.toml
index d3835790..246bf3eb 100644
--- a/rtthost/Cargo.toml
+++ b/rtthost/Cargo.toml
@@ -10,5 +10,4 @@ repository.workspace = true
 [dependencies]
 pretty_env_logger = "0.4.0"
 probe-rs = { workspace = true }
-probe-rs-rtt = { workspace = true }
-clap = { version = "4.1", features = ["derive"] }
+clap = { version = "4", features = ["derive"] }
diff --git a/rtthost/src/main.rs b/rtthost/src/main.rs
index ab91aef3..71ae639e 100644
--- a/rtthost/src/main.rs
+++ b/rtthost/src/main.rs
@@ -1,6 +1,6 @@
+use probe_rs::rtt::{Channels, Rtt, RttChannel, ScanRegion};
 use probe_rs::Permissions;
 use probe_rs::{config::TargetSelector, DebugProbeInfo, Probe};
-use probe_rs_rtt::{Channels, Rtt, RttChannel, ScanRegion};
 
 use clap::Parser;
 use std::io::prelude::*;
diff --git a/target-gen/Cargo.toml b/target-gen/Cargo.toml
index 0b92ac06..38e611fa 100644
--- a/target-gen/Cargo.toml
+++ b/target-gen/Cargo.toml
@@ -30,6 +30,7 @@ serde_yaml = "^0.9"
 log = "0.4.16"
 zip = "0.6.2"
 clap = { version = "4.1", features = ["derive"] }
+colored = "2"
 base64 = "0.21.0"
 anyhow = "1.0.57"
 reqwest = { version = "0.11.10", features = ["json", "blocking"] }
@@ -37,6 +38,9 @@ serde = { version = "1", features = ["derive"] }
 futures = "0.3.21"
 tokio = { version = "1.18.0", features = ["macros", "rt", "rt-multi-thread"] }
 tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
+xshell = { version = "0.2", default-features = false }
+cargo_metadata = { version = "0.15", default-features = false }
+indicatif = { version = "0.17", default-features = false }
 
 [dev-dependencies]
 assert_cmd = "2.0.4"
diff --git a/target-gen/src/commands.rs b/target-gen/src/commands.rs
new file mode 100644
index 00000000..c1834bdf
--- /dev/null
+++ b/target-gen/src/commands.rs
@@ -0,0 +1,2 @@
+pub mod elf;
+pub mod test;
diff --git a/target-gen/src/commands/elf.rs b/target-gen/src/commands/elf.rs
new file mode 100644
index 00000000..35b7f67b
--- /dev/null
+++ b/target-gen/src/commands/elf.rs
@@ -0,0 +1,158 @@
+use anyhow::{bail, Context, Result};
+use probe_rs::CoreType;
+use probe_rs_target::{
+    ArmCoreAccessOptions, Chip, ChipFamily, Core, CoreAccessOptions, MemoryRegion, NvmRegion,
+    RamRegion, TargetDescriptionSource::BuiltIn,
+};
+use std::{
+    fs::{File, OpenOptions},
+    io::{BufRead, Write},
+    path::Path,
+};
+
+use crate::parser::extract_flash_algo;
+
+/// Prepare a target config based on an ELF file containing a flash algorithm.
+pub fn cmd_elf(
+    file: &Path,
+    fixed_load_address: bool,
+    output: Option<&Path>,
+    update: bool,
+    name: Option<String>,
+) -> Result<()> {
+    let elf_file = File::open(file)?;
+
+    let mut algorithm = extract_flash_algo(elf_file, file, true, fixed_load_address)?;
+
+    if let Some(name) = name {
+        algorithm.name = name;
+    }
+
+    if update {
+        // Update an existing target file
+
+        let target_description_file = output.unwrap(); // Argument is checked by structopt, so we now its present.
+
+        let target_description = File::open(target_description_file).context(format!(
+            "Unable to open target specification '{}'",
+            target_description_file.display()
+        ))?;
+
+        let mut family: ChipFamily = serde_yaml::from_reader(target_description)?;
+
+        let algorithm_to_update = family
+            .flash_algorithms
+            .iter()
+            .position(|old_algorithm| old_algorithm.name == algorithm.name);
+
+        match algorithm_to_update {
+            None => bail!("Unable to update flash algorithm in target description file '{}'. Did not find an existing algorithm with name '{}'", target_description_file.display(), &algorithm.name),
+            Some(index) => {
+                let current = &family.flash_algorithms[index];
+
+                // if a load address was specified, use it in the replacement
+                if let Some(load_addr)  = current.load_address {
+                    algorithm.load_address = Some(load_addr);
+                    algorithm.data_section_offset = algorithm.data_section_offset.saturating_sub(load_addr);
+                }
+                // core access cannot be determined, use the current value
+                algorithm.cores = current.cores.clone();
+                algorithm.description = current.description.clone();
+
+                family.flash_algorithms[index] = algorithm
+            },
+        }
+
+        let target_description = File::create(target_description_file)?;
+        serialize_to_yaml_file(&family, &target_description)?;
+    } else {
+        // Create a complete target specification, with place holder values
+        let algorithm_name = algorithm.name.clone();
+        algorithm.cores = vec!["main".to_owned()];
+
+        let chip_family = ChipFamily {
+            name: "<family name>".to_owned(),
+            manufacturer: None,
+            generated_from_pack: false,
+            pack_file_release: None,
+            variants: vec![Chip {
+                cores: vec![Core {
+                    name: "main".to_owned(),
+                    core_type: CoreType::Armv6m,
+                    core_access_options: CoreAccessOptions::Arm(ArmCoreAccessOptions {
+                        ap: 0,
+                        psel: 0,
+                        debug_base: None,
+                        cti_base: None,
+                    }),
+                }],
+                part: None,
+                name: "<chip name>".to_owned(),
+                memory_map: vec![
+                    MemoryRegion::Nvm(NvmRegion {
+                        is_boot_memory: false,
+                        range: 0..0x2000,
+                        cores: vec!["main".to_owned()],
+                        name: None,
+                    }),
+                    MemoryRegion::Ram(RamRegion {
+                        is_boot_memory: true,
+                        range: 0x1_0000..0x2_0000,
+                        cores: vec!["main".to_owned()],
+                        name: None,
+                    }),
+                ],
+                flash_algorithms: vec![algorithm_name],
+            }],
+            flash_algorithms: vec![algorithm],
+            source: BuiltIn,
+        };
+
+        match output {
+            Some(output) => {
+                // Ensure we don't overwrite an existing file
+                let file = OpenOptions::new()
+                    .write(true)
+                    .create_new(true)
+                    .open(output)
+                    .context(format!(
+                        "Failed to create target file '{}'.",
+                        output.display()
+                    ))?;
+                serialize_to_yaml_file(&chip_family, &file)?;
+            }
+            None => println!("{}", serde_yaml::to_string(&chip_family)?),
+        }
+    }
+
+    Ok(())
+}
+
+/// Some optimizations to improve the readability of the `serde_yaml` output:
+/// - If `Option<T>` is `None`, it is serialized as `null` ... we want to omit it.
+/// - If `Vec<T>` is empty, it is serialized as `[]` ... we want to omit it.
+/// - `serde_yaml` serializes hex formatted integers as single quoted strings, e.g. '0x1234' ... we need to remove the single quotes so that it round-trips properly.
+pub fn serialize_to_yaml_file(family: &ChipFamily, file: &File) -> Result<(), anyhow::Error> {
+    let yaml_string = serde_yaml::to_string(&family)?;
+    let mut reader = std::io::BufReader::new(yaml_string.as_bytes());
+    let mut reader_line = String::new();
+    let mut writer = std::io::BufWriter::new(file);
+    while reader.read_line(&mut reader_line)? > 0 {
+        if reader_line.ends_with(": null\n")
+            || reader_line.ends_with(": []\n")
+            || reader_line.ends_with(": false\n")
+        {
+            // Skip the line
+        } else if (reader_line.contains("'0x") || reader_line.contains("'0X"))
+            && reader_line.ends_with("'\n")
+        {
+            // Remove the single quotes
+            reader_line = reader_line.replace('\'', "");
+            writer.write_all(reader_line.as_bytes())?;
+        } else {
+            writer.write_all(reader_line.as_bytes())?;
+        }
+        reader_line.clear();
+    }
+    Ok(())
+}
diff --git a/target-gen/src/main.rs b/target-gen/src/main.rs
index 4c2cb213..81a4152b 100644
--- a/target-gen/src/main.rs
+++ b/target-gen/src/main.rs
@@ -1,28 +1,25 @@
 pub mod algorithm_binary;
+pub mod commands;
 pub mod fetch;
 pub mod flash_device;
 pub mod generate;
 pub mod parser;
 
-use anyhow::{bail, ensure, Context, Result};
+use anyhow::{ensure, Context, Result};
 use clap::Parser;
-use parser::extract_flash_algo;
-use probe_rs::{
-    config::{
-        Chip, ChipFamily, Core, MemoryRegion, NvmRegion, RamRegion,
-        TargetDescriptionSource::BuiltIn,
-    },
-    CoreType,
-};
-use probe_rs_target::{ArmCoreAccessOptions, CoreAccessOptions};
+use probe_rs::config::ChipFamily;
 use std::{
     env::current_dir,
-    fs::{create_dir, File, OpenOptions},
-    io::{BufRead, Write},
+    fs::create_dir,
     path::{Path, PathBuf},
 };
 use tracing_subscriber::EnvFilter;
 
+use crate::commands::{
+    elf::{cmd_elf, serialize_to_yaml_file},
+    test::cmd_test,
+};
+
 #[derive(clap::Parser)]
 enum TargetGen {
     /// Generate target description from ARM CMSIS-Packs
@@ -86,6 +83,22 @@ enum TargetGen {
         #[clap(value_parser)]
         output: Option<PathBuf>,
     },
+    /// Generates a target yaml from a flash algorithm Rust project.
+    ///
+    /// Extracts parameters and functions from the ELF, generates the target yaml file
+    /// and runs the flash algorithm on the given attached target.
+    ///
+    /// This can be used as a cargo runner.
+    Test {
+        /// The path of the template YAML definition file.
+        /// This file plus the information of the ELF will be merged
+        /// and stored into the `definition_export_path` file.
+        template_path: PathBuf,
+        /// The path of the completed YAML definition file.
+        definition_export_path: PathBuf,
+        /// The path to the ELF.
+        target_artifact: PathBuf,
+    },
 }
 
 fn main() -> Result<()> {
@@ -106,12 +119,27 @@ fn main() -> Result<()> {
             update,
             name,
             fixed_load_address,
-        } => cmd_elf(elf, fixed_load_address, output, update, name)?,
+        } => cmd_elf(
+            elf.as_path(),
+            fixed_load_address,
+            output.as_deref(),
+            update,
+            name,
+        )?,
         TargetGen::Arm {
             output_dir,
             pack_filter: chip_family,
             list,
         } => cmd_arm(output_dir, chip_family, list)?,
+        TargetGen::Test {
+            target_artifact,
+            template_path,
+            definition_export_path,
+        } => cmd_test(
+            target_artifact.as_path(),
+            template_path.as_path(),
+            definition_export_path.as_path(),
+        )?,
     }
 
     println!("Finished in {:?}", t.elapsed());
@@ -119,122 +147,6 @@ fn main() -> Result<()> {
     Ok(())
 }
 
-/// Prepare a target config based on an ELF file containing a flash algorithm.
-fn cmd_elf(
-    file: PathBuf,
-    fixed_load_address: bool,
-    output: Option<PathBuf>,
-    update: bool,
-    name: Option<String>,
-) -> Result<()> {
-    let elf_file = File::open(&file)?;
-
-    let mut algorithm = extract_flash_algo(elf_file, &file, true, fixed_load_address)?;
-
-    if let Some(name) = name {
-        algorithm.name = name;
-    }
-
-    if update {
-        // Update an existing target file
-
-        let target_description_file = output.unwrap(); // Argument is checked by structopt, so we now its present.
-
-        let target_description = File::open(&target_description_file).context(format!(
-            "Unable to open target specification '{}'",
-            target_description_file.display()
-        ))?;
-
-        let mut family: ChipFamily = serde_yaml::from_reader(target_description)?;
-
-        let algorithm_to_update = family
-            .flash_algorithms
-            .iter()
-            .position(|old_algorithm| old_algorithm.name == algorithm.name);
-
-        match algorithm_to_update {
-            None => bail!("Unable to update flash algorithm in target description file '{}'. Did not find an existing algorithm with name '{}'", target_description_file.display(), &algorithm.name),
-            Some(index) => {
-                let current = &family.flash_algorithms[index];
-
-                // if a load address was specified, use it in the replacement
-                if let Some(load_addr)  = current.load_address {
-                    algorithm.load_address = Some(load_addr);
-                    algorithm.data_section_offset = algorithm.data_section_offset.saturating_sub(load_addr);
-                }
-                // core access cannot be determined, use the current value
-                algorithm.cores = current.cores.clone();
-                algorithm.description = current.description.clone();
-
-                family.flash_algorithms[index] = algorithm
-            },
-        }
-
-        let target_description = File::create(&target_description_file)?;
-        serialize_to_yaml_file(&family, &target_description)?;
-    } else {
-        // Create a complete target specification, with place holder values
-        let algorithm_name = algorithm.name.clone();
-        algorithm.cores = vec!["main".to_owned()];
-
-        let chip_family = ChipFamily {
-            name: "<family name>".to_owned(),
-            manufacturer: None,
-            generated_from_pack: false,
-            pack_file_release: None,
-            variants: vec![Chip {
-                cores: vec![Core {
-                    name: "main".to_owned(),
-                    core_type: CoreType::Armv6m,
-                    core_access_options: CoreAccessOptions::Arm(ArmCoreAccessOptions {
-                        ap: 0,
-                        psel: 0,
-                        debug_base: None,
-                        cti_base: None,
-                    }),
-                }],
-                part: None,
-                name: "<chip name>".to_owned(),
-                memory_map: vec![
-                    MemoryRegion::Nvm(NvmRegion {
-                        is_boot_memory: false,
-                        range: 0..0x2000,
-                        cores: vec!["main".to_owned()],
-                        name: None,
-                    }),
-                    MemoryRegion::Ram(RamRegion {
-                        is_boot_memory: true,
-                        range: 0x1_0000..0x2_0000,
-                        cores: vec!["main".to_owned()],
-                        name: None,
-                    }),
-                ],
-                flash_algorithms: vec![algorithm_name],
-            }],
-            flash_algorithms: vec![algorithm],
-            source: BuiltIn,
-        };
-
-        match output {
-            Some(output) => {
-                // Ensure we don't overwrite an existing file
-                let file = OpenOptions::new()
-                    .write(true)
-                    .create_new(true)
-                    .open(&output)
-                    .context(format!(
-                        "Failed to create target file '{}'.",
-                        output.display()
-                    ))?;
-                serialize_to_yaml_file(&chip_family, &file)?;
-            }
-            None => println!("{}", serde_yaml::to_string(&chip_family)?),
-        }
-    }
-
-    Ok(())
-}
-
 /// Handle the pack subcommand. `input` is either the path
 /// to a CMSIS-Pack file, or a directory containing at least one .pdsc file.
 ///
@@ -341,32 +253,3 @@ fn cmd_arm(out_dir: Option<PathBuf>, chip_family: Option<String>, list: bool) ->
 
     Ok(())
 }
-
-/// Some optimizations to improve the readability of the `serde_yaml` output:
-/// - If `Option<T>` is `None`, it is serialized as `null` ... we want to omit it.
-/// - If `Vec<T>` is empty, it is serialized as `[]` ... we want to omit it.
-/// - `serde_yaml` serializes hex formatted integers as single quoted strings, e.g. '0x1234' ... we need to remove the single quotes so that it round-trips properly.
-fn serialize_to_yaml_file(family: &ChipFamily, file: &File) -> Result<(), anyhow::Error> {
-    let yaml_string = serde_yaml::to_string(&family)?;
-    let mut reader = std::io::BufReader::new(yaml_string.as_bytes());
-    let mut reader_line = String::new();
-    let mut writer = std::io::BufWriter::new(file);
-    while reader.read_line(&mut reader_line)? > 0 {
-        if reader_line.ends_with(": null\n")
-            || reader_line.ends_with(": []\n")
-            || reader_line.ends_with(": false\n")
-        {
-            // Skip the line
-        } else if (reader_line.contains("'0x") || reader_line.contains("'0X"))
-            && reader_line.ends_with("'\n")
-        {
-            // Remove the single quotes
-            reader_line = reader_line.replace('\'', "");
-            writer.write_all(reader_line.as_bytes())?;
-        } else {
-            writer.write_all(reader_line.as_bytes())?;
-        }
-        reader_line.clear();
-    }
-    Ok(())
-}


Test patch:
diff --git a/smoke-tester/src/tests.rs b/smoke-tester/src/tests.rs
index 9e5be3bc..656f487f 100644
--- a/smoke-tester/src/tests.rs
+++ b/smoke-tester/src/tests.rs
@@ -163,7 +163,7 @@ pub fn test_flashing(
     });
 
     let mut options = DownloadOptions::default();
-    options.progress = Some(&progress);
+    options.progress = Some(progress);
 
     println_test_status!(tracker, blue, "Starting flashing test");
     println_test_status!(tracker, blue, "Binary: {}", test_binary.display());
diff --git a/target-gen/src/commands/test.rs b/target-gen/src/commands/test.rs
new file mode 100644
index 00000000..e365c4b4
--- /dev/null
+++ b/target-gen/src/commands/test.rs
@@ -0,0 +1,218 @@
+use std::cell::RefCell;
+use std::path::Path;
+use std::rc::Rc;
+use std::time::Instant;
+
+use anyhow::Result;
+use colored::Colorize;
+use probe_rs::flashing::erase_all;
+use probe_rs::MemoryInterface;
+use probe_rs::{
+    flashing::{erase_sectors, DownloadOptions, FlashLoader, FlashProgress},
+    Permissions, Session,
+};
+use xshell::{cmd, Shell};
+
+use crate::commands::elf::cmd_elf;
+
+const ALGORITHM_NAME: &str = "algorithm-test";
+
+pub fn cmd_test(
+    target_artifact: &Path,
+    template_path: &Path,
+    definition_export_path: &Path,
+) -> Result<()> {
+    // Generate the binary
+    println!("Generating the YAML file in `{definition_export_path:?}`");
+    std::fs::copy(template_path, definition_export_path)?;
+    cmd_elf(
+        target_artifact,
+        true,
+        Some(definition_export_path),
+        true,
+        Some(String::from(ALGORITHM_NAME)),
+    )?;
+
+    if let Err(error) = generate_debug_info(target_artifact) {
+        println!("Generating debug artifacts failed because:");
+        println!("{error}");
+    }
+
+    probe_rs::config::add_target_from_yaml(definition_export_path)?;
+    let mut session =
+        probe_rs::Session::auto_attach(ALGORITHM_NAME, Permissions::new().allow_erase_all())?;
+
+    let data_size = probe_rs::config::get_target_by_name(ALGORITHM_NAME)?.flash_algorithms[0]
+        .flash_properties
+        .page_size;
+
+    // Register callback to update the progress.
+    let t = Rc::new(RefCell::new(Instant::now()));
+    let progress = FlashProgress::new(move |event| {
+        use probe_rs::flashing::ProgressEvent::*;
+        match event {
+            StartedProgramming => {
+                let mut t = t.borrow_mut();
+                *t = Instant::now();
+            }
+            StartedErasing => {
+                let mut t = t.borrow_mut();
+                *t = Instant::now();
+            }
+            FailedErasing => {
+                println!("Failed erasing in {:?}", t.borrow().elapsed());
+            }
+            FinishedErasing => {
+                println!("Finished erasing in {:?}", t.borrow().elapsed());
+            }
+            FailedProgramming => {
+                println!("Failed programming in {:?}", t.borrow().elapsed());
+            }
+            FinishedProgramming => {
+                println!("Finished programming in {:?}", t.borrow().elapsed());
+            }
+            DiagnosticMessage { message } => {
+                let prefix = "Message".yellow();
+                if message.ends_with('\n') {
+                    print!("{prefix}: {message}");
+                } else {
+                    println!("{prefix}: {message}");
+                }
+            }
+            _ => (),
+        }
+    });
+
+    let test = "Test".green();
+    let flash_properties = session.target().flash_algorithms[0]
+        .flash_properties
+        .clone();
+    let erased_state = flash_properties.erased_byte_value;
+
+    println!("{test}: Erasing sectorwise and writing two pages ...");
+
+    run_flash_erase(&mut session, progress.clone(), false)?;
+    // TODO: The sector used here is not necessarily the sector the flash algorithm targets.
+    // Make this configurable.
+    let mut readback = vec![0; flash_properties.sectors[0].size as usize];
+    session.core(0)?.read_8(0x0, &mut readback)?;
+    assert!(
+        !readback.iter().any(|v| *v != erased_state),
+        "Not all sectors were erased"
+    );
+
+    let mut loader = session.target().flash_loader();
+    let data = (0..data_size)
+        .into_iter()
+        .map(|n| (n % 256) as u8)
+        .collect::<Vec<_>>();
+    loader.add_data(0x1, &data)?;
+    run_flash_download(&mut session, loader, progress.clone(), true)?;
+    let mut readback = vec![0; data_size as usize];
+    session.core(0)?.read_8(0x1, &mut readback)?;
+    assert_eq!(readback, data);
+
+    println!("{test}: Erasing the entire chip and writing two pages ...");
+    run_flash_erase(&mut session, progress.clone(), true)?;
+    // TODO: The sector used here is not necessarily the sector the flash algorithm targets.
+    // Make this configurable.
+    let mut readback = vec![0; flash_properties.sectors[0].size as usize];
+    session.core(0)?.read_8(0x0, &mut readback)?;
+    assert!(
+        !readback.iter().any(|v| *v != erased_state),
+        "Not all sectors were erased"
+    );
+
+    let mut loader = session.target().flash_loader();
+    let data = (0..data_size)
+        .into_iter()
+        .map(|n| (n % 256) as u8)
+        .collect::<Vec<_>>();
+    loader.add_data(0x1, &data)?;
+    run_flash_download(&mut session, loader, progress.clone(), true)?;
+    let mut readback = vec![0; data_size as usize];
+    session.core(0)?.read_8(0x1, &mut readback)?;
+    assert_eq!(readback, data);
+
+    println!("{test}: Erasing sectorwise and writing two pages double buffered ...");
+    run_flash_erase(&mut session, progress.clone(), false)?;
+    // TODO: The sector used here is not necessarily the sector the flash algorithm targets.
+    // Make this configurable.
+    let mut readback = vec![0; flash_properties.sectors[0].size as usize];
+    session.core(0)?.read_8(0x0, &mut readback)?;
+    assert!(
+        !readback.iter().any(|v| *v != erased_state),
+        "Not all sectors were erased"
+    );
+
+    let mut loader = session.target().flash_loader();
+    let data = (0..data_size)
+        .into_iter()
+        .map(|n| (n % 256) as u8)
+        .collect::<Vec<_>>();
+    loader.add_data(0x1, &data)?;
+    run_flash_download(&mut session, loader, progress, false)?;
+    let mut readback = vec![0; data_size as usize];
+    session.core(0)?.read_8(0x1, &mut readback)?;
+    assert_eq!(readback, data);
+
+    Ok(())
+}
+
+/// Performs the flash download with the given loader. Ensure that the loader has the data to load already stored.
+/// This function also manages the update and display of progress bars.
+pub fn run_flash_download(
+    session: &mut Session,
+    loader: FlashLoader,
+    progress: FlashProgress,
+    disable_double_buffering: bool,
+) -> Result<()> {
+    let mut download_option = DownloadOptions::default();
+    download_option.keep_unwritten_bytes = false;
+    download_option.disable_double_buffering = disable_double_buffering;
+
+    download_option.progress = Some(progress);
+    download_option.skip_erase = true;
+
+    loader.commit(session, download_option)?;
+
+    Ok(())
+}
+
+/// Erases the entire flash if `do_chip_erase` is true,
+/// Otherwise it erases sectors 0 and 1.
+pub fn run_flash_erase(
+    session: &mut Session,
+    progress: FlashProgress,
+    do_chip_erase: bool,
+) -> Result<()> {
+    if do_chip_erase {
+        erase_all(session, Some(progress))?;
+    } else {
+        erase_sectors(session, Some(progress), 0, 2)?;
+    }
+
+    Ok(())
+}
+
+fn generate_debug_info(target_artifact: &Path) -> Result<()> {
+    let sh = Shell::new()?;
+    std::fs::write(
+        "target/disassembly.s",
+        cmd!(sh, "rust-objdump --disassemble {target_artifact}")
+            .output()?
+            .stdout,
+    )?;
+    std::fs::write(
+        "target/dump.txt",
+        cmd!(sh, "rust-objdump -x {target_artifact}")
+            .output()?
+            .stdout,
+    )?;
+    std::fs::write(
+        "target/nm.txt",
+        cmd!(sh, "rust-nm {target_artifact} -n").output()?.stdout,
+    )?;
+
+    Ok(())
+}