Repository: probe-rs/probe-rs
Pull request #2831 (2024-09-29 12:15:50)

Unwind: Xtensa: follow frame pointer
The Xtensa enabled rustc doesn't seem to emit the debuginfo necessary for unwinding. However, we can implement an architecture-specific frame-pointer chasing to at least get a full backtrace without any more details.

This PR allows other architectures to implement similar schemes by moving the fallback unwinding method into `ExceptionInterface`.


Patch:
diff --git a/changelog/fixed-flash-algo-crash.md b/changelog/fixed-flash-algo-crash.md
new file mode 100644
index 00000000..b85e56cf
--- /dev/null
+++ b/changelog/fixed-flash-algo-crash.md
@@ -0,0 +1,1 @@
+Fixed crashes when processing flash algorithm data of devices with small RAM.
\ No newline at end of file
diff --git a/changelog/fixed-xtensa-unwind.md b/changelog/fixed-xtensa-unwind.md
new file mode 100644
index 00000000..90d0a5b2
--- /dev/null
+++ b/changelog/fixed-xtensa-unwind.md
@@ -0,0 +1,1 @@
+Improved unwinding Xtensa stack traces
\ No newline at end of file
diff --git a/probe-rs-target/src/chip_family.rs b/probe-rs-target/src/chip_family.rs
index 5545779b..db5b25ee 100644
--- a/probe-rs-target/src/chip_family.rs
+++ b/probe-rs-target/src/chip_family.rs
@@ -421,4 +421,32 @@ impl ChipFamily {
         let name = name.as_ref();
         self.flash_algorithms.iter().find(|elem| elem.name == name)
     }
+
+    /// Tries to find a [RawFlashAlgorithm] with a given name and returns it with the
+    /// core assignment fixed to the cores of the given chip.
+    pub fn get_algorithm_for_chip(
+        &self,
+        name: impl AsRef<str>,
+        chip: &Chip,
+    ) -> Option<RawFlashAlgorithm> {
+        self.get_algorithm(name).map(|algo| {
+            let mut algo_cores = if algo.cores.is_empty() {
+                chip.cores.iter().map(|core| core.name.clone()).collect()
+            } else {
+                algo.cores.clone()
+            };
+
+            // only keep cores in the algo that are also in the chip
+            algo_cores.retain(|algo_core| {
+                chip.cores
+                    .iter()
+                    .any(|chip_core| &chip_core.name == algo_core)
+            });
+
+            RawFlashAlgorithm {
+                cores: algo_cores,
+                ..algo.clone()
+            }
+        })
+    }
 }
diff --git a/probe-rs-target/src/memory.rs b/probe-rs-target/src/memory.rs
index 42c66508..19ae03b2 100644
--- a/probe-rs-target/src/memory.rs
+++ b/probe-rs-target/src/memory.rs
@@ -220,6 +220,8 @@ where
 
             region.cores = common_cores;
             region.range.end = next.range.end;
+
+            self.iter.next();
         }
 
         Some(region)
@@ -589,4 +591,82 @@ mod test {
         assert_eq!(range.start, 4);
         assert_eq!(range.end, 16);
     }
+
+    #[test]
+    fn merge_consecutive_outputs_single_region() {
+        let regions = [RamRegion {
+            name: None,
+            range: 0..4,
+            cores: vec!["core0".to_string()],
+            access: None,
+        }];
+
+        let merged_regions: Vec<RamRegion> = regions.iter().merge_consecutive().collect();
+
+        assert_eq!(
+            merged_regions,
+            vec![RamRegion {
+                name: None,
+                range: 0..4,
+                cores: vec!["core0".to_string()],
+                access: None,
+            },]
+        );
+    }
+
+    #[test]
+    fn merge_consecutive_separates_ranges_with_different_cores() {
+        let regions = vec![
+            RamRegion {
+                name: None,
+                range: 0..4,
+                cores: vec!["core0".to_string()],
+                access: None,
+            },
+            RamRegion {
+                name: None,
+                range: 4..8,
+                cores: vec!["core1".to_string()],
+                access: None,
+            },
+            RamRegion {
+                name: None,
+                range: 8..12,
+                cores: vec!["core1".to_string()],
+                access: None,
+            },
+            RamRegion {
+                name: None,
+                range: 16..20,
+                cores: vec!["core1".to_string()],
+                access: None,
+            },
+        ];
+
+        let merged_regions: Vec<RamRegion> = regions.iter().merge_consecutive().collect();
+
+        assert_eq!(
+            merged_regions,
+            vec![
+                RamRegion {
+                    name: None,
+                    range: 0..4,
+                    cores: vec!["core0".to_string()],
+                    access: None,
+                },
+                RamRegion {
+                    name: None,
+                    range: 4..12,
+                    cores: vec!["core1".to_string()],
+                    access: None,
+                },
+                RamRegion {
+                    name: None,
+                    range: 16..20,
+                    cores: vec!["core1".to_string()],
+                    access: None,
+                },
+            ]
+        );
+    }
 }
diff --git a/probe-rs/src/config/target.rs b/probe-rs/src/config/target.rs
index 1c410b5b..81a7d4a0 100644
--- a/probe-rs/src/config/target.rs
+++ b/probe-rs/src/config/target.rs
@@ -68,20 +68,13 @@ impl Target {
         let mut memory_map = chip.memory_map.clone();
         let mut flash_algorithms = Vec::new();
         for algo_name in chip.flash_algorithms.iter() {
-            let mut algo = family
-                .get_algorithm(algo_name)
+            let algo = family
+                .get_algorithm_for_chip(algo_name, chip)
                 .expect(
                     "The required flash algorithm was not found. This is a bug. Please report it.",
                 )
                 .clone();
 
-            // only keep cores in the algo that are also in the chip
-            algo.cores.retain(|algo_core| {
-                chip.cores
-                    .iter()
-                    .any(|chip_core| &chip_core.name == algo_core)
-            });
-
             // If the flash algorithm addresses a range that is not covered by any memory region,
             // we add a new memory region for it. This is usually the case for option bytes and
             // some OTP memory regions in certain CMSIS packs.
@@ -90,12 +83,15 @@ impl Target {
                 .iter()
                 .any(|region| region.address_range().intersects_range(algo_range))
             {
-                // Conjure up a memory region for the flash algorithm.
+                // HACK (doubly, even):
+                // Conjure up a memory region for the flash algorithm. This is mostly used by
+                // EEPROM regions. We probably don't want to erase these regions, so we set
+                // `is_alias`, which then causes "erase all" to skip the region.
                 memory_map.push(MemoryRegion::Nvm(NvmRegion {
                     name: None,
                     range: algo_range.clone(),
                     cores: algo.cores.clone(),
-                    is_alias: false,
+                    is_alias: true,
                     access: Some(MemoryAccess {
                         read: false,
                         write: false,
diff --git a/probe-rs/src/debug/debug_info.rs b/probe-rs/src/debug/debug_info.rs
index bf5cf891..aa55e5e8 100644
--- a/probe-rs/src/debug/debug_info.rs
+++ b/probe-rs/src/debug/debug_info.rs
@@ -172,7 +172,7 @@ impl DebugInfo {
                                 if let Some((file, directory)) =
                                     self.find_file_and_directory(unit, previous_row.file_index())
                                 {
-                                    tracing::debug!("{} - {:?}", address, previous_row.isa());
+                                    tracing::debug!("{:#010x} - {:?}", address, previous_row.isa());
                                     return Some(SourceLocation {
                                         line: previous_row.line().map(NonZeroU64::get),
                                         column: Some(previous_row.column().into()),
@@ -187,7 +187,7 @@ impl DebugInfo {
                             if let Some((file, directory)) =
                                 self.find_file_and_directory(unit, row.file_index())
                             {
-                                tracing::debug!("{} - {:?}", address, row.isa());
+                                tracing::debug!("{:#010x} - {:?}", address, row.isa());
 
                                 return Some(SourceLocation {
                                     line: row.line().map(NonZeroU64::get),
@@ -683,52 +683,32 @@ impl DebugInfo {
             // and then the unwind info associated with this row.
             let unwind_info =
                 match get_unwind_info(&mut unwind_context, &self.frame_section, frame_pc) {
-                    Ok(unwind_info) => unwind_info,
-                    Err(_) => {
-                        // For non exception frames, we cannot do stack unwinding if we do not have debug info.
-                        // However, there is one case where we can continue. When the frame registers have a valid
-                        // return address/LR value, we can use the LR value to calculate the PC for the calling frame.
-                        // The current logic will then use that PC to get the next frame's unwind info, and if that exists,
-                        // we will be able to continue unwinding.
-                        // If the calling frame has no debug info, then the unwinding will end with that frame.
-                        let callee_frame_registers = unwind_registers.clone();
-                        let mut unwound_return_address: Option<RegisterValue> = unwind_registers
-                            .get_return_address()
-                            .and_then(|lr| lr.value);
-
-                        // This will update the program counter in the `unwind_registers` with the PC value calculated from the LR value.
-                        if let Some(calling_pc) = unwind_registers.get_program_counter_mut() {
-                            if let ControlFlow::Break(error) = unwind_register(
-                                calling_pc,
-                                &callee_frame_registers,
-                                None,
-                                stack_frames
-                                    .last()
-                                    .and_then(|first_frame| first_frame.canonical_frame_address),
-                                &mut unwound_return_address,
-                                memory,
+                    Ok(unwind_info) => {
+                        tracing::trace!("UNWIND: Found unwind info for address {frame_pc:#010x}");
+                        unwind_info
+                    }
+                    Err(err) => {
+                        tracing::trace!(
+                            "UNWIND: Unable to find unwind info for address {frame_pc:#010x}: {err}"
+                        );
+                        if let ControlFlow::Break(error) = exception_handler
+                            .unwind_without_debuginfo(
+                                &mut unwind_registers,
+                                frame_pc,
+                                &stack_frames,
                                 instruction_set,
-                            ) {
+                                memory,
+                            )
+                        {
+                            if let Some(error) = error {
                                 // This is not fatal, but we cannot continue unwinding beyond the current frame.
                                 tracing::error!("{:?}", &error);
                                 if let Some(first_frame) = stack_frames.first_mut() {
                                     first_frame.function_name =
                                         format!("{} : ERROR : {error}", first_frame.function_name);
                                 };
-                                break 'unwind;
-                            }
-                            if calling_pc
-                                .value
-                                .map(|calling_pc_value| {
-                                    calling_pc_value.eq(&frame_pc_register_value)
-                                })
-                                .unwrap_or(false)
-                            {
-                                // Typically if we have to infer the PC value, it might happen that we are in
-                                // a function that has no debug info, and the code is in a tight loop (typical of exception handlers).
-                                // In such cases, we will not be able to unwind the stack beyond this frame.
-                                break 'unwind;
                             }
+                            break 'unwind;
                         }
                         continue 'unwind;
                     }
@@ -1062,6 +1042,56 @@ pub fn determine_cfa<R: gimli::ReaderOffset>(
     Ok(cfa)
 }
 
+/// Unwind the program counter for the caller frame, using the LR value from the callee frame.
+pub fn unwind_pc_without_debuginfo(
+    unwind_registers: &mut DebugRegisters,
+    frame_pc: u64,
+    stack_frames: &[StackFrame],
+    instruction_set: Option<crate::InstructionSet>,
+    memory: &mut dyn MemoryInterface,
+) -> ControlFlow<Option<DebugError>> {
+    // For non exception frames, we cannot do stack unwinding if we do not have debug info.
+    // However, there is one case where we can continue. When the frame registers have a valid
+    // return address/LR value, we can use the LR value to calculate the PC for the calling frame.
+    // The current logic will then use that PC to get the next frame's unwind info, and if that exists,
+    // we will be able to continue unwinding.
+    // If the calling frame has no debug info, then the unwinding will end with that frame.
+    let callee_frame_registers = unwind_registers.clone();
+    let mut unwound_return_address: Option<RegisterValue> = unwind_registers
+        .get_return_address()
+        .and_then(|lr| lr.value);
+
+    // This will update the program counter in the `unwind_registers` with the PC value calculated from the LR value.
+    if let Some(calling_pc) = unwind_registers.get_program_counter_mut() {
+        if let ControlFlow::Break(error) = unwind_register(
+            calling_pc,
+            &callee_frame_registers,
+            None,
+            stack_frames
+                .last()
+                .and_then(|first_frame| first_frame.canonical_frame_address),
+            &mut unwound_return_address,
+            memory,
+            instruction_set,
+        ) {
+            return ControlFlow::Break(Some(error.into()));
+        };
+
+        if calling_pc
+            .value
+            .map(|calling_pc_value| calling_pc_value == RegisterValue::from(frame_pc))
+            .unwrap_or(false)
+        {
+            // Typically if we have to infer the PC value, it might happen that we are in
+            // a function that has no debug info, and the code is in a tight loop (typical of exception handlers).
+            // In such cases, we will not be able to unwind the stack beyond this frame.
+            return ControlFlow::Break(None);
+        }
+    }
+
+    ControlFlow::Continue(())
+}
+
 /// A per_register unwind, applying register rules and updating the [`registers::DebugRegister`] value as appropriate, before returning control to the calling function.
 pub fn unwind_register(
     debug_register: &mut super::DebugRegister,
diff --git a/probe-rs/src/debug/exception_handling.rs b/probe-rs/src/debug/exception_handling.rs
index f6b21ef2..48e8be85 100644
--- a/probe-rs/src/debug/exception_handling.rs
+++ b/probe-rs/src/debug/exception_handling.rs
@@ -1,9 +1,11 @@
 //! This module (and its children) contains the implementation of the [`ExceptionInterface`] for the various ARM core
 //! variants.
 
+use std::ops::ControlFlow;
+
 use probe_rs_target::CoreType;
 
-use crate::MemoryInterface;
+use crate::{debug::unwind_pc_without_debuginfo, MemoryInterface};
 
 use super::{DebugError, DebugInfo, DebugRegisters, StackFrame};
 
@@ -16,6 +18,8 @@ pub(crate) mod armv7m;
 
 pub(crate) mod armv8m;
 
+pub(crate) mod xtensa;
+
 /// Creates a new exception interface for the [`CoreType`] at hand.
 pub fn exception_handler_for_core(core_type: CoreType) -> Box<dyn ExceptionInterface> {
     use self::{armv6m, armv7m, armv8m};
@@ -23,7 +27,8 @@ pub fn exception_handler_for_core(core_type: CoreType) -> Box<dyn ExceptionInter
         CoreType::Armv6m => Box::new(armv6m::ArmV6MExceptionHandler),
         CoreType::Armv7m | CoreType::Armv7em => Box::new(armv7m::ArmV7MExceptionHandler),
         CoreType::Armv8m => Box::new(armv8m::ArmV8MExceptionHandler),
-        CoreType::Armv7a | CoreType::Armv8a | CoreType::Riscv | CoreType::Xtensa => {
+        CoreType::Xtensa => Box::new(xtensa::XtensaExceptionHandler),
+        CoreType::Armv7a | CoreType::Armv8a | CoreType::Riscv => {
             Box::new(UnimplementedExceptionHandler)
         }
     }
@@ -120,4 +125,24 @@ pub trait ExceptionInterface {
         raw_exception: u32,
         memory: &mut dyn MemoryInterface,
     ) -> Result<String, DebugError>;
+
+    /// Unwind the stack without debug info.
+    ///
+    /// This method can be implemented to provide a stack trace using frame pointers, for example.
+    fn unwind_without_debuginfo(
+        &self,
+        unwind_registers: &mut DebugRegisters,
+        frame_pc: u64,
+        stack_frames: &[StackFrame],
+        instruction_set: Option<crate::InstructionSet>,
+        memory: &mut dyn MemoryInterface,
+    ) -> ControlFlow<Option<DebugError>> {
+        unwind_pc_without_debuginfo(
+            unwind_registers,
+            frame_pc,
+            stack_frames,
+            instruction_set,
+            memory,
+        )
+    }
 }
diff --git a/probe-rs/src/debug/exception_handling/xtensa.rs b/probe-rs/src/debug/exception_handling/xtensa.rs
new file mode 100644
index 00000000..23535d67
--- /dev/null
+++ b/probe-rs/src/debug/exception_handling/xtensa.rs
@@ -0,0 +1,106 @@
+use std::ops::ControlFlow;
+
+use crate::{
+    debug::{
+        exception_handling::{ExceptionInfo, ExceptionInterface},
+        unwind_pc_without_debuginfo, DebugError, DebugInfo, DebugRegisters, StackFrame,
+    },
+    MemoryInterface, RegisterRole, RegisterValue,
+};
+
+pub struct XtensaExceptionHandler;
+
+impl ExceptionInterface for XtensaExceptionHandler {
+    fn exception_details(
+        &self,
+        _memory: &mut dyn MemoryInterface,
+        _stackframe_registers: &DebugRegisters,
+        _debug_info: &DebugInfo,
+    ) -> Result<Option<ExceptionInfo>, DebugError> {
+        // For architectures where the exception handling has not been implemented in probe-rs,
+        // this will result in maintaining the current `unwind` behavior, i.e. unwinding will include up
+        // to the first frame that was called from an exception handler.
+        Ok(None)
+    }
+
+    fn calling_frame_registers(
+        &self,
+        _memory: &mut dyn MemoryInterface,
+        _stackframe_registers: &crate::debug::DebugRegisters,
+        _raw_exception: u32,
+    ) -> Result<crate::debug::DebugRegisters, DebugError> {
+        Err(DebugError::NotImplemented("calling frame registers"))
+    }
+
+    fn raw_exception(
+        &self,
+        _stackframe_registers: &crate::debug::DebugRegisters,
+    ) -> Result<u32, DebugError> {
+        Err(DebugError::NotImplemented("raw exception"))
+    }
+
+    fn exception_description(
+        &self,
+        _raw_exception: u32,
+        _memory: &mut dyn MemoryInterface,
+    ) -> Result<String, DebugError> {
+        Err(DebugError::NotImplemented("exception description"))
+    }
+
+    fn unwind_without_debuginfo(
+        &self,
+        unwind_registers: &mut DebugRegisters,
+        frame_pc: u64,
+        stack_frames: &[StackFrame],
+        instruction_set: Option<crate::InstructionSet>,
+        memory: &mut dyn MemoryInterface,
+    ) -> ControlFlow<Option<DebugError>> {
+        // Use the default method to unwind PC.
+        unwind_pc_without_debuginfo(
+            unwind_registers,
+            frame_pc,
+            stack_frames,
+            instruction_set,
+            memory,
+        )?;
+
+        // We can try and use FP to unwind SP and RA that allows us to continue unwinding.
+
+        // Current register values
+        let Ok(fp) = unwind_registers.get_register_value_by_role(&RegisterRole::FramePointer)
+        else {
+            // We can't unwind without FP.
+            return ControlFlow::Break(None);
+        };
+
+        let sp = unwind_registers
+            .get_register_value_by_role(&RegisterRole::StackPointer)
+            .unwrap();
+
+        if sp.abs_diff(fp) >= 1024 * 1024 {
+            // Heuristic: the stack frame is probably smaller than 1MB.
+            return ControlFlow::Continue(());
+        }
+
+        // Read PC and FP from previous stack frame's Register-Spill Area.
+        let mut stack_frame = [0; 2];
+        if let Err(e) = memory.read_32(fp - 16, &mut stack_frame) {
+            // FP points at something we can't read.
+            return ControlFlow::Break(Some(e.into()));
+        }
+
+        let [caller_ra, caller_sp] = stack_frame;
+
+        let unwound_fp = unwind_registers
+            .get_register_mut_by_role(&RegisterRole::FramePointer)
+            .unwrap();
+        unwound_fp.value = Some(RegisterValue::from(caller_sp));
+
+        let unwound_ra = unwind_registers
+            .get_register_mut_by_role(&RegisterRole::ReturnAddress)
+            .unwrap();
+        unwound_ra.value = Some(RegisterValue::from(caller_ra));
+
+        ControlFlow::Continue(())
+    }
+}
diff --git a/probe-rs/src/debug/unit_info.rs b/probe-rs/src/debug/unit_info.rs
index f6bccb66..9ee2bef0 100644
--- a/probe-rs/src/debug/unit_info.rs
+++ b/probe-rs/src/debug/unit_info.rs
@@ -67,7 +67,7 @@ impl UnitInfo {
         debug_info: &'debug_info super::DebugInfo,
         address: u64,
     ) -> Result<Vec<FunctionDie>, DebugError> {
-        tracing::trace!("Searching Function DIE for address {:#x}", address);
+        tracing::trace!("Searching Function DIE for address {:#010x}", address);
 
         let mut entries_cursor = self.unit.entries();
         while let Ok(Some((_depth, current))) = entries_cursor.next_dfs() {
@@ -85,7 +85,7 @@ impl UnitInfo {
             let inlined_functions =
                 self.find_inlined_functions(debug_info, address, current.offset())?;
             tracing::debug!(
-                "{} inlined functions for address {}",
+                "{} inlined functions for address {:#010x}",
                 inlined_functions.len(),
                 address
             );
diff --git a/probe-rs/src/flashing/error.rs b/probe-rs/src/flashing/error.rs
index f508e26b..a95dbd30 100644
--- a/probe-rs/src/flashing/error.rs
+++ b/probe-rs/src/flashing/error.rs
@@ -115,11 +115,13 @@ pub enum FlashError {
         /// The size of the stack that was tried to be configured.
         size: u64,
     },
-    /// The given page size is not valid. Only page sizes multiples of 4 bytes are allowed.
-    #[error("Invalid page size {size:#010x}. Must be a multiple of 4 bytes.")]
-    InvalidPageSize {
-        /// The size of the page in bytes.
-        size: u32,
+    /// Failed to configure the data region of a flash algorithm.
+    #[error("Failed to place data to address {data_load_addr:#010x} in RAM. The data must be placed in the range {data_ram:#x?}.")]
+    InvalidDataAddress {
+        /// The address where the data was supposed to be loaded to.
+        data_load_addr: u64,
+        /// The range of the data memory.
+        data_ram: Range<u64>,
     },
     // TODO: Warn at YAML parsing stage.
     // TODO: 1 Add information about flash (name, address)
@@ -150,7 +152,7 @@ pub enum FlashError {
     // TODO: 1 Add source of target definition
     // TOOD: 2 Do this at target load time.
     /// The given chip has no RAM defined.
-    #[error("No RAM defined for target: {name}.")]
+    #[error("No suitable RAM region is defined for target: {name}.")]
     NoRamDefined {
         /// The name of the chip.
         name: String,
@@ -179,7 +181,7 @@ pub enum FlashError {
     #[error("No core can access the NVM region {0:?}.")]
     NoNvmCoreAccess(NvmRegion),
     /// No core can access this RAM region.
-    #[error("No core can access the ram region {0:?}.")]
+    #[error("No core can access the RAM region {0:?}.")]
     NoRamCoreAccess(RamRegion),
     /// The register value supplied for this flash algorithm is out of the supported range.
     #[error("The register value {0:#010x} is out of the supported range.")]
diff --git a/probe-rs/src/flashing/flash_algorithm.rs b/probe-rs/src/flashing/flash_algorithm.rs
index a396a547..06cb6601 100644
--- a/probe-rs/src/flashing/flash_algorithm.rs
+++ b/probe-rs/src/flashing/flash_algorithm.rs
@@ -225,13 +225,6 @@ impl FlashAlgorithm {
     ) -> Result<Self, FlashError> {
         use std::mem::size_of;
 
-        if raw.flash_properties.page_size % 4 != 0 {
-            // TODO move to yaml validation
-            return Err(FlashError::InvalidPageSize {
-                size: raw.flash_properties.page_size,
-            });
-        }
-
         let assembled_instructions = raw.instructions.chunks_exact(size_of::<u32>());
 
         let remainder = assembled_instructions.remainder();
@@ -312,9 +305,18 @@ impl FlashAlgorithm {
         };
 
         // Available memory for data depends on where the stack needs to be placed.
+        if data_ram_region.range.end < data_load_addr {
+            return Err(FlashError::InvalidDataAddress {
+                data_load_addr,
+                data_ram: data_ram_region.range.clone(),
+            });
+        }
         let mut ram_for_data = data_ram_region.range.end - data_load_addr;
         if code_end + stack_size > data_load_addr && ram_region == data_ram_region {
             // Stack can only go after the data, so let's reduce the available size.
+            if stack_size > ram_for_data {
+                return Err(FlashError::InvalidFlashAlgorithmStackSize { size: stack_size });
+            }
             ram_for_data -= stack_size;
         }
 
diff --git a/probe-rs/src/flashing/loader.rs b/probe-rs/src/flashing/loader.rs
index 4ed3c79e..d1758053 100644
--- a/probe-rs/src/flashing/loader.rs
+++ b/probe-rs/src/flashing/loader.rs
@@ -636,10 +636,6 @@ impl FlashLoader {
             .iter()
             .filter_map(MemoryRegion::as_nvm_region)
         {
-            if region.is_alias {
-                tracing::debug!("Skipping alias memory region {:#010X?}", region.range);
-                continue;
-            }
             tracing::debug!(
                 "    region: {:#010X?} ({} bytes)",
                 region.range,
@@ -648,6 +644,8 @@ impl FlashLoader {
 
             // If we have no data in this region, ignore it.
             // This avoids uselessly initializing and deinitializing its flash algorithm.
+            // We do not check for alias regions here, as we'll work with them if data explicitly
+            // targets them.
             if !self.builder.has_data_in_range(&region.range) {
                 tracing::debug!("     -- empty, ignoring!");
                 continue;
diff --git a/probe-rs/targets/MSPM0L_Series.yaml b/probe-rs/targets/MSPM0L_Series.yaml
index b669a013..1ad5b245 100644
--- a/probe-rs/targets/MSPM0L_Series.yaml
+++ b/probe-rs/targets/MSPM0L_Series.yaml
@@ -124,7 +124,6 @@ variants:
     - main
   flash_algorithms:
   - mspm0l_main_16kb
-  - mspm0l_nonmain
 - name: MSPM0L1304
   cores:
   - name: main
@@ -165,7 +164,6 @@ variants:
     - main
   flash_algorithms:
   - mspm0l_main_16kb
-  - mspm0l_nonmain
 - name: MSPM0L1305
   cores:
   - name: main
@@ -288,7 +286,6 @@ variants:
     - main
   flash_algorithms:
   - mspm0l_main_16kb
-  - mspm0l_nonmain
 - name: MSPM0L1344
   cores:
   - name: main
@@ -329,7 +326,6 @@ variants:
     - main
   flash_algorithms:
   - mspm0l_main_16kb
-  - mspm0l_nonmain
 - name: MSPM0L1345
   cores:
   - name: main
diff --git a/probe-rs/targets/STM32WBA_Series.yaml b/probe-rs/targets/STM32WBA_Series.yaml
index a63fd644..804eb03e 100644
--- a/probe-rs/targets/STM32WBA_Series.yaml
+++ b/probe-rs/targets/STM32WBA_Series.yaml
@@ -32,7 +32,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA50KGUx
   cores:
   - name: main
@@ -60,7 +60,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA52CEUx
   cores:
   - name: main
@@ -88,7 +88,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA52CGUx
   cores:
   - name: main
@@ -116,7 +116,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA52KEUx
   cores:
   - name: main
@@ -144,7 +144,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA52KGUx
   cores:
   - name: main
@@ -172,7 +172,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA54CEUx
   cores:
   - name: main
@@ -200,7 +200,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA54CGUx
   cores:
   - name: main
@@ -228,7 +228,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA54KEUx
   cores:
   - name: main
@@ -256,7 +256,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA54KGUx
   cores:
   - name: main
@@ -284,7 +284,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA55CEUx
   cores:
   - name: main
@@ -312,7 +312,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA55CGUx
   cores:
   - name: main
@@ -340,7 +340,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA55HEFx
   cores:
   - name: main
@@ -368,7 +368,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA55HGFx
   cores:
   - name: main
@@ -396,7 +396,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA55UEIx
   cores:
   - name: main
@@ -424,7 +424,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 - name: STM32WBA55UGIx
   cores:
   - name: main
@@ -452,7 +452,7 @@ variants:
     - main
   flash_algorithms:
   - stm32wbax_1m_0800
-  - stm32wbax_1m_0c00
+  #- stm32wbax_1m_0c00
 flash_algorithms:
 - name: stm32wbax_1m_0800
   description: STM32WBAxx 1M NSecure Flash
diff --git a/probe-rs/targets/STM32WL_Series.yaml b/probe-rs/targets/STM32WL_Series.yaml
index 1b00e2a4..d25aba43 100644
--- a/probe-rs/targets/STM32WL_Series.yaml
+++ b/probe-rs/targets/STM32WL_Series.yaml
@@ -253,7 +253,7 @@ variants:
     cores:
     - main
   flash_algorithms:
-  - stm32wlexx_128
+  #- stm32wlexx_128
 - name: STM32WLE4CBUx
   cores:
   - name: main
@@ -355,7 +355,7 @@ variants:
     cores:
     - main
   flash_algorithms:
-  - stm32wlexx_128
+  #- stm32wlexx_128
 - name: STM32WLE4JBIx
   cores:
   - name: main
@@ -457,7 +457,7 @@ variants:
     cores:
     - main
   flash_algorithms:
-  - stm32wlexx_128
+  #- stm32wlexx_128
 - name: STM32WLE5CBUx
   cores:
   - name: main
@@ -559,7 +559,7 @@ variants:
     cores:
     - main
   flash_algorithms:
-  - stm32wlexx_128
+  #- stm32wlexx_128
 - name: STM32WLE5JBIx
   cores:
   - name: main


Test patch:
diff --git a/smoke-tester/src/tests.rs b/smoke-tester/src/tests.rs
index 9d1184ac..e0f476f6 100644
--- a/smoke-tester/src/tests.rs
+++ b/smoke-tester/src/tests.rs
@@ -162,6 +162,7 @@ fn test_hw_breakpoints(tracker: &TestTracker, core: &mut Core) -> TestResult {
     let memory_regions: Vec<_> = core
         .memory_regions()
         .filter_map(MemoryRegion::as_nvm_region)
+        .filter(|r| r.is_executable())
         .cloned()
         .collect();