Repository: probe-rs/probe-rs
Pull request #3220 (2025-04-09 11:10:19)

Correctly resolve values that come from a register but aren't addresses


Patch:
diff --git a/changelog/fixed-xtensa-exceptions.md b/changelog/fixed-xtensa-exceptions.md
new file mode 100644
index 00000000..101f45ff
--- /dev/null
+++ b/changelog/fixed-xtensa-exceptions.md
@@ -0,0 +1,1 @@
+Xtensa exceptions should no longer cause the debugger to crash.
\ No newline at end of file
diff --git a/probe-rs-debug/src/function_die.rs b/probe-rs-debug/src/function_die.rs
index 3b510564..fc25412d 100644
--- a/probe-rs-debug/src/function_die.rs
+++ b/probe-rs-debug/src/function_die.rs
@@ -234,6 +234,9 @@ impl<'a> FunctionDie<'a> {
             frame_info,
         )? {
             ExpressionResult::Location(VariableLocation::Address(address)) => Ok(Some(address)),
+            ExpressionResult::Location(VariableLocation::RegisterValue(value)) => {
+                Ok(value.try_into().ok())
+            }
             _ => Ok(None),
         }
     }
diff --git a/probe-rs-debug/src/language/c.rs b/probe-rs-debug/src/language/c.rs
index 14260e72..9b1e2da7 100644
--- a/probe-rs-debug/src/language/c.rs
+++ b/probe-rs-debug/src/language/c.rs
@@ -181,11 +181,7 @@ impl Value for CChar {
     where
         Self: Sized,
     {
-        let mut buff = 0u8;
-        memory.read(
-            variable.memory_location.memory_address()?,
-            std::slice::from_mut(&mut buff),
-        )?;
+        let buff = u8::get_value(variable, memory, _variable_cache)?;
 
         Ok(Self(buff))
     }
@@ -237,13 +233,22 @@ impl UnsignedInt {
     where
         Self: Sized,
     {
-        // Read the bits
+        // Read the bits. The actual count is encoded in the variable type.
         let mut buff = [0u8; 16];
         let bytes = variable.byte_size.unwrap_or(1).min(16) as usize;
-        memory.read(
-            variable.memory_location.memory_address()?,
-            &mut buff[..bytes],
-        )?;
+        if let VariableLocation::RegisterValue(value) = variable.memory_location {
+            // The value is in a register, we just need to extract the bytes.
+            let reg_bytes = TryInto::<u128>::try_into(value)?.to_le_bytes();
+
+            buff[..bytes].copy_from_slice(&reg_bytes[..bytes]);
+        } else {
+            // We only have an address, we need to read the value from memory.
+            memory.read(
+                variable.memory_location.memory_address()?,
+                &mut buff[..bytes],
+            )?;
+        }
+
         let value = u128::from_le_bytes(buff);
 
         // Extract bitfield bits
diff --git a/probe-rs-debug/src/language/parsing.rs b/probe-rs-debug/src/language/parsing.rs
index 2816b793..49d3df20 100644
--- a/probe-rs-debug/src/language/parsing.rs
+++ b/probe-rs-debug/src/language/parsing.rs
@@ -1,15 +1,24 @@
-use crate::DebugError;
+use probe_rs::MemoryInterface;
+
+use crate::{DebugError, Variable, VariableLocation};
 
 /// Extension methods to simply parse a value into a number of bytes.
-pub trait ParseToBytes {
+pub trait ValueExt: Sized {
     type Out;
 
+    /// Parse the value from a string into a number of bytes.
     fn parse_to_bytes(s: &str) -> Result<Self::Out, DebugError>;
+
+    /// Read the value from a [`VariableLocation`].
+    fn read_from_location(
+        variable: &crate::Variable,
+        memory: &mut dyn crate::MemoryInterface,
+    ) -> Result<Self, DebugError>;
 }
 
-macro_rules! impl_parse {
+macro_rules! impl_extensions {
     ($t:ty, $bytes:expr) => {
-        impl ParseToBytes for $t {
+        impl ValueExt for $t {
             type Out = [u8; $bytes];
 
             fn parse_to_bytes(s: &str) -> Result<Self::Out, DebugError> {
@@ -20,20 +29,38 @@ macro_rules! impl_parse {
                     }),
                 }
             }
+
+            fn read_from_location(
+                variable: &Variable,
+                memory: &mut dyn MemoryInterface,
+            ) -> Result<Self, DebugError> {
+                let mut buff: Self::Out = [0u8; $bytes];
+                if let VariableLocation::RegisterValue(value) = variable.memory_location {
+                    // The value is in a register, we just need to extract the bytes.
+                    let reg_bytes = TryInto::<u128>::try_into(value)?.to_le_bytes();
+
+                    buff.copy_from_slice(&reg_bytes[..$bytes]);
+                } else {
+                    // We only have an address, we need to read the value from memory.
+                    memory.read(variable.memory_location.memory_address()?, &mut buff)?;
+                }
+
+                Ok(<$t>::from_le_bytes(buff))
+            }
         }
     };
 }
 
-impl_parse!(u8, 1);
-impl_parse!(i8, 1);
-impl_parse!(u16, 2);
-impl_parse!(i16, 2);
-impl_parse!(u32, 4);
-impl_parse!(i32, 4);
-impl_parse!(u64, 8);
-impl_parse!(i64, 8);
-impl_parse!(u128, 16);
-impl_parse!(i128, 16);
-
-impl_parse!(f32, 4);
-impl_parse!(f64, 8);
+impl_extensions!(u8, 1);
+impl_extensions!(i8, 1);
+impl_extensions!(u16, 2);
+impl_extensions!(i16, 2);
+impl_extensions!(u32, 4);
+impl_extensions!(i32, 4);
+impl_extensions!(u64, 8);
+impl_extensions!(i64, 8);
+impl_extensions!(u128, 16);
+impl_extensions!(i128, 16);
+
+impl_extensions!(f32, 4);
+impl_extensions!(f64, 8);
diff --git a/probe-rs-debug/src/language/value.rs b/probe-rs-debug/src/language/value.rs
index df31cfdf..70cbc152 100644
--- a/probe-rs-debug/src/language/value.rs
+++ b/probe-rs-debug/src/language/value.rs
@@ -1,8 +1,8 @@
 use std::str::FromStr;
 
 use crate::{
-    DebugError, Variable, VariableCache, VariableName, VariableValue,
-    language::parsing::ParseToBytes,
+    DebugError, Variable, VariableCache, VariableLocation, VariableName, VariableValue,
+    language::parsing::ValueExt,
 };
 
 use probe_rs::MemoryInterface;
@@ -56,6 +56,12 @@ impl Value for bool {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         memory
             .write_word_8(
                 variable.memory_location.memory_address()?,
@@ -91,6 +97,12 @@ impl Value for char {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         memory
             .write_word_32(
                 variable.memory_location.memory_address()?,
@@ -181,16 +193,14 @@ impl Value for String {
         Err(DebugError::WarnAndContinue { message:"Unsupported datatype: \"String\". Please only update variables with a base data type.".to_string()})
     }
 }
+
 impl Value for i8 {
     fn get_value(
         variable: &Variable,
         memory: &mut dyn MemoryInterface,
         _variable_cache: &VariableCache,
     ) -> Result<Self, DebugError> {
-        let mut buff = [0u8; 1];
-        memory.read(variable.memory_location.memory_address()?, &mut buff)?;
-        let ret_value = i8::from_le_bytes(buff);
-        Ok(ret_value)
+        Self::read_from_location(variable, memory)
     }
 
     fn update_value(
@@ -198,6 +208,12 @@ impl Value for i8 {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         let buff = i8::parse_to_bytes(new_value)?;
         memory
             .write_word_8(variable.memory_location.memory_address()?, buff[0])
@@ -212,10 +228,7 @@ impl Value for i16 {
         memory: &mut dyn MemoryInterface,
         _variable_cache: &VariableCache,
     ) -> Result<Self, DebugError> {
-        let mut buff = [0u8; 2];
-        memory.read(variable.memory_location.memory_address()?, &mut buff)?;
-        let ret_value = i16::from_le_bytes(buff);
-        Ok(ret_value)
+        Self::read_from_location(variable, memory)
     }
 
     fn update_value(
@@ -223,6 +236,12 @@ impl Value for i16 {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         let buff = i16::parse_to_bytes(new_value)?;
         memory
             .write_8(variable.memory_location.memory_address()?, &buff)
@@ -237,10 +256,7 @@ impl Value for i32 {
         memory: &mut dyn MemoryInterface,
         _variable_cache: &VariableCache,
     ) -> Result<Self, DebugError> {
-        let mut buff = [0u8; 4];
-        memory.read(variable.memory_location.memory_address()?, &mut buff)?;
-        let ret_value = i32::from_le_bytes(buff);
-        Ok(ret_value)
+        Self::read_from_location(variable, memory)
     }
 
     fn update_value(
@@ -248,6 +264,12 @@ impl Value for i32 {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         let buff = i32::parse_to_bytes(new_value)?;
         memory
             .write_8(variable.memory_location.memory_address()?, &buff)
@@ -262,10 +284,7 @@ impl Value for i64 {
         memory: &mut dyn MemoryInterface,
         _variable_cache: &VariableCache,
     ) -> Result<Self, DebugError> {
-        let mut buff = [0u8; 8];
-        memory.read(variable.memory_location.memory_address()?, &mut buff)?;
-        let ret_value = i64::from_le_bytes(buff);
-        Ok(ret_value)
+        Self::read_from_location(variable, memory)
     }
 
     fn update_value(
@@ -273,6 +292,12 @@ impl Value for i64 {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         let buff = i64::parse_to_bytes(new_value)?;
         memory
             .write_8(variable.memory_location.memory_address()?, &buff)
@@ -287,10 +312,7 @@ impl Value for i128 {
         memory: &mut dyn MemoryInterface,
         _variable_cache: &VariableCache,
     ) -> Result<Self, DebugError> {
-        let mut buff = [0u8; 16];
-        memory.read(variable.memory_location.memory_address()?, &mut buff)?;
-        let ret_value = i128::from_le_bytes(buff);
-        Ok(ret_value)
+        Self::read_from_location(variable, memory)
     }
 
     fn update_value(
@@ -298,6 +320,12 @@ impl Value for i128 {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         let buff = i128::parse_to_bytes(new_value)?;
         memory
             .write_8(variable.memory_location.memory_address()?, &buff)
@@ -312,10 +340,7 @@ impl Value for u8 {
         memory: &mut dyn MemoryInterface,
         _variable_cache: &VariableCache,
     ) -> Result<Self, DebugError> {
-        let mut buff = [0u8; 1];
-        memory.read(variable.memory_location.memory_address()?, &mut buff)?;
-        let ret_value = u8::from_le_bytes(buff);
-        Ok(ret_value)
+        Self::read_from_location(variable, memory)
     }
 
     fn update_value(
@@ -323,6 +348,12 @@ impl Value for u8 {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         let buff = u8::parse_to_bytes(new_value)?;
         memory
             .write_word_8(variable.memory_location.memory_address()?, buff[0])
@@ -337,10 +368,7 @@ impl Value for u16 {
         memory: &mut dyn MemoryInterface,
         _variable_cache: &VariableCache,
     ) -> Result<Self, DebugError> {
-        let mut buff = [0u8; 2];
-        memory.read(variable.memory_location.memory_address()?, &mut buff)?;
-        let ret_value = u16::from_le_bytes(buff);
-        Ok(ret_value)
+        Self::read_from_location(variable, memory)
     }
 
     fn update_value(
@@ -348,6 +376,12 @@ impl Value for u16 {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         let buff = u16::parse_to_bytes(new_value)?;
         memory
             .write_8(variable.memory_location.memory_address()?, &buff)
@@ -362,10 +396,7 @@ impl Value for u32 {
         memory: &mut dyn MemoryInterface,
         _variable_cache: &VariableCache,
     ) -> Result<Self, DebugError> {
-        let mut buff = [0u8; 4];
-        memory.read(variable.memory_location.memory_address()?, &mut buff)?;
-        let ret_value = u32::from_le_bytes(buff);
-        Ok(ret_value)
+        Self::read_from_location(variable, memory)
     }
 
     fn update_value(
@@ -373,6 +404,12 @@ impl Value for u32 {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         let buff = u32::parse_to_bytes(new_value)?;
         memory
             .write_8(variable.memory_location.memory_address()?, &buff)
@@ -387,10 +424,7 @@ impl Value for u64 {
         memory: &mut dyn MemoryInterface,
         _variable_cache: &VariableCache,
     ) -> Result<Self, DebugError> {
-        let mut buff = [0u8; 8];
-        memory.read(variable.memory_location.memory_address()?, &mut buff)?;
-        let ret_value = u64::from_le_bytes(buff);
-        Ok(ret_value)
+        Self::read_from_location(variable, memory)
     }
 
     fn update_value(
@@ -398,6 +432,12 @@ impl Value for u64 {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         let buff = u64::parse_to_bytes(new_value)?;
         memory
             .write_8(variable.memory_location.memory_address()?, &buff)
@@ -412,10 +452,7 @@ impl Value for u128 {
         memory: &mut dyn MemoryInterface,
         _variable_cache: &VariableCache,
     ) -> Result<Self, DebugError> {
-        let mut buff = [0u8; 16];
-        memory.read(variable.memory_location.memory_address()?, &mut buff)?;
-        let ret_value = u128::from_le_bytes(buff);
-        Ok(ret_value)
+        Self::read_from_location(variable, memory)
     }
 
     fn update_value(
@@ -423,6 +460,12 @@ impl Value for u128 {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         let buff = u128::parse_to_bytes(new_value)?;
         memory
             .write_8(variable.memory_location.memory_address()?, &buff)
@@ -437,10 +480,7 @@ impl Value for f32 {
         memory: &mut dyn MemoryInterface,
         _variable_cache: &VariableCache,
     ) -> Result<Self, DebugError> {
-        let mut buff = [0u8; 4];
-        memory.read(variable.memory_location.memory_address()?, &mut buff)?;
-        let ret_value = f32::from_le_bytes(buff);
-        Ok(ret_value)
+        Self::read_from_location(variable, memory)
     }
 
     fn update_value(
@@ -448,6 +488,12 @@ impl Value for f32 {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         let buff = f32::parse_to_bytes(new_value)?;
         memory
             .write_8(variable.memory_location.memory_address()?, &buff)
@@ -462,10 +508,7 @@ impl Value for f64 {
         memory: &mut dyn MemoryInterface,
         _variable_cache: &VariableCache,
     ) -> Result<Self, DebugError> {
-        let mut buff = [0u8; 8];
-        memory.read(variable.memory_location.memory_address()?, &mut buff)?;
-        let ret_value = f64::from_le_bytes(buff);
-        Ok(ret_value)
+        Self::read_from_location(variable, memory)
     }
 
     fn update_value(
@@ -473,6 +516,12 @@ impl Value for f64 {
         memory: &mut dyn MemoryInterface,
         new_value: &str,
     ) -> Result<(), DebugError> {
+        if let VariableLocation::RegisterValue(_) = variable.memory_location {
+            return Err(DebugError::NotImplemented(
+                "Updating a register value is not implemented",
+            ));
+        }
+
         let buff = f64::parse_to_bytes(new_value)?;
         memory
             .write_8(variable.memory_location.memory_address()?, &buff)
diff --git a/probe-rs-debug/src/unit_info.rs b/probe-rs-debug/src/unit_info.rs
index 63c39403..e6cc2594 100644
--- a/probe-rs-debug/src/unit_info.rs
+++ b/probe-rs-debug/src/unit_info.rs
@@ -1625,17 +1625,14 @@ impl UnitInfo {
                     child_variable.memory_location = VariableLocation::Value;
                     child_variable.set_value(value_from_expression);
                 }
-
                 ExpressionResult::Value(value_from_expression) => {
                     child_variable.set_value(value_from_expression);
                 }
-
                 ExpressionResult::Location(VariableLocation::Unavailable) => {
                     child_variable.set_value(VariableValue::Error(
                         "<value optimized away by compiler, out of scope, or dropped>".to_string(),
                     ));
                 }
-
                 ExpressionResult::Location(
                     ref location @ VariableLocation::Error(ref error_message)
                     | ref location @ VariableLocation::Unsupported(ref error_message),
@@ -1643,7 +1640,6 @@ impl UnitInfo {
                     child_variable.set_value(VariableValue::Error(error_message.clone()));
                     child_variable.memory_location = location.clone();
                 }
-
                 ExpressionResult::Location(location_from_expression) => {
                     child_variable.memory_location = location_from_expression;
                 }
@@ -1891,17 +1887,12 @@ impl UnitInfo {
                 ExpressionResult::Value(VariableValue::Valid(value))
             }
             Location::Register { register } => {
-                if let Some(address) = frame_info
+                if let Some(value) = frame_info
                     .registers
                     .get_register_by_dwarf_id(register.0)
                     .and_then(|register| register.value)
                 {
-                    match address.try_into() {
-                        Ok(address) => evaluate_address(address, memory),
-                        Err(error) => ExpressionResult::Location(VariableLocation::Error(format!(
-                            "Error: Cannot convert register value to location address: {error:?}"
-                        ))),
-                    }
+                    ExpressionResult::Location(VariableLocation::RegisterValue(value))
                 } else {
                     ExpressionResult::Location(VariableLocation::Error(format!(
                         "Error: Cannot resolve register: {register:?}"
@@ -1993,9 +1984,10 @@ impl UnitInfo {
             // Non-array members can inherit their memory location from their parent, but only if the parent has a valid memory location.
             if self.is_pointer(child_variable, parent_variable, unit_ref) {
                 match &parent_variable.memory_location {
-                    VariableLocation::Address(address) => {
+                    address @ (VariableLocation::Address(_)
+                    | VariableLocation::RegisterValue(_)) => {
                         // Now, retrieve the location by reading the adddress pointed to by the parent variable.
-                        match memory.read_word_32(*address) {
+                        match memory.read_word_32(address.memory_address().unwrap()) {
                             Ok(memory_location) => {
                                 VariableLocation::Address(memory_location as u64)
                             }
diff --git a/probe-rs-debug/src/variable.rs b/probe-rs-debug/src/variable.rs
index df284643..7088791a 100644
--- a/probe-rs-debug/src/variable.rs
+++ b/probe-rs-debug/src/variable.rs
@@ -3,6 +3,7 @@ use crate::{language::ProgrammingLanguage, unit_info::UnitInfo};
 use super::*;
 use gimli::{DebugInfoOffset, DwLang, UnitOffset};
 use itertools::Itertools;
+use probe_rs::RegisterValue;
 use std::ops::Range;
 
 /// Define the role that a variable plays in a Variant relationship. See section '5.7.10 Variant
@@ -403,7 +404,7 @@ impl VariableType {
 }
 
 /// Location of a variable
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
+#[derive(Debug, Clone, PartialEq, Default)]
 pub enum VariableLocation {
     /// Location of the variable is not known. This means that it has not been evaluated yet.
     #[default]
@@ -414,6 +415,8 @@ pub enum VariableLocation {
     Address(u64),
     /// The value of the variable is directly available.
     Value,
+    /// The variable is stored in a register, and the value is read from there.
+    RegisterValue(RegisterValue),
     /// There was an error evaluating the variable location.
     Error(String),
     /// Support for handling the location of this variable is not (yet) implemented.
@@ -425,6 +428,12 @@ impl VariableLocation {
     pub fn memory_address(&self) -> Result<u64, DebugError> {
         match self {
             VariableLocation::Address(address) => Ok(*address),
+            VariableLocation::RegisterValue(address) => match TryInto::<u64>::try_into(*address) {
+                Ok(address) => Ok(address),
+                Err(_) => Err(DebugError::WarnAndContinue {
+                    message: "Register value is not a valid address".to_string(),
+                }),
+            },
             other => Err(DebugError::WarnAndContinue {
                 message: format!("Variable does not have a memory location: location={other:?}"),
             }),
@@ -434,9 +443,10 @@ impl VariableLocation {
     /// Check if the location is valid, ie. not an error, unsupported, or unavailable.
     pub fn valid(&self) -> bool {
         match self {
-            VariableLocation::Address(_) | VariableLocation::Value | VariableLocation::Unknown => {
-                true
-            }
+            VariableLocation::Address(_)
+            | VariableLocation::RegisterValue(_)
+            | VariableLocation::Value
+            | VariableLocation::Unknown => true,
             _other => false,
         }
     }
@@ -447,7 +457,14 @@ impl std::fmt::Display for VariableLocation {
         match self {
             VariableLocation::Unknown => "<unknown value>".fmt(f),
             VariableLocation::Unavailable => "<value not available>".fmt(f),
-            VariableLocation::Address(address) => write!(f, "{address:#010X}"),
+            VariableLocation::Address(address) => {
+                write!(f, "{address:#010X}")
+            }
+            VariableLocation::RegisterValue(address) => match address {
+                RegisterValue::U32(value) => write!(f, "{value:#010X}"),
+                RegisterValue::U64(value) => write!(f, "{value:#018X}"),
+                RegisterValue::U128(value) => write!(f, "{value:#034X}"),
+            },
             VariableLocation::Value => "<not applicable - statically stored value>".fmt(f),
             VariableLocation::Error(error) => error.fmt(f),
             VariableLocation::Unsupported(reason) => reason.fmt(f),
@@ -459,7 +476,7 @@ impl std::fmt::Display for VariableLocation {
 ///
 /// Any modifications to the `Variable` value will be transient (lost when it goes out of scope),
 /// unless it is updated through one of the available methods on `VariableCache`.
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq)]
 pub struct Variable {
     /// Every variable must have a unique key value assigned to it.
     /// The value will be zero until it is stored in VariableCache, at which time its value will be
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/cargo_embed/mod.rs b/probe-rs-tools/src/bin/probe-rs/cmd/cargo_embed/mod.rs
index 9bc4c228..a547a70f 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/cargo_embed/mod.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/cargo_embed/mod.rs
@@ -291,18 +291,7 @@ fn main_try(args: &[OsString], offset: UtcOffset) -> Result<()> {
         let format_options = FormatOptions::default();
         let loader = build_loader(&mut session, &path, format_options, image_instr_set)?;
 
-        // When using RTT with a program in flash, the RTT header will be moved to RAM on
-        // startup, so clearing it before startup is ok. However, if we're downloading to the
-        // header's final address in RAM, then it's not relocated on startup and we should not
-        // clear it. This impacts static RTT headers, like used in defmt_rtt.
-        if let ScanRegion::Exact(address) = rtt_client.scan_region {
-            if loader.has_data_for_address(address) {
-                tracing::debug!(
-                    "RTT control block is initialized by flash loader. Disabling clearing."
-                );
-                rtt_client.disallow_clearing_rtt_header();
-            }
-        }
+        rtt_client.configure_from_loader(&loader);
 
         let boot_info = loader.boot_info();
 
diff --git a/probe-rs-tools/src/bin/probe-rs/rpc/client.rs b/probe-rs-tools/src/bin/probe-rs/rpc/client.rs
index 74fcf891..b014f8b2 100644
--- a/probe-rs-tools/src/bin/probe-rs/rpc/client.rs
+++ b/probe-rs-tools/src/bin/probe-rs/rpc/client.rs
@@ -104,9 +104,6 @@ pub async fn connect(host: &str, token: Option<String>) -> anyhow::Result<RpcCli
     )
     .await
     .context("Failed to connect")?;
-    let (tx, rx) = ws_stream.split();
-
-    let tx = WebsocketTx::new(tx);
 
     // Respond to the challenge
     let challenge = resp
@@ -119,9 +116,12 @@ pub async fn connect(host: &str, token: Option<String>) -> anyhow::Result<RpcCli
     let mut hasher = Sha512::new();
     hasher.update(challenge.as_bytes());
     hasher.update(token.unwrap_or_default().as_bytes());
-    let result = hasher.finalize().to_vec();
+    let challenge_response = hasher.finalize().to_vec();
 
-    tx.send(result)
+    let (tx, rx) = ws_stream.split();
+
+    let tx = WebsocketTx::new(tx);
+    tx.send(challenge_response)
         .await
         .map_err(|err| anyhow::anyhow!("Failed to send challenge response: {:?}", err))?;
 
diff --git a/probe-rs-tools/src/bin/probe-rs/rpc/functions/flash.rs b/probe-rs-tools/src/bin/probe-rs/rpc/functions/flash.rs
index daa2badc..5636fd13 100644
--- a/probe-rs-tools/src/bin/probe-rs/rpc/functions/flash.rs
+++ b/probe-rs-tools/src/bin/probe-rs/rpc/functions/flash.rs
@@ -1,9 +1,10 @@
+use std::time::Duration;
+
 use postcard_rpc::header::VarHeader;
 use postcard_schema::Schema;
 use probe_rs::{
     Session,
     flashing::{self, FileDownloadError, FlashLoader, FlashProgress},
-    rtt::ScanRegion,
 };
 use serde::{Deserialize, Serialize};
 use tokio::sync::mpsc::Sender;
@@ -76,6 +77,20 @@ pub struct FlashRequest {
     pub options: DownloadOptions,
     pub rtt_client: Option<Key<RttClient>>,
 }
+impl FlashRequest {
+    fn download_options<'a>(&self) -> flashing::DownloadOptions<'a> {
+        let mut options = probe_rs::flashing::DownloadOptions::default();
+
+        options.keep_unwritten_bytes = self.options.keep_unwritten_bytes;
+        options.do_chip_erase = self.options.do_chip_erase;
+        options.skip_erase = self.options.skip_erase;
+        options.preverify = false;
+        options.verify = self.options.verify;
+        options.disable_double_buffering = self.options.disable_double_buffering;
+
+        options
+    }
+}
 
 #[derive(Default, Clone, Serialize, Deserialize, Schema)]
 pub struct FlashLayout {
@@ -163,7 +178,7 @@ pub struct FlashDataBlockSpan {
     pub size: u64,
 }
 
-#[derive(Clone, Copy, Serialize, Deserialize, Schema)]
+#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Schema)]
 pub enum Operation {
     /// Reading back flash contents to restore erased regions that should be kept unchanged.
     Fill,
@@ -255,6 +270,18 @@ impl ProgressEvent {
 
         cb(event);
     }
+
+    pub fn is_operation(&self, operation: Operation) -> bool {
+        matches!(
+            self,
+            ProgressEvent::Started(op)
+            | ProgressEvent::Progress { operation: op, .. }
+            | ProgressEvent::Failed(op)
+            | ProgressEvent::Finished(op)
+            | ProgressEvent::AddProgressBar { operation: op, .. }
+            if *op == operation
+        )
+    }
 }
 
 /// Current boot information
@@ -271,6 +298,27 @@ pub enum BootInfo {
     Other,
 }
 
+impl BootInfo {
+    pub fn prepare(&self, session: &mut Session, core_id: usize) -> anyhow::Result<()> {
+        match self {
+            BootInfo::FromRam {
+                vector_table_addr, ..
+            } => {
+                // core should be already reset and halt by this point.
+                session.prepare_running_on_ram(*vector_table_addr)?;
+            }
+            BootInfo::Other => {
+                // reset the core to leave it in a consistent state after flashing
+                session
+                    .core(core_id)?
+                    .reset_and_halt(Duration::from_millis(100))?;
+            }
+        }
+
+        Ok(())
+    }
+}
+
 impl From<probe_rs::flashing::BootInfo> for BootInfo {
     fn from(boot_info: probe_rs::flashing::BootInfo) -> Self {
         match boot_info {
@@ -307,30 +355,11 @@ fn flash_impl(
     let loader = ctx.object_mut_blocking(request.loader);
 
     if let Some(rtt_client) = rtt_client.as_mut() {
-        // When using RTT with a program in flash, the RTT header will be moved to RAM on
-        // startup, so clearing it before startup is ok. However, if we're downloading to the
-        // header's final address in RAM, then it's not relocated on startup and we should not
-        // clear it. This impacts static RTT headers, like used in defmt_rtt.
-
-        if let ScanRegion::Exact(address) = rtt_client.scan_region {
-            if loader.has_data_for_address(address) {
-                tracing::debug!(
-                    "RTT control block is initialized by flash loader. Disabling clearing."
-                );
-                rtt_client.disallow_clearing_rtt_header()
-            }
-        }
+        rtt_client.configure_from_loader(&loader);
     }
 
-    let mut options = probe_rs::flashing::DownloadOptions::default();
-
-    options.keep_unwritten_bytes = request.options.keep_unwritten_bytes;
+    let mut options = request.download_options();
     options.dry_run = dry_run;
-    options.do_chip_erase = request.options.do_chip_erase;
-    options.skip_erase = request.options.skip_erase;
-    options.preverify = false;
-    options.verify = request.options.verify;
-    options.disable_double_buffering = request.options.disable_double_buffering;
     options.progress = Some(FlashProgress::new(move |event| {
         ProgressEvent::from_library_event(event, |event| sender.blocking_send(event).unwrap());
     }));
@@ -369,18 +398,7 @@ fn erase_impl(
     let progress = FlashProgress::new(move |event| {
         ProgressEvent::from_library_event(event, |event| {
             // Only emit Erase-related events.
-            if let ProgressEvent::AddProgressBar {
-                operation: Operation::Erase,
-                ..
-            }
-            | ProgressEvent::Started(Operation::Erase)
-            | ProgressEvent::Progress {
-                operation: Operation::Erase,
-                ..
-            }
-            | ProgressEvent::Failed(Operation::Erase)
-            | ProgressEvent::Finished(Operation::Erase) = event
-            {
+            if event.is_operation(Operation::Erase) {
                 sender.blocking_send(event).unwrap()
             }
         });
@@ -426,19 +444,8 @@ fn verify_impl(
 
     let progress = FlashProgress::new(move |event| {
         ProgressEvent::from_library_event(event, |event| {
-            // Only emit Erase-related events.
-            if let ProgressEvent::AddProgressBar {
-                operation: Operation::Verify,
-                ..
-            }
-            | ProgressEvent::Started(Operation::Verify)
-            | ProgressEvent::Progress {
-                operation: Operation::Verify,
-                ..
-            }
-            | ProgressEvent::Failed(Operation::Verify)
-            | ProgressEvent::Finished(Operation::Verify) = event
-            {
+            // Only emit Verify-related events.
+            if event.is_operation(Operation::Verify) {
                 sender.blocking_send(event).unwrap()
             }
         });
diff --git a/probe-rs-tools/src/bin/probe-rs/rpc/functions/monitor.rs b/probe-rs-tools/src/bin/probe-rs/rpc/functions/monitor.rs
index 5dba3d29..ec216956 100644
--- a/probe-rs-tools/src/bin/probe-rs/rpc/functions/monitor.rs
+++ b/probe-rs-tools/src/bin/probe-rs/rpc/functions/monitor.rs
@@ -24,6 +24,23 @@ pub enum MonitorMode {
     Run(BootInfo),
 }
 
+impl MonitorMode {
+    pub fn should_clear_rtt_header(&self) -> bool {
+        match self {
+            MonitorMode::Run(BootInfo::FromRam { .. }) => true,
+            MonitorMode::Run(BootInfo::Other) => true,
+            MonitorMode::AttachToRunning => false,
+        }
+    }
+
+    pub fn prepare(&self, session: &mut Session, core_id: usize) -> anyhow::Result<()> {
+        match self {
+            MonitorMode::Run(boot_info) => boot_info.prepare(session, core_id),
+            MonitorMode::AttachToRunning => Ok(()),
+        }
+    }
+}
+
 #[derive(Serialize, Deserialize, Schema)]
 pub enum MonitorEvent {
     RttDiscovered {
@@ -94,28 +111,10 @@ fn monitor_impl(
         cancellation_token: ctx.cancellation_token(),
     };
 
-    let mut clear_control_block = true;
-    match request.mode {
-        MonitorMode::Run(BootInfo::FromRam {
-            vector_table_addr, ..
-        }) => {
-            // core should be already reset and halt by this point.
-            session.prepare_running_on_ram(vector_table_addr)?;
-        }
-        MonitorMode::Run(BootInfo::Other) => {
-            // reset the core to leave it in a consistent state after flashing
-            session
-                .core(core_id)?
-                .reset_and_halt(Duration::from_millis(100))?;
-        }
-        MonitorMode::AttachToRunning => {
-            // do nothing
-            clear_control_block = false;
-        }
-    }
+    request.mode.prepare(&mut session, run_loop.core_id)?;
 
     let mut core = session.core(run_loop.core_id)?;
-    if clear_control_block {
+    if request.mode.should_clear_rtt_header() {
         if let Some(rtt_client) = rtt_client.as_mut() {
             rtt_client.clear_control_block(&mut core)?;
         }
@@ -240,15 +239,13 @@ impl<F: FnMut(MonitorEvent)> MonitorEventHandler<F> {
                 Ok(None) // Continue running
             }
             SemihostingCommand::Errno(_) => Ok(None),
-            other @ (SemihostingCommand::Open(_)
-            | SemihostingCommand::Close(_)
-            | SemihostingCommand::WriteConsole(_)
-            | SemihostingCommand::Write(_)) => {
+            other if SemihostingReader::is_io(other) => {
                 if let Some(output) = self.semihosting_reader.handle(other, core)? {
                     (self.sender)(MonitorEvent::SemihostingOutput(output));
                 }
                 Ok(None)
             }
+            other => anyhow::bail!("Unexpected semihosting command {:?}", other),
         }
     }
 }
@@ -275,6 +272,16 @@ impl SemihostingReader {
         }
     }
 
+    pub fn is_io(other: SemihostingCommand) -> bool {
+        matches!(
+            other,
+            SemihostingCommand::Open(_)
+                | SemihostingCommand::Close(_)
+                | SemihostingCommand::WriteConsole(_)
+                | SemihostingCommand::Write(_)
+        )
+    }
+
     pub fn handle(
         &mut self,
         command: SemihostingCommand,
diff --git a/probe-rs-tools/src/bin/probe-rs/rpc/functions/probe.rs b/probe-rs-tools/src/bin/probe-rs/rpc/functions/probe.rs
index 35246e65..9dff1a4a 100644
--- a/probe-rs-tools/src/bin/probe-rs/rpc/functions/probe.rs
+++ b/probe-rs-tools/src/bin/probe-rs/rpc/functions/probe.rs
@@ -232,6 +232,9 @@ pub async fn attach(
     };
 
     let mut session = common_options.attach_session(probe, target)?;
+
+    // attach_session halts the target, let's give the user the option
+    // to resume it without a roundtrip
     if request.resume_target {
         session.resume_all_cores()?;
     }
diff --git a/probe-rs-tools/src/bin/probe-rs/rpc/transport/websocket.rs b/probe-rs-tools/src/bin/probe-rs/rpc/transport/websocket.rs
index 0bfd60df..9c35ca83 100644
--- a/probe-rs-tools/src/bin/probe-rs/rpc/transport/websocket.rs
+++ b/probe-rs-tools/src/bin/probe-rs/rpc/transport/websocket.rs
@@ -109,9 +109,7 @@ where
             return Err(WireTxErrorKind::Other);
         }
 
-        let mut bytes = BytesMut::with_capacity(4 + msg.len());
-        bytes.put_u32_le(msg.len() as u32);
-        bytes.put_slice(&msg);
+        let bytes = prefix_with_length(&msg);
 
         self.writer
             .lock()
@@ -144,9 +142,7 @@ where
     }
 
     fn start_send(mut self: Pin<&mut Self>, msg: Vec<u8>) -> Result<(), Self::Error> {
-        let mut bytes = BytesMut::with_capacity(4 + msg.len());
-        bytes.put_u32_le(msg.len() as u32);
-        bytes.put_slice(&msg);
+        let bytes = prefix_with_length(&msg);
 
         self.writer
             .start_send_unpin(ws::Message::Binary(bytes.freeze()))
@@ -160,3 +156,10 @@ where
         self.writer.poll_close_unpin(cx)
     }
 }
+
+fn prefix_with_length(msg: &[u8]) -> BytesMut {
+    let mut bytes = BytesMut::with_capacity(4 + msg.len());
+    bytes.put_u32_le(msg.len() as u32);
+    bytes.put_slice(msg);
+    bytes
+}
diff --git a/probe-rs-tools/src/bin/probe-rs/rpc/utils/run_loop.rs b/probe-rs-tools/src/bin/probe-rs/rpc/utils/run_loop.rs
index 2ab3ee25..9509ca6e 100644
--- a/probe-rs-tools/src/bin/probe-rs/rpc/utils/run_loop.rs
+++ b/probe-rs-tools/src/bin/probe-rs/rpc/utils/run_loop.rs
@@ -1,5 +1,6 @@
 use tokio_util::sync::CancellationToken;
 
+use std::ops::ControlFlow;
 use std::thread;
 use std::time::{Duration, Instant};
 
@@ -66,9 +67,8 @@ impl RunLoop {
         if core.core_halted()? {
             core.run()?;
         }
-        let start = Instant::now();
 
-        let result = self.do_run_until(core, &mut poller, timeout, start, &mut predicate);
+        let result = self.do_run_until(core, &mut poller, timeout, &mut predicate);
 
         // Always clean up after RTT but don't overwrite the original result.
         let poller_exit_result = poller.exit(core);
@@ -82,68 +82,88 @@ impl RunLoop {
 
     fn do_run_until<F, R>(
         &mut self,
-        core: &mut Core,
+        core: &mut Core<'_>,
         poller: &mut impl RunLoopPoller,
         timeout: Option<Duration>,
-        start: Instant,
         predicate: &mut F,
     ) -> Result<ReturnReason<R>>
     where
         F: FnMut(HaltReason, &mut Core) -> Result<Option<R>>,
     {
+        let start = Instant::now();
+
         loop {
-            let mut next_poll = Duration::from_millis(100);
-
-            // check for halt first, poll rtt after.
-            // this is important so we do one last poll after halt, so we flush all messages
-            // the core printed before halting, such as a panic message.
-            let mut return_reason = None;
-            match core.status()? {
-                probe_rs::CoreStatus::Halted(reason) => match predicate(reason, core) {
-                    Ok(Some(r)) => return_reason = Some(Ok(ReturnReason::Predicate(r))),
-                    Err(e) => return_reason = Some(Err(e)),
-                    Ok(None) => {
-                        // Poll at 1kHz if the core was halted, to speed up reading strings
-                        // from semihosting. The core is not expected to be halted for other reasons.
-                        next_poll = Duration::from_millis(1);
-                        core.run()?;
+            match self.poll_once(core, poller, predicate)? {
+                ControlFlow::Break(reason) => return Ok(reason),
+                ControlFlow::Continue(next_poll) => {
+                    if let Some(timeout) = timeout {
+                        if start.elapsed() >= timeout {
+                            return Ok(ReturnReason::Timeout);
+                        }
                     }
-                },
-                probe_rs::CoreStatus::Running
-                | probe_rs::CoreStatus::Sleeping
-                | probe_rs::CoreStatus::Unknown => {
-                    // Carry on
-                }
 
-                probe_rs::CoreStatus::LockedUp => anyhow::bail!("The core is locked up."),
+                    // If the polling frequency is too high, the USB connection to the probe
+                    // can become unstable. Hence we only poll as little as necessary.
+                    thread::sleep(next_poll);
+                }
             }
+        }
+    }
 
-            let poller_result = poller.poll(core);
-
-            if let Some(reason) = return_reason {
-                return reason;
-            } else if let Some(timeout) = timeout {
-                if start.elapsed() >= timeout {
-                    return Ok(ReturnReason::Timeout);
+    fn poll_once<F, R>(
+        &self,
+        core: &mut Core<'_>,
+        poller: &mut impl RunLoopPoller,
+        predicate: &mut F,
+    ) -> Result<ControlFlow<ReturnReason<R>, Duration>>
+    where
+        F: FnMut(HaltReason, &mut Core) -> Result<Option<R>>,
+    {
+        let mut next_poll = Duration::from_millis(100);
+
+        // check for halt first, poll rtt after.
+        // this is important so we do one last poll after halt, so we flush all messages
+        // the core printed before halting, such as a panic message.
+        let mut return_reason = None;
+        match core.status()? {
+            probe_rs::CoreStatus::Halted(reason) => match predicate(reason, core) {
+                Ok(Some(r)) => return_reason = Some(Ok(ReturnReason::Predicate(r))),
+                Err(e) => return_reason = Some(Err(e)),
+                Ok(None) => {
+                    // Poll at 1kHz if the core was halted, to speed up reading strings
+                    // from semihosting. The core is not expected to be halted for other reasons.
+                    next_poll = Duration::from_millis(1);
+                    core.run()?;
                 }
-            }
-            if self.cancellation_token.is_cancelled() {
-                return Ok(ReturnReason::Cancelled);
-            }
-            match poller_result {
-                Ok(delay) => next_poll = next_poll.min(delay),
-                Err(error) => return Err(error),
+            },
+            probe_rs::CoreStatus::Running
+            | probe_rs::CoreStatus::Sleeping
+            | probe_rs::CoreStatus::Unknown => {
+                // Carry on
             }
 
-            // If the polling frequency is too high, the USB connection to the probe
-            // can become unstable. Hence we only poll as little as necessary.
-            thread::sleep(next_poll);
+            probe_rs::CoreStatus::LockedUp => anyhow::bail!("The core is locked up."),
         }
+
+        let poller_result = poller.poll(core);
+
+        if let Some(reason) = return_reason {
+            return reason.map(ControlFlow::Break);
+        }
+        if self.cancellation_token.is_cancelled() {
+            return Ok(ControlFlow::Break(ReturnReason::Cancelled));
+        }
+        match poller_result {
+            Ok(delay) => next_poll = next_poll.min(delay),
+            Err(error) => return Err(error),
+        }
+
+        Ok(ControlFlow::Continue(next_poll))
     }
 }
 
 pub trait RunLoopPoller {
-    fn start(&mut self, core: &mut Core<'_>) -> anyhow::Result<()>;
+    fn start(&mut self, core: &mut Core<'_>) -> Result<()>;
     fn poll(&mut self, core: &mut Core<'_>) -> Result<Duration>;
     fn exit(&mut self, core: &mut Core<'_>) -> Result<()>;
 }
@@ -151,7 +171,7 @@ pub trait RunLoopPoller {
 pub struct NoopPoller;
 
 impl RunLoopPoller for NoopPoller {
-    fn start(&mut self, _core: &mut Core<'_>) -> anyhow::Result<()> {
+    fn start(&mut self, _core: &mut Core<'_>) -> Result<()> {
         Ok(())
     }
 
@@ -168,7 +188,7 @@ impl<T> RunLoopPoller for Option<T>
 where
     T: RunLoopPoller,
 {
-    fn start(&mut self, core: &mut Core<'_>) -> anyhow::Result<()> {
+    fn start(&mut self, core: &mut Core<'_>) -> Result<()> {
         if let Some(poller) = self {
             poller.start(core)
         } else {
diff --git a/probe-rs-tools/src/bin/probe-rs/util/rtt/client.rs b/probe-rs-tools/src/bin/probe-rs/util/rtt/client.rs
index faa22ec1..a3277eed 100644
--- a/probe-rs-tools/src/bin/probe-rs/util/rtt/client.rs
+++ b/probe-rs-tools/src/bin/probe-rs/util/rtt/client.rs
@@ -3,6 +3,7 @@ use crate::util::rtt::{
 };
 use probe_rs::{
     Core, MemoryInterface,
+    flashing::FlashLoader,
     rtt::{Error, Rtt, ScanRegion},
 };
 
@@ -50,6 +51,22 @@ impl RttClient {
         self.disallow_clearing_rtt_header = true;
     }
 
+    pub fn configure_from_loader(&mut self, loader: &FlashLoader) {
+        // When using RTT with a program in flash, the RTT header will be moved to RAM on
+        // startup, so clearing it before startup is ok. However, if we're downloading to the
+        // header's final address in RAM, then it's not relocated on startup and we should not
+        // clear it. This impacts static RTT headers, like used in defmt_rtt.
+
+        if let ScanRegion::Exact(address) = self.scan_region {
+            if loader.has_data_for_address(address) {
+                tracing::debug!(
+                    "RTT control block is initialized by flash loader. Disabling clearing."
+                );
+                self.disallow_clearing_rtt_header()
+            }
+        }
+    }
+
     pub fn is_attached(&self) -> bool {
         self.target.is_some()
     }
diff --git a/probe-rs/src/architecture/xtensa/communication_interface.rs b/probe-rs/src/architecture/xtensa/communication_interface.rs
index 1a18dafe..f7e26725 100644
--- a/probe-rs/src/architecture/xtensa/communication_interface.rs
+++ b/probe-rs/src/architecture/xtensa/communication_interface.rs
@@ -116,8 +116,8 @@ pub struct XtensaCoreProperties {
     /// The interrupt level at which debug exceptions are generated. CPU-specific configuration value.
     pub debug_level: DebugLevel,
 
-    /// The address range for which we should not use LDDR32.P/SDDR32.P instructions.
-    pub slow_memory_access_ranges: Vec<Range<u64>>,
+    /// The address ranges for which we can use LDDR32.P/SDDR32.P instructions.
+    pub fast_memory_access_ranges: Vec<Range<u64>>,
 
     /// Configurable options in the Windowed Register Option
     pub window_option_properties: WindowProperties,
@@ -128,7 +128,7 @@ impl Default for XtensaCoreProperties {
         Self {
             hw_breakpoint_num: 2,
             debug_level: DebugLevel::L6,
-            slow_memory_access_ranges: vec![],
+            fast_memory_access_ranges: vec![],
             window_option_properties: WindowProperties::lx(64),
         }
     }
@@ -642,16 +642,16 @@ impl<'probe> XtensaCommunicationInterface<'probe> {
     }
 
     fn memory_access_for(&self, address: u64, len: usize) -> Box<dyn MemoryAccess> {
-        let use_slow_access = self
+        let use_fast_access = self
             .core_properties
-            .slow_memory_access_ranges
+            .fast_memory_access_ranges
             .iter()
             .any(|r| r.intersects_range(&(address..address + len as u64)));
 
-        if use_slow_access {
-            Box::new(SlowMemoryAccess::new())
-        } else {
+        if use_fast_access {
             Box::new(FastMemoryAccess::new())
+        } else {
+            Box::new(SlowMemoryAccess::new())
         }
     }
 
diff --git a/probe-rs/src/architecture/xtensa/xdm.rs b/probe-rs/src/architecture/xtensa/xdm.rs
index a72b3574..448048e8 100644
--- a/probe-rs/src/architecture/xtensa/xdm.rs
+++ b/probe-rs/src/architecture/xtensa/xdm.rs
@@ -333,6 +333,15 @@ impl<'probe> Xdm<'probe> {
                             // The instruction is still executing. Retry the Debug Status read.
                             to_consume -= 1;
                         }
+                        ProbeRsError::Xtensa(XtensaError::XdmError(Error::ExecExeception)) => {
+                            // Clear exception to allow executing further instructions.
+                            self.clear_exception_state()?;
+                            // TODO: in the future, we might want to bubble up the exception cause.
+                            // We might also want to store this error for each result that has not
+                            // yet been read.
+                            return Err(XtensaError::XdmError(Error::ExecExeception));
+                        }
+
                         ProbeRsError::Probe(error) => return Err(error.into()),
                         ProbeRsError::Xtensa(error) => return Err(error),
                         other => panic!("Unexpected error: {other}"),
@@ -669,6 +678,18 @@ impl<'probe> Xdm<'probe> {
 
         Ok(())
     }
+
+    fn clear_exception_state(&mut self) -> Result<(), XtensaError> {
+        self.write_nexus_register({
+            let mut status = DebugStatus(0);
+
+            status.set_exec_exception(true);
+            status.set_exec_done(true);
+            status.set_exec_overrun(true);
+
+            status
+        })
+    }
 }
 
 type TransformFn = fn(&ShiftDrCommand, Vec<u8>) -> Result<CommandResult, ProbeRsError>;
diff --git a/probe-rs/src/vendor/espressif/sequences/esp32.rs b/probe-rs/src/vendor/espressif/sequences/esp32.rs
index fd43b0ce..87ef32e4 100644
--- a/probe-rs/src/vendor/espressif/sequences/esp32.rs
+++ b/probe-rs/src/vendor/espressif/sequences/esp32.rs
@@ -77,11 +77,13 @@ impl ESP32 {
 
 impl XtensaDebugSequence for ESP32 {
     fn on_connect(&self, interface: &mut XtensaCommunicationInterface) -> Result<(), crate::Error> {
-        // Peripheral address range
         interface
             .core_properties()
-            .slow_memory_access_ranges
-            .push(0x3FF0_0000..0x3FF8_0000);
+            .fast_memory_access_ranges
+            .extend_from_slice(&[
+                0x3FF8_0000..0x4000_0000, // Data
+                0x4000_0000..0x400C_2000, // Instruction
+            ]);
 
         self.disable_wdts(interface)
     }
diff --git a/probe-rs/src/vendor/espressif/sequences/esp32s2.rs b/probe-rs/src/vendor/espressif/sequences/esp32s2.rs
index eada1733..87516058 100644
--- a/probe-rs/src/vendor/espressif/sequences/esp32s2.rs
+++ b/probe-rs/src/vendor/espressif/sequences/esp32s2.rs
@@ -146,6 +146,14 @@ impl ESP32S2 {
 
 impl XtensaDebugSequence for ESP32S2 {
     fn on_connect(&self, interface: &mut XtensaCommunicationInterface) -> Result<(), crate::Error> {
+        interface
+            .core_properties()
+            .fast_memory_access_ranges
+            .extend_from_slice(&[
+                0x3FF9_E000..0x4000_0000, // Data
+                0x4000_0000..0x4007_2000, // Instruction
+            ]);
+
         self.disable_wdts(interface)
     }
 
diff --git a/probe-rs/src/vendor/espressif/sequences/esp32s3.rs b/probe-rs/src/vendor/espressif/sequences/esp32s3.rs
index f22182dd..f174b6e0 100644
--- a/probe-rs/src/vendor/espressif/sequences/esp32s3.rs
+++ b/probe-rs/src/vendor/espressif/sequences/esp32s3.rs
@@ -83,11 +83,15 @@ impl ESP32S3 {
 
 impl XtensaDebugSequence for ESP32S3 {
     fn on_connect(&self, interface: &mut XtensaCommunicationInterface) -> Result<(), crate::Error> {
-        // External memory bus
         interface
             .core_properties()
-            .slow_memory_access_ranges
-            .push(0x3C00_0000..0x3E00_0000);
+            .fast_memory_access_ranges
+            .extend_from_slice(&[
+                0x3FC8_8000..0x3FD0_0000, // Internal DRAM
+                0x3FF0_0000..0x3FF2_0000, // Internal DROM
+                0x4000_0000..0x4006_0000, // Internal IROM
+                0x4037_0000..0x403E_0000, // Internal IRAM
+            ]);
 
         self.disable_wdts(interface)
     }


Test patch:
diff --git a/probe-rs-debug/src/snapshots/probe_rs_debug__debug_info__test__esp32s3_esp_hal_panic.snap b/probe-rs-debug/src/snapshots/probe_rs_debug__debug_info__test__esp32s3_esp_hal_panic.snap
index 2bf1ee0d..a2627e18 100644
--- a/probe-rs-debug/src/snapshots/probe_rs_debug__debug_info__test__esp32s3_esp_hal_panic.snap
+++ b/probe-rs-debug/src/snapshots/probe_rs_debug__debug_info__test__esp32s3_esp_hal_panic.snap
@@ -5103,55 +5103,10 @@ expression: stack_frames
             address: ~
           children:
             - name:
-                Named: Some
+                Named: None
               type_name:
-                Struct: Some
-              value: Some @ 0x00000000
-              children:
-                - name:
-                    Named: __0
-                  type_name:
-                    Struct: TaskRef
-                  value: TaskRef @ 0x00000000
-                  children:
-                    - name:
-                        Named: ptr
-                      type_name:
-                        Struct: "NonNull<embassy_executor::raw::TaskHeader>"
-                      value: "NonNull<embassy_executor::raw::TaskHeader> @ 0x00000000"
-                      children:
-                        - name:
-                            Named: pointer
-                          type_name:
-                            Pointer: "*const embassy_executor::raw::TaskHeader"
-                          value: "*const embassy_executor::raw::TaskHeader @ 0x00000000"
-                          children:
-                            - name:
-                                Named: "*pointer"
-                              type_name:
-                                Struct: TaskHeader
-                              value: "TaskHeader {\n\tstate: <unknown> = < Failed to read referenced variable address from memory location 0x00000000 : The coredump does not include the memory for address 0x0 of size 0x4. >,\n\trun_queue_item: <unknown> = < Failed to read referenced variable address from memory location 0x00000000 : The coredump does not include the memory for address 0x0 of size 0x4. >,\n\texecutor: <unknown> = < Failed to read referenced variable address from memory location 0x00000000 : The coredump does not include the memory for address 0x0 of size 0x4. >,\n\tpoll_fn: <unknown> = < Failed to read referenced variable address from memory location 0x00000000 : The coredump does not include the memory for address 0x0 of size 0x4. >,\n\ttimer_queue_item: <unknown> = < Failed to read referenced variable address from memory location 0x00000000 : The coredump does not include the memory for address 0x0 of size 0x4. >}"
-                              children:
-                                - name:
-                                    Named: state
-                                  type_name: Unknown
-                                  value: "< Failed to read referenced variable address from memory location 0x00000000 : The coredump does not include the memory for address 0x0 of size 0x4. >"
-                                - name:
-                                    Named: run_queue_item
-                                  type_name: Unknown
-                                  value: "< Failed to read referenced variable address from memory location 0x00000000 : The coredump does not include the memory for address 0x0 of size 0x4. >"
-                                - name:
-                                    Named: executor
-                                  type_name: Unknown
-                                  value: "< Failed to read referenced variable address from memory location 0x00000000 : The coredump does not include the memory for address 0x0 of size 0x4. >"
-                                - name:
-                                    Named: poll_fn
-                                  type_name: Unknown
-                                  value: "< Failed to read referenced variable address from memory location 0x00000000 : The coredump does not include the memory for address 0x0 of size 0x4. >"
-                                - name:
-                                    Named: timer_queue_item
-                                  type_name: Unknown
-                                  value: "< Failed to read referenced variable address from memory location 0x00000000 : The coredump does not include the memory for address 0x0 of size 0x4. >"
+                Struct: None
+              value: None @ 0x00000000
         - name:
             Named: task
           type_name:
@@ -6127,7 +6082,7 @@ expression: stack_frames
     Child Variables:
       name: LocalScopeRoot
       type_name: Unknown
-      value: "<unknown> {\n\tself: &mut esp_hal_embassy::executor::thread::Executor = &mut esp_hal_embassy::executor::thread::Executor @ 0x3FCDC6B0,\n\tinit: {closure_env#0} = {closure_env#0} @ <unknown value>,\n\tcpu: usize = < Probe(Other(\"The coredump does not include the memory for address 0x0 of size 0x4\")) >}"
+      value: "<unknown> {\n\tself: &mut esp_hal_embassy::executor::thread::Executor = &mut esp_hal_embassy::executor::thread::Executor @ 0x3FCDC6B0,\n\tinit: {closure_env#0} = {closure_env#0} @ <unknown value>,\n\tcpu: usize = 0}"
       children:
         - name:
             Named: self
@@ -6174,7 +6129,7 @@ expression: stack_frames
             Named: cpu
           type_name:
             Base: usize
-          value: "< Probe(Other(\"The coredump does not include the memory for address 0x0 of size 0x4\")) >"
+          value: "0"
           source_location:
             path: "C:\\Users\\bugad\\.cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\esp-hal-embassy-0.7.0\\src\\executor\\thread.rs"
             line: 108
diff --git a/probe-rs-tools/src/bin/probe-rs/rpc/functions/test.rs b/probe-rs-tools/src/bin/probe-rs/rpc/functions/test.rs
index d1c59410..adef7b55 100644
--- a/probe-rs-tools/src/bin/probe-rs/rpc/functions/test.rs
+++ b/probe-rs-tools/src/bin/probe-rs/rpc/functions/test.rs
@@ -144,31 +144,18 @@ fn list_tests_impl(
 
     let core_id = rtt_client.as_ref().map(|rtt| rtt.core_id()).unwrap_or(0);
 
-    match request.boot_info {
-        BootInfo::FromRam {
-            vector_table_addr, ..
-        } => {
-            // core should be already reset and halt by this point.
-            session.prepare_running_on_ram(vector_table_addr)?;
-        }
-        BootInfo::Other => {
-            // reset the core to leave it in a consistent state after flashing
-            session
-                .core(core_id)?
-                .reset_and_halt(Duration::from_millis(100))?;
-        }
-    }
+    let mut run_loop = RunLoop {
+        core_id,
+        cancellation_token: ctx.cancellation_token(),
+    };
+
+    request.boot_info.prepare(&mut session, run_loop.core_id)?;
 
     let mut core = session.core(0)?;
     if let Some(rtt_client) = rtt_client.as_mut() {
         rtt_client.clear_control_block(&mut core)?;
     }
 
-    let mut run_loop = RunLoop {
-        core_id,
-        cancellation_token: ctx.cancellation_token(),
-    };
-
     let poller = rtt_client.as_deref_mut().map(|client| RttPoller {
         rtt_client: client,
         sender: sender.clone(),
@@ -317,12 +304,7 @@ impl<F: FnMut(MonitorEvent)> ListEventHandler<F> {
             SemihostingCommand::Unknown(details)
                 if details.operation == Self::SEMIHOSTING_USER_LIST && self.cmdline_requested =>
             {
-                let buf = details.get_buffer(core)?;
-                let buf = buf.read(core)?;
-                let list = serde_json::from_slice::<TestDefinitions>(&buf[..])?;
-
-                // Signal status=success back to the target
-                details.write_status(core, 0)?;
+                let list = read_test_list(details, core)?;
 
                 tracing::debug!("got list of tests from target: {list:?}");
                 if list.version != 1 {
@@ -331,10 +313,7 @@ impl<F: FnMut(MonitorEvent)> ListEventHandler<F> {
 
                 Ok(Some(list.into()))
             }
-            other @ (SemihostingCommand::Open(_)
-            | SemihostingCommand::Close(_)
-            | SemihostingCommand::WriteConsole(_)
-            | SemihostingCommand::Write(_)) => {
+            other if SemihostingReader::is_io(other) => {
                 if let Some(output) = self.semihosting_reader.handle(other, core)? {
                     (self.sender)(MonitorEvent::SemihostingOutput(output));
                 }
@@ -350,6 +329,20 @@ impl<F: FnMut(MonitorEvent)> ListEventHandler<F> {
     }
 }
 
+fn read_test_list(
+    details: probe_rs::semihosting::UnknownCommandDetails,
+    core: &mut Core<'_>,
+) -> anyhow::Result<TestDefinitions> {
+    let buf = details.get_buffer(core)?;
+    let buf = buf.read(core)?;
+    let list = serde_json::from_slice::<TestDefinitions>(&buf[..])?;
+
+    // Signal status=success back to the target
+    details.write_status(core, 0)?;
+
+    Ok(list)
+}
+
 struct RunEventHandler<F: FnMut(MonitorEvent)> {
     semihosting_reader: SemihostingReader,
     cmdline_requested: bool,
@@ -395,10 +388,7 @@ impl<F: FnMut(MonitorEvent)> RunEventHandler<F> {
             SemihostingCommand::ExitError(_) if self.cmdline_requested => {
                 Ok(Some(TestOutcome::Panic))
             }
-            other @ (SemihostingCommand::Open(_)
-            | SemihostingCommand::Close(_)
-            | SemihostingCommand::WriteConsole(_)
-            | SemihostingCommand::Write(_)) => {
+            other if SemihostingReader::is_io(other) => {
                 if let Some(output) = self.semihosting_reader.handle(other, core)? {
                     (self.sender)(MonitorEvent::SemihostingOutput(output));
                 }